From a3ae825d5d03fdbac30728cf04bc622f0648ddb6 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 7 Apr 2024 02:17:10 +0100 Subject: [PATCH 001/167] Initial commit --- .gitignore | 4 + .nvmrc | 1 + config.default.json5 | 10 + nodemon.json | 6 + package-lock.json | 11289 +++++++++++++++++++++++++++++++++++++++++ package.json | 58 + src/config.js | 17 + src/helia.ts | 32 + src/index.js | 387 ++ src/libp2p.js | 54 + src/store.js | 5 + tsconfig.json | 12 + 12 files changed, 11875 insertions(+) create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 config.default.json5 create mode 100644 nodemon.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/config.js create mode 100644 src/helia.ts create mode 100644 src/index.js create mode 100644 src/libp2p.js create mode 100644 src/store.js create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b35a16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +blockstore/ +datastore/ +.idea diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..e6a1787 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.* diff --git a/config.default.json5 b/config.default.json5 new file mode 100644 index 0000000..ee2ef3b --- /dev/null +++ b/config.default.json5 @@ -0,0 +1,10 @@ +{ + nodes: [ + // ipfs0 + "/ip4/5.161.75.77/tcp/4001/p2p/12D3KooWE849ZdcQhLP2yE4fbmU89APmjYXJPeuNChNUBGgeJJYe", + // ipf1 + "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ", + // ipf2 + // ipf3 + ], +} diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..dcb0655 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src"], + "ext": "js,ts,json", + "ignore": ["src/**/*.spec.ts"], + "exec": "node src/index.js" +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..213aff4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11289 @@ +{ + "name": "ipfs-node", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ipfs-node", + "version": "0.0.1", + "license": "GPLv3", + "dependencies": { + "@chainsafe/libp2p-gossipsub": "^13.0.0", + "@chainsafe/libp2p-noise": "^15.0.0", + "@chainsafe/libp2p-yamux": "^6.0.2", + "@helia/mfs": "^3.0.2", + "@helia/unixfs": "^3.0.2", + "@libp2p/autonat": "^1.0.14", + "@libp2p/bootstrap": "^10.0.17", + "@libp2p/circuit-relay-v2": "^1.0.17", + "@libp2p/identify": "^1.0.16", + "@libp2p/kad-dht": "^12.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-id-factory": "^4.0.8", + "@libp2p/tcp": "^9.0.17", + "@libp2p/upnp-nat": "^1.0.15", + "@libp2p/webrtc": "^4.0.23", + "blockstore-core": "^4.4.0", + "blockstore-fs": "^1.1.10", + "busboy": "^1.6.0", + "datastore-core": "^9.2.9", + "datastore-fs": "^9.1.8", + "express": "^4.19.2", + "helia": "^4.1.0", + "it-last": "^3.0.4", + "json5": "^2.2.3", + "libp2p": "^1.3.1", + "multer": "^1.4.5-lts.1", + "multiformats": "^13.1.0" + }, + "devDependencies": { + "@types/busboy": "^1.5.3", + "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", + "@types/node": "^20.11.30", + "cross-env": "^7.0.3", + "nodemon": "^3.1.0", + "prettier": "^3.2.5", + "ts-node": "^10.9.2", + "typescript": "^5.4.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@achingbrain/nat-port-mapper": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@achingbrain/nat-port-mapper/-/nat-port-mapper-1.0.13.tgz", + "integrity": "sha512-B5GL6ILDek72OjoEyFGEuuNYaEOYxO06Ulhcaf/5iQ4EO8uaZWS+OkolYST7L+ecJrkjfaSNmSAsWRRuh+1Z5A==", + "dependencies": { + "@achingbrain/ssdp": "^4.0.1", + "@libp2p/logger": "^4.0.1", + "default-gateway": "^7.2.2", + "err-code": "^3.0.1", + "it-first": "^3.0.1", + "p-defer": "^4.0.0", + "p-timeout": "^6.1.1", + "xml2js": "^0.6.0" + } + }, + "node_modules/@achingbrain/ssdp": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@achingbrain/ssdp/-/ssdp-4.0.6.tgz", + "integrity": "sha512-Y4JE2L9150i50V6lg/Y8+ilhxRpUZKKv+PKo68Aj7MjPfaUAar6ZHilF9h4/Zb3q0fqGMXNc9o11cQLNI8J8bA==", + "dependencies": { + "event-iterator": "^2.0.0", + "freeport-promise": "^2.0.0", + "merge-options": "^3.0.4", + "xml2js": "^0.6.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", + "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "peer": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", + "peer": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "peer": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", + "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", + "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "peer": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "peer": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "peer": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "peer": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "peer": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "peer": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.1.tgz", + "integrity": "sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-export-default-from": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "peer": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.1.tgz", + "integrity": "sha512-cNXSxv9eTkGUtd0PsNMK8Yx5xeScxfpWOUAxE+ZPAXXEcAMOC3fk7LRdXq5fvpra2pLx2p1YtkAhpUbB2SwaRA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz", + "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz", + "integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "peer": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz", + "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-flow": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "peer": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", + "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", + "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", + "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", + "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", + "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", + "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz", + "integrity": "sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz", + "integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.24.1", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.1", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.1", + "@babel/plugin-transform-classes": "^7.24.1", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.1", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.1", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.1", + "@babel/plugin-transform-parameters": "^7.24.1", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.1", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.1", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.1.tgz", + "integrity": "sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-transform-flow-strip-types": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", + "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", + "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", + "peer": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "peer": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "peer": true + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@chainsafe/as-chacha20poly1305": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz", + "integrity": "sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew==" + }, + "node_modules/@chainsafe/as-sha256": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.4.1.tgz", + "integrity": "sha512-IqeeGwQihK6Y2EYLFofqs2eY2ep1I2MvQXHzOAI+5iQN51OZlUkrLgyAugu2x86xZewDk5xas7lNczkzFzF62w==" + }, + "node_modules/@chainsafe/is-ip": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.0.2.tgz", + "integrity": "sha512-ndGqEMG1W5WkGagaqOZHpPU172AGdxr+LD15sv3WIUvT5oCFUrG1Y0CW/v2Egwj4JXEvSibaIIIqImsm98y1nA==" + }, + "node_modules/@chainsafe/libp2p-gossipsub": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-gossipsub/-/libp2p-gossipsub-13.0.0.tgz", + "integrity": "sha512-2q+v429uZjMl6N3d+j9QCMj8YO0aiYvLSN1t0aTdpMXQHCHLJaaT9PtNn845B1Li7/uZjYESmikgVt8r7keH0w==", + "dependencies": { + "@libp2p/crypto": "^4.0.1", + "@libp2p/interface": "^1.1.2", + "@libp2p/interface-internal": "^1.0.7", + "@libp2p/peer-id": "^4.0.5", + "@libp2p/pubsub": "^9.0.8", + "@multiformats/multiaddr": "^12.1.14", + "denque": "^2.1.0", + "it-length-prefixed": "^9.0.4", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "multiformats": "^13.0.1", + "protons-runtime": "5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.1" + }, + "engines": { + "npm": ">=8.7.0" + } + }, + "node_modules/@chainsafe/libp2p-noise": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-15.0.0.tgz", + "integrity": "sha512-O8Y/WVU4J/qrnG72jwVhbmdXiBzv1dT9B3PMClCRmZ9z/5vVPEGRVXE/SVYeGF3bNuBTLoh+F+GaKG/9UHlMhg==", + "dependencies": { + "@chainsafe/as-chacha20poly1305": "^0.1.0", + "@chainsafe/as-sha256": "^0.4.1", + "@libp2p/crypto": "^4.0.0", + "@libp2p/interface": "^1.0.0", + "@libp2p/peer-id": "^4.0.0", + "@noble/ciphers": "^0.4.0", + "@noble/curves": "^1.1.0", + "@noble/hashes": "^1.3.1", + "it-length-prefixed": "^9.0.1", + "it-length-prefixed-stream": "^1.0.0", + "it-pair": "^2.0.6", + "it-pipe": "^3.0.1", + "it-stream-types": "^2.0.1", + "protons-runtime": "^5.0.0", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^5.0.0", + "wherearewe": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@chainsafe/libp2p-yamux": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-yamux/-/libp2p-yamux-6.0.2.tgz", + "integrity": "sha512-S5OkLHqYhEVMQQ4BTgnRANEIbGTQhaC23glCgBwGdeoTRtMpIozwDiPfljFLCm0RYWdCRJw9oFztO95KUHjptA==", + "dependencies": { + "@libp2p/interface": "^1.1.3", + "@libp2p/utils": "^5.2.5", + "get-iterator": "^2.0.1", + "it-foreach": "^2.0.6", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@chainsafe/netmask": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", + "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "peer": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@helia/block-brokers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.0.3.tgz", + "integrity": "sha512-MlVlm7vWgQ/4Ga4/IVuKwhXIIHHQBr/+rfSxxPcO1Al30s+6SNSMDwwVKWY7tHNTOsnc9IC/bTURCwR/yBstrQ==", + "dependencies": { + "@helia/interface": "^4.1.0", + "@libp2p/interface": "^1.1.4", + "interface-blockstore": "^5.2.10", + "ipfs-bitswap": "^20.0.2", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0" + } + }, + "node_modules/@helia/delegated-routing-v1-http-api-client": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.0.tgz", + "integrity": "sha512-NQRG5djXKI0GcFx35JMKSQ92RscGOhER6ti66ExjreAffaO2/9vk2ZbAbfCoZnjyTnx66HWAgAyB4GDDTAeFLA==", + "dependencies": { + "@libp2p/interface": "^1.1.1", + "@libp2p/logger": "^4.0.4", + "@libp2p/peer-id": "^4.0.4", + "@multiformats/multiaddr": "^12.1.3", + "any-signal": "^4.1.1", + "browser-readablestream-to-it": "^2.0.3", + "ipns": "^9.0.0", + "it-first": "^3.0.3", + "it-map": "^3.0.4", + "it-ndjson": "^1.0.4", + "multiformats": "^13.0.0", + "p-defer": "^4.0.0", + "p-queue": "^8.0.1", + "uint8arrays": "^5.0.1" + } + }, + "node_modules/@helia/interface": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.1.0.tgz", + "integrity": "sha512-uSuUEhV9njhMRCc44EtMW9mvXsshEke4qxyIy4YNiZfgDs1vPfbn6/QHHgS7gIMPBCkg+v/mCRdBAyTjW3d2dg==", + "dependencies": { + "@libp2p/interface": "^1.1.4", + "@multiformats/dns": "^1.0.1", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0" + } + }, + "node_modules/@helia/mfs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.2.tgz", + "integrity": "sha512-vFhU2AMBGcw54FHoJPKG74lBkFGheJFJA69Tl0n531QKdaZwEgnjZB97zhx+Hxyx2S3a92gEdQiCTNRYAkOJ6g==", + "dependencies": { + "@helia/unixfs": "^3.0.2", + "@libp2p/interfaces": "^3.3.2", + "@libp2p/logger": "^4.0.7", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "ipfs-unixfs": "^11.1.3", + "ipfs-unixfs-exporter": "^13.5.0", + "ipfs-unixfs-importer": "^15.2.4", + "multiformats": "^13.1.0" + } + }, + "node_modules/@helia/routers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.2.tgz", + "integrity": "sha512-lLoDjVYZwwwav7UmEOnpNQJsL6UdQNCbAyFJxDcKk+T/DTG10B/LDItM20+FO1F35RjfW9LZ0Czz3/OokE9raw==", + "dependencies": { + "@helia/delegated-routing-v1-http-api-client": "^3.0.0", + "@helia/interface": "^4.1.0", + "@libp2p/interface": "^1.1.4", + "ipns": "^9.0.0", + "it-first": "^3.0.4", + "it-map": "^3.0.5", + "multiformats": "^13.1.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@helia/unixfs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.2.tgz", + "integrity": "sha512-zGOCFPJn8aAxNCImo67PReV2WJIdmjh5pPBDp2MTyeMoa1G5VuhfW6GnSYzQQNagbY/LYdSxDIE+a33rgjGtdw==", + "dependencies": { + "@helia/interface": "^4.1.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@multiformats/murmur3": "^2.1.8", + "hamt-sharding": "^3.0.6", + "interface-blockstore": "^5.2.10", + "ipfs-unixfs": "^11.1.3", + "ipfs-unixfs-exporter": "^13.5.0", + "ipfs-unixfs-importer": "^15.2.4", + "it-all": "^3.0.4", + "it-glob": "^2.0.6", + "it-last": "^3.0.4", + "it-pipe": "^3.0.1", + "merge-options": "^3.0.4", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "sparse-array": "^1.3.2", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@helia/utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.1.0.tgz", + "integrity": "sha512-uRUDzGpeWbPANFlq3J3x14tE89enVEBoaRXM1CKpE25vXUOij9qoYjE8GlXDNCQgkazAzrVswdjcRn6Eza+bIg==", + "dependencies": { + "@helia/interface": "^4.1.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/peer-collections": "^5.1.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.0", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@ipld/dag-cbor": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz", + "integrity": "sha512-N14oMy0q4gM6OuZkIpisKe0JBSjf1Jb39VI+7jMLiWX9124u1Z3Fdj/Tag1NA0cVxxqWDh0CqsjcVfOKtelPDA==", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-json": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.0.tgz", + "integrity": "sha512-O9YLUrl3d3WbVz7v1WkajFkyfOLEe2Fep+wor4fgVe0ywxzrivrj437NiPcVyB+2EDdFn/Q7tCHFf8YVhDf8ZA==", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.0.tgz", + "integrity": "sha512-LJU451Drqs5zjFm7jI4Hs3kHlilOqkjcSfPiQgVsZnWaYb2C7YdfhnclrVn/X+ucKejlU9BL3+gXFCZUXkMuCg==", + "dependencies": { + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + }, + "node_modules/@libp2p/autonat": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.14.tgz", + "integrity": "sha512-yd6Qlltzt7/kkec9wOtgUJzzDXXreVXyKVR1E5zms58sQmVavc3PWEJE1cFxK0GYMF2QjjzJY13/z9WLeQBzIA==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-id-factory": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "it-first": "^3.0.4", + "it-length-prefixed": "^9.0.4", + "it-map": "^3.0.5", + "it-parallel": "^3.0.6", + "it-pipe": "^3.0.1", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/bootstrap": { + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.17.tgz", + "integrity": "sha512-NO+YTigMn62YMbPAz4jK8sOC6tY2K5Q7wnRI2lmHzP9albIX+Nf4/Tyj7tu8Owi5CP+v07eKXWejmrXm+MkJPg==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14" + } + }, + "node_modules/@libp2p/circuit-relay-v2": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.17.tgz", + "integrity": "sha512-FSTqidxco90MjMdKTGhFZoGxf/EaZMuwRWyEq5oGw1aG8lBKrH2fwP1fTRfwAQVhQ8Pxd9B9/SfBU6QSHA5YKQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-record": "^7.0.11", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "any-signal": "^4.1.1", + "it-protobuf-stream": "^1.1.2", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "p-defer": "^4.0.0", + "p-retry": "^6.2.0", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/crypto": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.4.tgz", + "integrity": "sha512-sJR/516/yJFwWJGme7sbQhe2DQYfoNTwGqKOmCz3M6fdeucfSCCLkaNjphijzvUoXROTsDs5Lv4xCTh0/KanMw==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "asn1js": "^3.0.5", + "multiformats": "^13.1.0", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/dcutr": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.0.14.tgz", + "integrity": "sha512-0x5Sn21KVe1Fz0YeIudPIg2GB+XDVwr5IHblXVVKq5PNHjUvqarO4XM5GAaLyK0tZqwFkkgDNE4BjmHWZkejng==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "delay": "^6.0.0", + "it-protobuf-stream": "^1.1.2", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/identify": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-1.0.16.tgz", + "integrity": "sha512-uDXae1DGok+rFF5T8yixf3lkadr5U3O+x72BjgZaIeduWLCbPDczlKMNJuFJF3+ys45qcO8WuySuZqyK2aq8kw==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-record": "^7.0.11", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "it-protobuf-stream": "^1.1.2", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2", + "wherearewe": "^2.0.1" + } + }, + "node_modules/@libp2p/interface": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.1.5.tgz", + "integrity": "sha512-BjFgv/3VwEDNRcFKL4KW6g29IcUWUjaTJhyZVGWtodFuPjZsZHJgoQU7T/FFxDcfTdI90qpFbTREycOB+VL9NQ==", + "dependencies": { + "@multiformats/multiaddr": "^12.1.14", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/interface-internal": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.0.10.tgz", + "integrity": "sha512-12mWf+H6Ue/qUzruzuVG2rKk78r810klBYDISvYdsdj+JO4sk/iIMlsFsWGu40LWVOG7eQLYf6QDP2jx4vo9qQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-collections": "^5.1.8", + "@multiformats/multiaddr": "^12.1.14", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/interfaces": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", + "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/kad-dht": { + "version": "12.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.10.tgz", + "integrity": "sha512-GHeoyOFawAkUAr++9V/a7txUOjhQDKdXd8+Zx4dRPdFMlpHGAV6Tdjcewv49upf98ahWB7FHxwf2qs3kuF3jKg==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "@types/sinon": "^17.0.3", + "any-signal": "^4.1.1", + "hashlru": "^2.3.0", + "interface-datastore": "^8.2.11", + "it-drain": "^3.0.5", + "it-length": "^3.0.4", + "it-length-prefixed": "^9.0.4", + "it-map": "^3.0.5", + "it-merge": "^3.0.3", + "it-parallel": "^3.0.6", + "it-pipe": "^3.0.1", + "it-protobuf-stream": "^1.1.2", + "it-pushable": "^3.2.3", + "it-take": "^3.0.4", + "multiformats": "^13.1.0", + "p-defer": "^4.0.0", + "p-event": "^6.0.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0", + "protons-runtime": "^5.4.0", + "race-signal": "^1.0.2", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/keychain": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.0.10.tgz", + "integrity": "sha512-yI7Z3H4hNky7uRyJUHE/4lcc42jboFlGnveW+nrUEm+3/IrCt7T+Ci74Ighhh41V90iKJyFAfQeR+HAnwpT9GA==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "interface-datastore": "^8.2.11", + "merge-options": "^3.0.4", + "multiformats": "^13.1.0", + "sanitize-filename": "^1.6.3", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/logger": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.8.tgz", + "integrity": "sha512-H8vdQdjkPxnR9JbjyWQpyc+hjuE18ONvZet3hyOFqUUi28LkNSNMaW3VozgL+Gvb/JyJ3Hzni4Wbiqs2+EOi3g==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@multiformats/multiaddr": "^12.1.14", + "debug": "^4.3.4", + "interface-datastore": "^8.2.11", + "multiformats": "^13.1.0" + } + }, + "node_modules/@libp2p/mdns": { + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.0.17.tgz", + "integrity": "sha512-hflI9xnA1vWtwiZnl9Y4HXGp0QoDIt38kFdYqlgvcRIAJuH4Gw0dD/pFvzYUT3/BPAM1sZb9xU8sK1dwmPwWzQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "@types/multicast-dns": "^7.2.4", + "dns-packet": "^5.6.1", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/@libp2p/mplex": { + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.0.17.tgz", + "integrity": "sha512-/+AW5J5Zh/M2vEE7nDU00zaLyCtbmBstcUnps+MBKEKoZ4nQaGtAox2dq9dvCOSWrFl3zhHDrLQy1Kt2xIfxMA==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/utils": "^5.2.7", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/multistream-select": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.5.tgz", + "integrity": "sha512-fQSF3bjlaSqneD50/cj9Kbl2YjFVy45kHxtjnMGb04cnyL8CefJ89dY2aF3bWCbbEqUxzc4yCCNi48y+Pfo34w==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "it-length-prefixed": "^9.0.4", + "it-length-prefixed-stream": "^1.1.6", + "it-stream-types": "^2.0.1", + "p-defer": "^4.0.0", + "race-signal": "^1.0.2", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-collections": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.8.tgz", + "integrity": "sha512-g2KnQqEZ3lVLcVK2S0zJMo5NcqiO9jeKr2HyB2qc7EHDrsHeXIiD9Rs7EU2XPcYSEBVGJTrQpjUwya+QhTw23Q==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8" + } + }, + "node_modules/@libp2p/peer-id": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.8.tgz", + "integrity": "sha512-2aa3yN5RhaPe3iivuPEOdtnNbSp/EzPlmk1GAevKIpnztHi67ZmRPn+MzbllEwVPHZaRu+jEI1qugX1MLFv7aw==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "multiformats": "^13.1.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-id-factory": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.0.8.tgz", + "integrity": "sha512-Kk9TqhSY0dPr2RmLUSJj+V5j4vgxrB25G5bf+nNfY79s/YNh0uBIq2ZmeBezXnG4q5egH1e9vbrgHdl7PUllUA==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-record": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.11.tgz", + "integrity": "sha512-jAi/TX1NSdNPsO0TmJd5fEKQfjdkxxBPmlwofBx2dPqtud/Aac4wa6dvIFQKqGkh8ZeC8FAQbv8jE/a8jgg3MQ==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "protons-runtime": "^5.4.0", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-store": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.12.tgz", + "integrity": "sha512-lX8wdgX0NNxIcnutJ3hxfhyt+QXl0fVxYY3O1tKtEPoyilbxBA66qVVdU3nVmETkku7TcUuSOEfwevwmQC7qhw==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-record": "^7.0.11", + "@multiformats/multiaddr": "^12.1.14", + "interface-datastore": "^8.2.11", + "it-all": "^3.0.4", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/ping": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.0.13.tgz", + "integrity": "sha512-x3q0IOYKmLzwKbsXKL5fH9EAf/M4/LoGctCbffpr+dxn3cYZOJIMO2ZgJkEBW7DslNTskufgeTsh+wgG0EsWrw==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@multiformats/multiaddr": "^12.1.14", + "it-first": "^3.0.4", + "it-pipe": "^3.0.1", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/pubsub": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-9.0.12.tgz", + "integrity": "sha512-PTrdRXoluKFhnhQVMwk5iDuwrEvOTqM/fjcOE5YZ5mJCCu5NuHGmFPy5hkVeu19IC8vM9LCTCdNLuA71/GUEZg==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "it-length-prefixed": "^9.0.4", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "multiformats": "^13.1.0", + "p-queue": "^8.0.1", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/tcp": { + "version": "9.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.17.tgz", + "integrity": "sha512-joGy4Zs/2jQ1pe8fD7yr9lLqJ2ZXxZN6dZhhzknf3Opq1YRQ3yyz2eu5G1LNdN0jqyO259UO+TnLtEkruel/QQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "@types/sinon": "^17.0.3", + "stream-to-it": "^0.2.4" + } + }, + "node_modules/@libp2p/upnp-nat": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.15.tgz", + "integrity": "sha512-F9NStswMhpKNfzFf91mHxWCw+yKTZfbeadgb5wW6sFh6gQNpMWHdvUDoC8hg2qL8snHcMn5OqGNgE+KvNvSKAw==", + "dependencies": { + "@achingbrain/nat-port-mapper": "^1.0.13", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "wherearewe": "^2.0.1" + } + }, + "node_modules/@libp2p/utils": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.2.7.tgz", + "integrity": "sha512-Pxrj52DpLTGdP0IrLZJAvyZmu/mAq7QWTMrggwL6DqAM3CkzdWOwIrMysm2+RM0y60UQt05ZZSMFpKx3m8eeag==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.2", + "@libp2p/interface": "^1.1.5", + "@libp2p/logger": "^4.0.8", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "delay": "^6.0.0", + "get-iterator": "^2.0.1", + "is-loopback-addr": "^2.0.2", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "netmask": "^2.0.2", + "p-defer": "^4.0.0", + "race-event": "^1.2.0", + "race-signal": "^1.0.2", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/webrtc": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.23.tgz", + "integrity": "sha512-/HI60R9KLivtFCB2vIKDcwyppnKpB2T7BYBej5CCO6KQQM2zEA9/jd/dZ94hs7DUQZCOynJHcUjRKG/rIDyncQ==", + "dependencies": { + "@chainsafe/libp2p-noise": "^15.0.0", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "detect-browser": "^5.3.0", + "it-length-prefixed": "^9.0.4", + "it-protobuf-stream": "^1.1.2", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "multihashes": "^4.0.3", + "node-datachannel": "^0.5.5", + "p-defer": "^4.0.0", + "p-event": "^6.0.0", + "p-timeout": "^6.1.2", + "protons-runtime": "^5.4.0", + "race-signal": "^1.0.2", + "react-native-webrtc": "^118.0.1", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/websockets": { + "version": "8.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.0.17.tgz", + "integrity": "sha512-b8CebwVYSL+tvoh3yvDzwbY7KgLwdkPb22aPjc7ogB/+XHu/R4cTXSPRHAuzNXNdM4XN5NFtwjyHVBUXSqYRBQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-to-uri": "^10.0.1", + "@types/ws": "^8.5.10", + "it-ws": "^6.1.1", + "p-defer": "^4.0.0", + "wherearewe": "^2.0.1", + "ws": "^8.16.0" + } + }, + "node_modules/@libp2p/webtransport": { + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.0.22.tgz", + "integrity": "sha512-oHI9xJBmknO51P9XqQDa3mN/ed0s043eYvnpp1useealowXedA/4Eth84s9cvVC54o6mCNyPzqIgg2UMBWb7Bw==", + "dependencies": { + "@chainsafe/libp2p-noise": "^15.0.0", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" + }, + "node_modules/@multiformats/dns": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.5.tgz", + "integrity": "sha512-qP42WXdmK5D0KTMervvkE9N1l+1WbReMk9UwCmvE6iPterZgtNcNO5LQVfUrl0xqajQG9wDlom+a8YwA+sa5KQ==", + "dependencies": { + "@types/dns-packet": "^5.6.5", + "buffer": "^6.0.3", + "dns-packet": "^5.6.1", + "hashlru": "^2.3.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/mafmt": { + "version": "12.1.6", + "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", + "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/multiaddr": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.2.1.tgz", + "integrity": "sha512-UwjoArBbv64FlaetV4DDwh+PUMfzXUBltxQwdh+uTYnGFzVa8ZfJsn1vt1RJlJ6+Xtrm3RMekF/B+K338i2L5Q==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/interface": "^1.0.0", + "@multiformats/dns": "^1.0.3", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@multiformats/multiaddr-matcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.0.tgz", + "integrity": "sha512-LH6yR7h3HSNKcxuvvi2UpLuowuVkYC6H9Y3jqmKuTai8XtKnXtW6NcDZFD/ooTBY+H4yX/scoJpjOalHrk5qdQ==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@multiformats/multiaddr": "^12.0.0", + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.0.1.tgz", + "integrity": "sha512-RtOBRJucMCzINPytvt1y7tJ2jr4aSKJmv3DF7/C515RJO9+nu9sZHdsk9vn251OtN8k21rAGlIHESt/BSJWAnQ==", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/murmur3": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.8.tgz", + "integrity": "sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA==", + "dependencies": { + "multiformats": "^13.0.0", + "murmurhash3js-revisited": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", + "integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@react-native-community/cli": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz", + "integrity": "sha512-647OSi6xBb8FbwFqX9zsJxOzu685AWtrOUWHfOkbKD+5LOpGORw+GQo0F9rWZnB68rLQyfKUZWJeaD00pGv5fw==", + "peer": true, + "dependencies": { + "@react-native-community/cli-clean": "12.3.6", + "@react-native-community/cli-config": "12.3.6", + "@react-native-community/cli-debugger-ui": "12.3.6", + "@react-native-community/cli-doctor": "12.3.6", + "@react-native-community/cli-hermes": "12.3.6", + "@react-native-community/cli-plugin-metro": "12.3.6", + "@react-native-community/cli-server-api": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "@react-native-community/cli-types": "12.3.6", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz", + "integrity": "sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.6.tgz", + "integrity": "sha512-JGWSYQ9EAK6m2v0abXwFLEfsqJ1zkhzZ4CV261QZF9MoUNB6h57a274h1MLQR9mG6Tsh38wBUuNfEPUvS1vYew==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.6.tgz", + "integrity": "sha512-SjUKKsx5FmcK9G6Pb6UBFT0s9JexVStK5WInmANw75Hm7YokVvHEgtprQDz2Uvy5znX5g2ujzrkIU//T15KQzA==", + "peer": true, + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.6.tgz", + "integrity": "sha512-fvBDv2lTthfw4WOQKkdTop2PlE9GtfrlNnpjB818MhcdEnPjfQw5YaTUcnNEGsvGomdCs1MVRMgYXXwPSN6OvQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-config": "12.3.6", + "@react-native-community/cli-platform-android": "12.3.6", + "@react-native-community/cli-platform-ios": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-hermes": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz", + "integrity": "sha512-sNGwfOCl8OAIjWCkwuLpP8NZbuO0dhDI/2W7NeOGDzIBsf4/c4MptTrULWtGIH9okVPLSPX0NnRyGQ+mSwWyuQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-android": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.6.tgz", + "integrity": "sha512-DeDDAB8lHpuGIAPXeeD9Qu2+/wDTFPo99c8uSW49L0hkmZJixzvvvffbGQAYk32H0TmaI7rzvzH+qzu7z3891g==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.6.tgz", + "integrity": "sha512-3eZ0jMCkKUO58wzPWlvAPRqezVKm9EPZyaPyHbRPWU8qw7JqkvnRlWIaYDGpjCJgVW4k2hKsEursLtYKb188tg==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.6.tgz", + "integrity": "sha512-3jxSBQt4fkS+KtHCPSyB5auIT+KKIrPCv9Dk14FbvOaEh9erUWEm/5PZWmtboW1z7CYeNbFMeXm9fM2xwtVOpg==", + "peer": true + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.6.tgz", + "integrity": "sha512-80NIMzo8b2W+PL0Jd7NjiJW9mgaT8Y8wsIT/lh6mAvYH7mK0ecDJUYUTAAv79Tbo1iCGPAr3T295DlVtS8s4yQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.6.tgz", + "integrity": "sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==", + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.6.tgz", + "integrity": "sha512-xPqTgcUtZowQ8WKOkI9TLGBwH2bGggOC4d2FFaIRST3gTcjrEeGRNeR5aXCzJFIgItIft8sd7p2oKEdy90+01Q==", + "peer": true, + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "peer": true, + "dependencies": { + "@react-native/codegen": "0.73.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.4", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.73.17", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.17.tgz", + "integrity": "sha512-F3PXZkcHg+1ARIr6FRQCQiB7ZAA+MQXGmq051metRscoLvgYJwj7dgC8pvgy0kexzUkHu5BNKrZeySzUft3xuQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-server-api": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "@react-native/dev-middleware": "0.73.8", + "@react-native/metro-babel-transformer": "0.73.15", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.73.8", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", + "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0", + "ws": "^6.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", + "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.21", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "peer": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "peer": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/busboy": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.3.tgz", + "integrity": "sha512-YMBLFN/xBD8bnqywIlGyYqsNFXu6bsiY7h3Ae0kO17qEuTjsqeyYMRPSUDacIKIquws2Y6KjmxAyNx8xB3xQbw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/dns-packet": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", + "integrity": "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/multicast-dns": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", + "integrity": "sha512-ib5K4cIDR4Ro5SR3Sx/LROkMDa0BHz0OPaCBL/OSPDsAXEGZ3/KQeS6poBKYVN7BfjXDL9lWNwzyHVgt/wkyCw==", + "dependencies": { + "@types/dns-packet": "*", + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "peer": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "peer": true + }, + "node_modules/@vascosantos/moving-average": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vascosantos/moving-average/-/moving-average-1.1.0.tgz", + "integrity": "sha512-MVEJ4vWAPNbrGLjz7ITnHYg+YXZ6ijAqtH5/cHwSoCpbvuJ98aLXwFfPKAUfZpJMQR5uXB58UJajbY130IRF/w==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "peer": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "peer": true + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "peer": true, + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-signal": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.1.1.tgz", + "integrity": "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "peer": true + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "peer": true + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "peer": true + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "peer": true, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.1", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", + "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "peer": true, + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blockstore-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.0.tgz", + "integrity": "sha512-tjOJAJMPWlqahqCjn5awLJz2eZeJnrGOBA0OInBFK69/FfPZbSID2t7s5jFcBRhGaglca56BzG4t5XOV3MPxOQ==", + "dependencies": { + "@libp2p/logger": "^4.0.6", + "err-code": "^3.0.1", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.0", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-merge": "^3.0.3", + "it-pushable": "^3.2.3", + "multiformats": "^13.0.1" + } + }, + "node_modules/blockstore-fs": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-1.1.10.tgz", + "integrity": "sha512-Dg0mbdma0OY4NEk78efcAAiG5ZrMcIVrM7s+0e2p4uavnvrcBT6vDj5ITfnRfid3idKHOoCYShGEi9ENNgJg1A==", + "dependencies": { + "blockstore-core": "^4.0.0", + "fast-write-atomic": "^0.2.1", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.0", + "it-glob": "^2.0.6", + "it-map": "^3.0.5", + "it-parallel-batch": "^3.0.4", + "multiformats": "^13.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-readablestream-to-it": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.5.tgz", + "integrity": "sha512-obLCT9jnxAeZlbaRWluUiZrcSJEoi2JkM0eoiJqlIP7MFwZwZjcB6giZvD343PXfr96ilD91M/wFqFvyAZq+Gg==" + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "peer": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "peer": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "peer": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/cborg": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.1.4.tgz", + "integrity": "sha512-cCw4IuvCnwjsrgrCQoreyLZDETsfw+AtFz+OFwWbWgA1yALo4nHC3Vv+zhgcVB2bor6GFRZVxrZ7Yt/3hBFAkA==", + "bin": { + "cborg": "lib/bin.js" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "peer": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "peer": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "peer": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "peer": true + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "peer": true + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "peer": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "peer": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "peer": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "peer": true + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js-compat": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "peer": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/datastore-core": { + "version": "9.2.9", + "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-9.2.9.tgz", + "integrity": "sha512-wraWTPsbtdE7FFaVo3pwPuTB/zXsgwGGAm8BgBYwYAuzZCTS0MfXmd/HH1vR9s0/NFFjOVmBkGiWCvKxZ+QjVw==", + "dependencies": { + "@libp2p/logger": "^4.0.6", + "err-code": "^3.0.1", + "interface-datastore": "^8.0.0", + "interface-store": "^5.0.0", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-map": "^3.0.5", + "it-merge": "^3.0.3", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-sort": "^3.0.4", + "it-take": "^3.0.4" + } + }, + "node_modules/datastore-fs": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-9.1.8.tgz", + "integrity": "sha512-NM/f7/yHkgnVSiYJFPbKlEIJZ49sWz2XkvYY+Kl3bl/esQILzDt4uXUhNMRF4xfLTpQKcr+ZOfrArxVYsfnj7g==", + "dependencies": { + "datastore-core": "^9.0.0", + "fast-write-atomic": "^0.2.1", + "interface-datastore": "^8.0.0", + "interface-store": "^5.0.0", + "it-glob": "^2.0.6", + "it-map": "^3.0.5", + "it-parallel-batch": "^3.0.4" + } + }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-7.2.2.tgz", + "integrity": "sha512-AD7TrdNNPXRZIGw63dw+lnGmT4v7ggZC5NHNJgAYWm5njrwoze1q5JSAW9YuLy2tjnoLUG/r8FEB93MCh9QJPg==", + "dependencies": { + "execa": "^7.1.1" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "peer": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "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==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delay": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", + "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "peer": true + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", + "peer": true, + "dependencies": { + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.721", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", + "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "peer": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "peer": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "peer": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "peer": true, + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-iterator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", + "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==" + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fast-write-atomic": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz", + "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" + }, + "node_modules/fast-xml-parser": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", + "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "peer": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "peer": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "peer": true + }, + "node_modules/flow-parser": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", + "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/freeport-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/freeport-promise/-/freeport-promise-2.0.0.tgz", + "integrity": "sha512-dwWpT1DdQcwrhmRwnDnPM/ZFny+FtzU+k50qF2eid3KxaQDsMiBrwo1i0G3qSugkN5db6Cb0zgfc68QeTOpEFg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "peer": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", + "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "peer": true + }, + "node_modules/hamt-sharding": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.6.tgz", + "integrity": "sha512-nZeamxfymIWLpVcAN0CRrb7uVq3hCOGj9IcL6NMA6VVCVWqj+h9Jo/SmaWuS92AEDf1thmHsM5D5c70hM3j2Tg==", + "dependencies": { + "sparse-array": "^1.3.1", + "uint8arrays": "^5.0.1" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashlru": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", + "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helia": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/helia/-/helia-4.1.0.tgz", + "integrity": "sha512-ZDRW4fir8GdUKjI+M21BpzGGzbgWeD5G4LGpMEiXBYwwgIdn+ITCeLIIIOA9pPc0BJSIOJGpsMtaCV8FztgQIg==", + "dependencies": { + "@chainsafe/libp2p-noise": "^15.0.0", + "@chainsafe/libp2p-yamux": "^6.0.2", + "@helia/block-brokers": "^2.0.3", + "@helia/delegated-routing-v1-http-api-client": "^3.0.0", + "@helia/interface": "^4.1.0", + "@helia/routers": "^1.0.2", + "@helia/utils": "^0.1.0", + "@libp2p/autonat": "^1.0.13", + "@libp2p/bootstrap": "^10.0.16", + "@libp2p/circuit-relay-v2": "^1.0.16", + "@libp2p/dcutr": "^1.0.13", + "@libp2p/identify": "^1.0.15", + "@libp2p/interface": "^1.1.4", + "@libp2p/kad-dht": "^12.0.8", + "@libp2p/keychain": "^4.0.9", + "@libp2p/logger": "^4.0.7", + "@libp2p/mdns": "^10.0.16", + "@libp2p/mplex": "^10.0.16", + "@libp2p/ping": "^1.0.12", + "@libp2p/tcp": "^9.0.16", + "@libp2p/upnp-nat": "^1.0.14", + "@libp2p/webrtc": "^4.0.20", + "@libp2p/websockets": "^8.0.16", + "@libp2p/webtransport": "^4.0.20", + "@multiformats/dns": "^1.0.1", + "blockstore-core": "^4.4.0", + "datastore-core": "^9.2.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "ipns": "^9.0.0", + "libp2p": "^1.3.0", + "multiformats": "^13.1.0" + } + }, + "node_modules/hermes-estree": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", + "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", + "peer": true + }, + "node_modules/hermes-parser": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", + "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", + "peer": true, + "dependencies": { + "hermes-estree": "0.15.0" + } + }, + "node_modules/hermes-profile-transformer": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", + "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "peer": true, + "dependencies": { + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/interface-blockstore": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.2.10.tgz", + "integrity": "sha512-9K48hTvBCGsKVD3pF4ILgDcf+W2P/gq0oxLcsHGB6E6W6nDutYkzR+7k7bCs9REHrBEfKzcVDEKieiuNM9WRZg==", + "dependencies": { + "interface-store": "^5.0.0", + "multiformats": "^13.0.1" + } + }, + "node_modules/interface-datastore": { + "version": "8.2.11", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.2.11.tgz", + "integrity": "sha512-9E0iXehfp/j0UbZ2mvlYB4K9pP7uQBCppfuy8WHs1EHF6wLQrM9+zwyX+8Qt6HnH4GKZRyXX/CNXm6oD4+QYgA==", + "dependencies": { + "interface-store": "^5.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/interface-store": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.8.tgz", + "integrity": "sha512-7na81Uxkl0vqk0CBPO5PvyTkdaJBaezwUJGsMOz7riPOq0rJt+7W31iaopaMICWea/iykUsvNlPx/Tc+MxC3/w==" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "peer": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ipfs-bitswap": { + "version": "20.0.2", + "resolved": "https://registry.npmjs.org/ipfs-bitswap/-/ipfs-bitswap-20.0.2.tgz", + "integrity": "sha512-B/pPf0Dvnp/TzZ/jk7IElxCH+MwxTCe8YvyQzsQ4i8RhUh0IwyyaHPy0LSVGuJpADNjaaZQffe6DSxwmRKF7uA==", + "dependencies": { + "@libp2p/interface": "^1.0.0", + "@libp2p/logger": "^4.0.0", + "@libp2p/utils": "^5.0.0", + "@multiformats/multiaddr": "^12.1.0", + "@vascosantos/moving-average": "^1.1.0", + "any-signal": "^4.1.1", + "events": "^3.3.0", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.1.0", + "it-drain": "^3.0.5", + "it-foreach": "^2.0.2", + "it-length-prefixed": "^9.0.0", + "it-map": "^3.0.2", + "it-pipe": "^3.0.1", + "it-take": "^3.0.1", + "just-debounce-it": "^3.0.1", + "multiformats": "^13.0.0", + "progress-events": "^1.0.0", + "protons-runtime": "^5.0.0", + "timeout-abort-controller": "^3.0.0", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^5.0.1", + "varint": "^6.0.0", + "varint-decoder": "^1.0.0" + } + }, + "node_modules/ipfs-unixfs": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.3.tgz", + "integrity": "sha512-sy6Koojwm/EcM8yvDlycRYA89C8wIcLcGTMMpqnCPUtqTCdl+JxsuPNCBgAu7tmO8Nipm7Tv7f0g/erxTGKKRA==", + "dependencies": { + "err-code": "^3.0.1", + "protons-runtime": "^5.0.0", + "uint8arraylist": "^2.4.3" + } + }, + "node_modules/ipfs-unixfs-exporter": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-exporter/-/ipfs-unixfs-exporter-13.5.0.tgz", + "integrity": "sha512-s1eWXzoyhQFNEAB1p+QE3adjhW+lBdgpORmmjiCLiruHs5z7T5zsAgRVcWpM8LWYhq2flRtJHObb7Hg73J+oLQ==", + "dependencies": { + "@ipld/dag-cbor": "^9.0.0", + "@ipld/dag-json": "^10.1.7", + "@ipld/dag-pb": "^4.0.0", + "@multiformats/murmur3": "^2.0.0", + "err-code": "^3.0.1", + "hamt-sharding": "^3.0.0", + "interface-blockstore": "^5.0.0", + "ipfs-unixfs": "^11.0.0", + "it-filter": "^3.0.2", + "it-last": "^3.0.2", + "it-map": "^3.0.3", + "it-parallel": "^3.0.0", + "it-pipe": "^3.0.1", + "it-pushable": "^3.1.0", + "multiformats": "^13.0.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0" + } + }, + "node_modules/ipfs-unixfs-importer": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.2.4.tgz", + "integrity": "sha512-3b7d/pLPwGvAEXvpJ0WyYlbn2pb2j7qY6FayuMSzbZNdFxdJ82l6VkJ9vK1d/G/AHx+0ZfB06eSdGKjX0GVCAg==", + "dependencies": { + "@ipld/dag-pb": "^4.0.0", + "@multiformats/murmur3": "^2.0.0", + "err-code": "^3.0.1", + "hamt-sharding": "^3.0.0", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.1", + "ipfs-unixfs": "^11.0.0", + "it-all": "^3.0.2", + "it-batch": "^3.0.2", + "it-first": "^3.0.2", + "it-parallel-batch": "^3.0.1", + "multiformats": "^13.0.0", + "progress-events": "^1.0.0", + "rabin-wasm": "^0.1.4", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/ipns": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ipns/-/ipns-9.0.0.tgz", + "integrity": "sha512-z/C/SfikvR23y0rhUOxL+zF5nG8bKv9xcdEcL8iO1IEnSBa0Y2pzke22V9YXw1r4uZ7CWukRQTC68+XHRu8y5Q==", + "dependencies": { + "@libp2p/crypto": "^4.0.0", + "@libp2p/interface": "^1.1.0", + "@libp2p/logger": "^4.0.3", + "@libp2p/peer-id": "^4.0.3", + "cborg": "^4.0.1", + "err-code": "^3.0.1", + "interface-datastore": "^8.1.0", + "multiformats": "^13.0.0", + "protons-runtime": "^5.2.1", + "timestamp-nano": "^1.0.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.1" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "peer": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-loopback-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", + "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==" + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "peer": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/it-all": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.4.tgz", + "integrity": "sha512-UMiy0i9DqCHBdWvMbzdYvVGa5/w4t1cc4nchpbnjdLhklglv8mQeEYnii0gvKESJuL1zV32Cqdb33R6/GPfxpQ==" + }, + "node_modules/it-batch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.4.tgz", + "integrity": "sha512-WRu2mqOYIs+T9k7+yxSK9VJdk0UE4R0jKQsWQcti5c6vhb1FhjC2+yCB5XBrctQ9edNfCMU/wVzdDj8qSwimbA==" + }, + "node_modules/it-byte-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-1.0.8.tgz", + "integrity": "sha512-H32LbN6kdX8HXqH68z5uivfkVYJEi5tIPRwIQNR5Qsx3uoDRhYdBRHzf3NOVAf6vqulFUSQLuU+Y0rs/QeWn3A==", + "dependencies": { + "it-stream-types": "^2.0.1", + "p-defer": "^4.0.0", + "race-signal": "^1.0.1", + "uint8arraylist": "^2.4.1" + } + }, + "node_modules/it-drain": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.5.tgz", + "integrity": "sha512-qYFe4SWdvs9oJGUY5bSjvmiLUMLzFEODNOQUdYdCIkuIgQF+AUB2INhM4yQ09buJ2rhHKDFxvTD/+yUq6qg0XA==" + }, + "node_modules/it-filter": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.0.4.tgz", + "integrity": "sha512-e0sz+st4sudK/zH6GZ/gRTRP8A/ADuJFCYDmRgMbZvR79y5+v4ZXav850bBZk5wL9zXaYZFxS1v/6Qi+Vjwh5g==", + "dependencies": { + "it-peekable": "^3.0.0" + } + }, + "node_modules/it-first": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.4.tgz", + "integrity": "sha512-FtQl84iTNxN5EItP/JgL28V2rzNMkCzTUlNoj41eVdfix2z1DBuLnBqZ0hzYhGGa1rMpbQf0M7CQSA2adlrLJg==" + }, + "node_modules/it-foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.0.6.tgz", + "integrity": "sha512-OVosBHJsdXpAyeFlCbe3IGZia+65UykyAznakNsKXK+b99dbhuu/mOnXxTadDEo1GWhKx+WA8RNanKkMf07zQw==", + "dependencies": { + "it-peekable": "^3.0.0" + } + }, + "node_modules/it-glob": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.6.tgz", + "integrity": "sha512-4C6ccz4nhqrq7yZMzBr3MsKhyL+rlnLXIPceyGG6ogl3Lx3eeWMv1RtlySJwFi6q+jVcPyTpeYt/xftwI2JEQQ==", + "dependencies": { + "minimatch": "^9.0.0" + } + }, + "node_modules/it-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/it-glob/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/it-last": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.4.tgz", + "integrity": "sha512-Ns+KTsQWhs0KCvfv5X3Ck3lpoYxHcp4zUp4d+AOdmC8cXXqDuoZqAjfWhgCbxJubXyIYWdfE2nRcfWqgvZHP8Q==" + }, + "node_modules/it-length": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-length/-/it-length-3.0.4.tgz", + "integrity": "sha512-RS3thYkvqtWksrV7SaAnTv+pgY7ozpS17HlRvWvcnoRjVyNJMuffdCkIKpKNPTq5uZw9zVnkVKLO077pJn5Yhg==" + }, + "node_modules/it-length-prefixed": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.0.4.tgz", + "integrity": "sha512-lz28fykbG0jq7s5XtvlzGxO5BeSOw6ikymkRllxjL21V5VKLcvB4pHr9wPvEnsAJ2et1xpOk3BRTMq9XrhgKsg==", + "dependencies": { + "err-code": "^3.0.1", + "it-reader": "^6.0.1", + "it-stream-types": "^2.0.1", + "uint8-varint": "^2.0.1", + "uint8arraylist": "^2.0.0", + "uint8arrays": "^5.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-length-prefixed-stream": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-1.1.6.tgz", + "integrity": "sha512-MEby4r8n3XIYXjaWT3DweCuhBPQmFVT8RdI1BNjYQ5gelbFD3NLdjYpTI3TVmSEs/aJfgpfVFZzy6iP7OCxIgw==", + "dependencies": { + "it-byte-stream": "^1.0.0", + "it-stream-types": "^2.0.1", + "uint8-varint": "^2.0.1", + "uint8arraylist": "^2.4.1" + } + }, + "node_modules/it-map": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.0.5.tgz", + "integrity": "sha512-hB0TDXo/h4KSJJDSRLgAPmDroiXP6Fx1ck4Bzl3US9hHfZweTKsuiP0y4gXuTMcJlS6vj0bb+f70rhkD47ZA3w==", + "dependencies": { + "it-peekable": "^3.0.0" + } + }, + "node_modules/it-merge": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.3.tgz", + "integrity": "sha512-FYVU15KC5pb/GQX1Ims+lee8d4pdqGVCpWr0lkNj8o4xuNo7jY71k6GuEiWdP+T7W1bJqewSxX5yoTy5yZpRVA==", + "dependencies": { + "it-pushable": "^3.2.0" + } + }, + "node_modules/it-ndjson": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.5.tgz", + "integrity": "sha512-2UEROCo458dDu9dABKb9fvD34p2YL6SqV5EOXN8SysX2Fpx0MSN69EiBmLLDDYSpQlrW0I5j3Tm8DtEIL5NsIw==" + }, + "node_modules/it-pair": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/it-pair/-/it-pair-2.0.6.tgz", + "integrity": "sha512-5M0t5RAcYEQYNG5BV7d7cqbdwbCAp5yLdzvkxsZmkuZsLbTdZzah6MQySYfaAQjNDCq6PUnDt0hqBZ4NwMfW6g==", + "dependencies": { + "it-stream-types": "^2.0.1", + "p-defer": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-parallel": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.6.tgz", + "integrity": "sha512-i7UM7I9LTkDJw3YIqXHFAPZX6CWYzGc+X3irdNrVExI4vPazrJdI7t5OqrSVN8CONXLAunCiqaSV/zZRbQR56A==", + "dependencies": { + "p-defer": "^4.0.0" + } + }, + "node_modules/it-parallel-batch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.4.tgz", + "integrity": "sha512-O1omh8ss8+UtXiMjE+8kM5C20DT0Ma4VtKVfrSHOJU0UHZ+iWBXarabzPYEp+WiuQmrv+klDPPlTZ9KaLN9xOA==", + "dependencies": { + "it-batch": "^3.0.0" + } + }, + "node_modules/it-peekable": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.3.tgz", + "integrity": "sha512-Wx21JX/rMzTEl9flx3DGHuPV1KQFGOl8uoKfQtmZHgPQtGb89eQ6RyVd82h3HuP9Ghpt0WgBDlmmdWeHXqyx7w==" + }, + "node_modules/it-pipe": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", + "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", + "dependencies": { + "it-merge": "^3.0.0", + "it-pushable": "^3.1.2", + "it-stream-types": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-protobuf-stream": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-1.1.2.tgz", + "integrity": "sha512-epZBuG+7cPaTxCR/Lf3ApshBdA9qfflGPQLfLLrp9VQ0w67Z2xo4H+SLLetav57/29oPtAXwVaoyemg99JOWzA==", + "dependencies": { + "it-length-prefixed-stream": "^1.0.0", + "it-stream-types": "^2.0.1", + "protons-runtime": "^5.0.0", + "uint8arraylist": "^2.4.1" + } + }, + "node_modules/it-pushable": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", + "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", + "dependencies": { + "p-defer": "^4.0.0" + } + }, + "node_modules/it-reader": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-6.0.4.tgz", + "integrity": "sha512-XCWifEcNFFjjBHtor4Sfaj8rcpt+FkY0L6WdhD578SCDhV4VUm7fCkF3dv5a+fTcfQqvN9BsxBTvWbYO6iCjTg==", + "dependencies": { + "it-stream-types": "^2.0.1", + "uint8arraylist": "^2.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-sort": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.4.tgz", + "integrity": "sha512-tvnC93JZZWjX4UxALy0asow0dzXabkoaRbrPJKClTKhNCqw4gzHr+H5axf1gohcthedRRkqd/ae+wl7WqoxFhw==", + "dependencies": { + "it-all": "^3.0.0" + } + }, + "node_modules/it-stream-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.1.tgz", + "integrity": "sha512-6DmOs5r7ERDbvS4q8yLKENcj6Yecr7QQTqWApbZdfAUTEC947d+PEha7PCqhm//9oxaLYL7TWRekwhoXl2s6fg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-take": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.4.tgz", + "integrity": "sha512-RG8HDjAZlvkzz5Nav4xq6gK5zNT+Ff1UTIf+CrSJW8nIl6N1FpBH5e7clUshiCn+MmmMoSdIEpw4UaTolszxhA==" + }, + "node_modules/it-ws": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.1.tgz", + "integrity": "sha512-oyk4eCeZto2lzWDnJOa3j1S2M+VOGKUh8isEf94ySoaL6IFlyie0T4P9E0ZUaIvX8LyJxYFHFKCt8Zk7Sm/XPQ==", + "dependencies": { + "@types/ws": "^8.2.2", + "event-iterator": "^2.0.0", + "it-stream-types": "^2.0.1", + "uint8arrays": "^5.0.0", + "ws": "^8.4.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.12.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", + "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "peer": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-android": { + "version": "250231.0.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", + "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", + "peer": true + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "peer": true + }, + "node_modules/jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "peer": true, + "dependencies": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-debounce-it": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/just-debounce-it/-/just-debounce-it-3.2.0.tgz", + "integrity": "sha512-WXzwLL0745uNuedrCsCs3rpmfD6DBaf7uuVwaq98/8dafURfgQaBsSpjiPp5+CW6Vjltwy9cOGI6qE71b3T8iQ==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/libp2p": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.3.1.tgz", + "integrity": "sha512-mcVAQRECnn7TBXVDGihgAKRSL+v7otA9jgpeBnfPJvlbVbtFE9tKAUeBBgHVLifM2dXPjvl5fxzFMgYyP+fGbA==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/logger": "^4.0.8", + "@libp2p/multistream-select": "^5.1.5", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-id-factory": "^4.0.8", + "@libp2p/peer-store": "^10.0.12", + "@libp2p/utils": "^5.2.7", + "@multiformats/dns": "^1.0.1", + "@multiformats/multiaddr": "^12.2.0", + "any-signal": "^4.1.1", + "datastore-core": "^9.2.8", + "interface-datastore": "^8.2.11", + "it-merge": "^3.0.3", + "it-parallel": "^3.0.6", + "merge-options": "^3.0.4", + "multiformats": "^13.1.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "peer": true + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "peer": true, + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } + }, + "node_modules/logkitty/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/logkitty/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "peer": true + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "peer": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "peer": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "peer": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "peer": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/metro": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.8.tgz", + "integrity": "sha512-in7S0W11mg+RNmcXw+2d9S3zBGmCARDxIwoXJAmLUQOQoYsRP3cpGzyJtc7WOw8+FXfpgXvceD0u+PZIHXEL7g==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.20.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.80.8", + "metro-cache": "0.80.8", + "metro-cache-key": "0.80.8", + "metro-config": "0.80.8", + "metro-core": "0.80.8", + "metro-file-map": "0.80.8", + "metro-resolver": "0.80.8", + "metro-runtime": "0.80.8", + "metro-source-map": "0.80.8", + "metro-symbolicate": "0.80.8", + "metro-transform-plugins": "0.80.8", + "metro-transform-worker": "0.80.8", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "rimraf": "^3.0.2", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.8.tgz", + "integrity": "sha512-TTzNwRZb2xxyv4J/+yqgtDAP2qVqH3sahsnFu6Xv4SkLqzrivtlnyUbaeTdJ9JjtADJUEjCbgbFgUVafrXdR9Q==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "hermes-parser": "0.20.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", + "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "peer": true + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", + "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "peer": true, + "dependencies": { + "hermes-estree": "0.20.1" + } + }, + "node_modules/metro-cache": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.8.tgz", + "integrity": "sha512-5svz+89wSyLo7BxdiPDlwDTgcB9kwhNMfNhiBZPNQQs1vLFXxOkILwQiV5F2EwYT9DEr6OPZ0hnJkZfRQ8lDYQ==", + "peer": true, + "dependencies": { + "metro-core": "0.80.8", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-cache-key": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.8.tgz", + "integrity": "sha512-qWKzxrLsRQK5m3oH8ePecqCc+7PEhR03cJE6Z6AxAj0idi99dHOSitTmY0dclXVB9vP2tQIAE8uTd8xkYGk8fA==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-config": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.8.tgz", + "integrity": "sha512-VGQJpfJawtwRzGzGXVUoohpIkB0iPom4DmSbAppKfumdhtLA8uVeEPp2GM61kL9hRvdbMhdWA7T+hZFDlo4mJA==", + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "jest-validate": "^29.6.3", + "metro": "0.80.8", + "metro-cache": "0.80.8", + "metro-core": "0.80.8", + "metro-runtime": "0.80.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-core": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.8.tgz", + "integrity": "sha512-g6lud55TXeISRTleW6SHuPFZHtYrpwNqbyFIVd9j9Ofrb5IReiHp9Zl8xkAfZQp8v6ZVgyXD7c130QTsCz+vBw==", + "peer": true, + "dependencies": { + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.80.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-file-map": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.8.tgz", + "integrity": "sha512-eQXMFM9ogTfDs2POq7DT2dnG7rayZcoEgRbHPXvhUWkVwiKkro2ngcBE++ck/7A36Cj5Ljo79SOkYwHaWUDYDw==", + "peer": true, + "dependencies": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/metro-file-map/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/metro-minify-terser": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.8.tgz", + "integrity": "sha512-y8sUFjVvdeUIINDuW1sejnIjkZfEF+7SmQo0EIpYbWmwh+kq/WMj74yVaBWuqNjirmUp1YNfi3alT67wlbBWBQ==", + "peer": true, + "dependencies": { + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-resolver": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.8.tgz", + "integrity": "sha512-JdtoJkP27GGoZ2HJlEsxs+zO7jnDUCRrmwXJozTlIuzLHMRrxgIRRby9fTCbMhaxq+iA9c+wzm3iFb4NhPmLbQ==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-runtime": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.8.tgz", + "integrity": "sha512-2oScjfv6Yb79PelU1+p8SVrCMW9ZjgEiipxq7jMRn8mbbtWzyv3g8Mkwr+KwOoDFI/61hYPUbY8cUnu278+x1g==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.8.tgz", + "integrity": "sha512-+OVISBkPNxjD4eEKhblRpBf463nTMk3KMEeYS8Z4xM/z3qujGJGSsWUGRtH27+c6zElaSGtZFiDMshEb8mMKQg==", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.80.8", + "nullthrows": "^1.1.1", + "ob1": "0.80.8", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.8.tgz", + "integrity": "sha512-nwhYySk79jQhwjL9QmOUo4wS+/0Au9joEryDWw7uj4kz2yvw1uBjwmlql3BprQCBzRdB3fcqOP8kO8Es+vE31g==", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "metro-source-map": "0.80.8", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.8.tgz", + "integrity": "sha512-sSu8VPL9Od7w98MftCOkQ1UDeySWbsIAS5I54rW22BVpPnI3fQ42srvqMLaJUQPjLehUanq8St6OMBCBgH/UWw==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.8.tgz", + "integrity": "sha512-+4FG3TQk3BTbNqGkFb2uCaxYTfsbuFOCKMMURbwu0ehCP8ZJuTUramkaNZoATS49NSAkRgUltgmBa4YaKZ5mqw==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "metro": "0.80.8", + "metro-babel-transformer": "0.80.8", + "metro-cache": "0.80.8", + "metro-cache-key": "0.80.8", + "metro-minify-terser": "0.80.8", + "metro-source-map": "0.80.8", + "metro-transform-plugins": "0.80.8", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "peer": true + }, + "node_modules/metro/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", + "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "peer": true + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", + "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "peer": true, + "dependencies": { + "hermes-estree": "0.20.1" + } + }, + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "peer": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mortice": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/mortice/-/mortice-3.0.4.tgz", + "integrity": "sha512-MUHRCAztSl4v/dAmK8vbYi5u1n9NZtQu4H3FsqS7qgMFQIAFw9lTpHiErd9kJpapqmvEdD1L3dUmiikifAvLsQ==", + "dependencies": { + "observable-webworkers": "^2.0.1", + "p-queue": "^8.0.1", + "p-timeout": "^6.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multibase": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.6.tgz", + "integrity": "sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "@multiformats/base-x": "^4.0.1" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multiformats": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.1.0.tgz", + "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==" + }, + "node_modules/multihashes": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.3.tgz", + "integrity": "sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==", + "dependencies": { + "multibase": "^4.0.1", + "uint8arrays": "^3.0.0", + "varint": "^5.0.2" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multihashes/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, + "node_modules/multihashes/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/multihashes/node_modules/varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "node_modules/murmurhash3js-revisited": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", + "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "peer": true + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/node-abi": { + "version": "3.56.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz", + "integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "peer": true + }, + "node_modules/node-datachannel": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.5.5.tgz", + "integrity": "sha512-B0MS/iK0qjCiNY5Go4055zuRCOuiFkOrhNOZuj2PAnaJN7YlnKxKidOLhLXf/lAcsJQXgj8BR31zsqJKKA16jA==", + "hasInstallScript": true, + "dependencies": { + "node-domexception": "^2.0.1", + "prebuild-install": "^7.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "peer": true, + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-2.0.1.tgz", + "integrity": "sha512-M85rnSC7WQ7wnfQTARPT4LrK7nwCHLdDFOCcItZMhTQjyCebJH8GciKqYJNgaOFZs9nFmTmd/VMyi3OW5jA47w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "peer": true + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "peer": true, + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/nodemon": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "peer": true + }, + "node_modules/ob1": { + "version": "0.80.8", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.8.tgz", + "integrity": "sha512-QHJQk/lXMmAW8I7AIM3in1MSlwe1umR72Chhi8B7Xnq6mzjhBKkA6Fy/zAhQnGkA4S912EPCEvTij5yh+EQTAA==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/observable-webworkers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/observable-webworkers/-/observable-webworkers-2.0.1.tgz", + "integrity": "sha512-JI1vB0u3pZjoQKOK1ROWzp0ygxSi7Yb0iR+7UNsw4/Zn4cQ0P3R7XL38zac/Dy2tEA7Lg88/wIJTjF8vYXZ0uw==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "peer": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "peer": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/ora/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-defer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.0.tgz", + "integrity": "sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-queue": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz", + "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", + "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "peer": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "peer": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "peer": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "peer": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "peer": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/pretty-format/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/pretty-format/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress-events": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.0.tgz", + "integrity": "sha512-zIB6QDrSbPfRg+33FZalluFIowkbV5Xh1xSuetjG+rlC5he6u2dc6VQJ0TbMdlN3R1RHdpOqxEFMKTnQ+itUwA==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "peer": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "peer": true + }, + "node_modules/protons-runtime": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.4.0.tgz", + "integrity": "sha512-XfA++W/WlQOSyjUyuF5lgYBfXZUEMP01Oh1C2dSwZAlF2e/ZrMRPfWonXj6BGM+o8Xciv7w0tsRMKYwYEuQvaw==", + "dependencies": { + "uint8-varint": "^2.0.2", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^5.0.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "peer": true, + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/rabin-wasm": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", + "integrity": "sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==", + "dependencies": { + "@assemblyscript/loader": "^0.9.4", + "bl": "^5.0.0", + "debug": "^4.3.1", + "minimist": "^1.2.5", + "node-fetch": "^2.6.1", + "readable-stream": "^3.6.0" + }, + "bin": { + "rabin-wasm": "cli/bin.js" + } + }, + "node_modules/race-event": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.2.0.tgz", + "integrity": "sha512-7EvAjTu9uuKa03Jky8yfSy6/SnnMTh6nouNmdeWv9b0dT8eDZC5ylx30cOR9YO9otQorVjjkIuSHQ5Ml/bKwMw==" + }, + "node_modules/race-signal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.0.2.tgz", + "integrity": "sha512-o3xNv0iTcIDQCXFlF6fPAMEBRjFxssgGoRqLbg06m+AdzEXXLUmoNOoUHTVz2NoBI8hHwKFKoC6IqyNtWr2bww==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", + "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "peer": true, + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "peer": true + }, + "node_modules/react-native": { + "version": "0.73.6", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.6.tgz", + "integrity": "sha512-oqmZe8D2/VolIzSPZw+oUd6j/bEmeRHwsLn1xLA5wllEYsZ5zNuMsDus235ONOnCRwexqof/J3aztyQswSmiaA==", + "peer": true, + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native-community/cli": "12.3.6", + "@react-native-community/cli-platform-android": "12.3.6", + "@react-native-community/cli-platform-ios": "12.3.6", + "@react-native/assets-registry": "0.73.1", + "@react-native/codegen": "0.73.3", + "@react-native/community-cli-plugin": "0.73.17", + "@react-native/gradle-plugin": "0.73.4", + "@react-native/js-polyfills": "0.73.1", + "@react-native/normalize-colors": "0.73.2", + "@react-native/virtualized-lists": "0.73.4", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "deprecated-react-native-prop-types": "^5.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "jsc-android": "^250231.0.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.80.3", + "metro-source-map": "^0.80.3", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "pretty-format": "^26.5.2", + "promise": "^8.3.0", + "react-devtools-core": "^4.27.7", + "react-refresh": "^0.14.0", + "react-shallow-renderer": "^16.15.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.24.0-canary-efb381bbf-20230505", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.2", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "18.2.0" + } + }, + "node_modules/react-native-webrtc": { + "version": "118.0.3", + "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.3.tgz", + "integrity": "sha512-qw+aa4rxGJTvltmYwwHonx4Qcgk/tcoojONu/6y5nsXGctkUqo886EIBb29Jv4ssHnudDzvkxyG/xVKK2vJc7Q==", + "dependencies": { + "base64-js": "1.5.1", + "debug": "4.3.4", + "event-target-shim": "6.0.2" + }, + "peerDependencies": { + "react-native": ">=0.60.0" + } + }, + "node_modules/react-native-webrtc/node_modules/event-target-shim": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", + "integrity": "sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==", + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/react-native/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "peer": true, + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "peer": true + }, + "node_modules/recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "peer": true, + "dependencies": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "peer": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "peer": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "peer": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "peer": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "peer": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "peer": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "peer": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retimer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", + "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==" + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/scheduler": { + "version": "0.24.0-canary-efb381bbf-20230505", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", + "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "peer": true + }, + "node_modules/set-function-length": { + "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==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "peer": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "peer": true + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sparse-array": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", + "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "peer": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "peer": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "peer": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "peer": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-to-it": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-0.2.4.tgz", + "integrity": "sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==", + "dependencies": { + "get-iterator": "^1.0.2" + } + }, + "node_modules/stream-to-it/node_modules/get-iterator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-1.0.2.tgz", + "integrity": "sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "peer": true + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "peer": true + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/tar-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "peer": true, + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/terser": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.0.tgz", + "integrity": "sha512-Y/SblUl5kEyEFzhMAQdsxVHh+utAxd4IuRNJzKywY/4uzSogh3G219jqbDDxYu4MXO9CzY3tSEqmZvW6AoEDJw==", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "peer": true + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "peer": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "peer": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/timeout-abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-3.0.0.tgz", + "integrity": "sha512-O3e+2B8BKrQxU2YRyEjC/2yFdb33slI22WRdUaDx6rvysfi9anloNZyR2q0l6LnePo5qH7gSM7uZtvvwZbc2yA==", + "dependencies": { + "retimer": "^3.0.0" + } + }, + "node_modules/timestamp-nano": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", + "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "peer": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "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==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8-varint": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", + "integrity": "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==", + "dependencies": { + "uint8arraylist": "^2.0.0", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/uint8arraylist": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.8.tgz", + "integrity": "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==", + "dependencies": { + "uint8arrays": "^5.0.1" + } + }, + "node_modules/uint8arrays": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.0.3.tgz", + "integrity": "sha512-6LBuKji28kHjgPJMkQ6GDaBb1lRwIhyOYq6pDGwYMoDPfImE9SkuYENVmR0yu9yGgs2clHUSY9fKDukR+AXfqQ==", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "peer": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" + }, + "node_modules/varint-decoder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/varint-decoder/-/varint-decoder-1.0.0.tgz", + "integrity": "sha512-JkOvdztASWGUAsXshCFHrB9f6AgR2Q8W08CEyJ+43b1qtFocmI8Sp1R/M0E/hDOY2FzVIqk63tOYLgDYWuJ7IQ==", + "dependencies": { + "varint": "^5.0.0" + }, + "engines": { + "node": ">=4.0.0", + "npm": ">=3.0.0" + } + }, + "node_modules/varint-decoder/node_modules/varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "peer": true + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "peer": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "peer": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wherearewe": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wherearewe/-/wherearewe-2.0.1.tgz", + "integrity": "sha512-XUguZbDxCA2wBn2LoFtcEhXL6AXo+hVjGonwhSTTTU9SzbWG8Xu3onNIpzf9j/mYUcJQ0f+m37SzG77G851uFw==", + "dependencies": { + "is-electron": "^2.2.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "peer": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4f51784 --- /dev/null +++ b/package.json @@ -0,0 +1,58 @@ +{ + "name": "ipfs-node", + "version": "0.0.1", + "description": "IPFS decentralized file storage and transfers", + "main": "index.js", + "type": "module", + "scripts": { + "build": "tsc", + "start": "cross-env NODE_OPTIONS=\"--loader=ts-node/esm\" node src/index.ts", + "dev": "nodemon", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ADAMANT Foundation ", + "license": "GPLv3", + "devDependencies": { + "@types/busboy": "^1.5.3", + "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", + "@types/node": "^20.11.30", + "cross-env": "^7.0.3", + "nodemon": "^3.1.0", + "prettier": "^3.2.5", + "ts-node": "^10.9.2", + "typescript": "^5.4.3" + }, + "dependencies": { + "@chainsafe/libp2p-gossipsub": "^13.0.0", + "@chainsafe/libp2p-noise": "^15.0.0", + "@chainsafe/libp2p-yamux": "^6.0.2", + "@helia/mfs": "^3.0.2", + "@helia/unixfs": "^3.0.2", + "@libp2p/autonat": "^1.0.14", + "@libp2p/bootstrap": "^10.0.17", + "@libp2p/circuit-relay-v2": "^1.0.17", + "@libp2p/identify": "^1.0.16", + "@libp2p/kad-dht": "^12.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-id-factory": "^4.0.8", + "@libp2p/tcp": "^9.0.17", + "@libp2p/upnp-nat": "^1.0.15", + "@libp2p/webrtc": "^4.0.23", + "blockstore-core": "^4.4.0", + "blockstore-fs": "^1.1.10", + "busboy": "^1.6.0", + "datastore-core": "^9.2.9", + "datastore-fs": "^9.1.8", + "express": "^4.19.2", + "helia": "^4.1.0", + "it-last": "^3.0.4", + "json5": "^2.2.3", + "libp2p": "^1.3.1", + "multer": "^1.4.5-lts.1", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..1de0642 --- /dev/null +++ b/src/config.js @@ -0,0 +1,17 @@ +import * as fs from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import JSON5 from "json5"; + +const currDir = dirname(fileURLToPath(import.meta.url)); +const rootDir = join(currDir, ".."); + +const configName = process.argv.slice(2)[0] || "default"; +const configFileName = `config.${configName}.json5`; +console.log("Using config file:", configFileName); + +const configPath = rootDir + `/${configFileName}`; + +const config = JSON5.parse(fs.readFileSync(configPath, "utf8")); + +export default config; diff --git a/src/helia.ts b/src/helia.ts new file mode 100644 index 0000000..ac0372f --- /dev/null +++ b/src/helia.ts @@ -0,0 +1,32 @@ +import { identify } from '@libp2p/identify'; +import { createHelia } from "helia"; +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { noise } from "@chainsafe/libp2p-noise"; +import { yamux } from "@chainsafe/libp2p-yamux"; +import { MemoryDatastore } from "datastore-core"; +import { MemoryBlockstore } from "blockstore-core"; +import { unixfs } from "@helia/unixfs"; +import last from "it-last"; + +const blockstore = new MemoryBlockstore(); +const datastore = new MemoryDatastore(); + +const libp2p = await createLibp2p({ + addresses: { + listen: ["/ip4/0.0.0.0/tcp/0"], + }, + transports: [tcp()], + connectionEncryption: [noise()], + streamMuxers: [yamux()], + datastore, + services: { + identify: identify(), + } +}); + +const helia = await createHelia({ + datastore, + blockstore, + libp2p, +}); diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..34f1c34 --- /dev/null +++ b/src/index.js @@ -0,0 +1,387 @@ +import { unixfs } from "@helia/unixfs"; +import { bootstrap } from "@libp2p/bootstrap"; +import { peerIdFromString } from "@libp2p/peer-id"; +import { createHelia } from "helia"; +import { CID } from "multiformats/cid"; +import path from "node:path"; +import express from "express"; +import { fileURLToPath } from "url"; +import multer from "multer"; +import { multiaddr } from "@multiformats/multiaddr"; + +// import { libp2p } from "./libp2p.js"; +import { blockstore, datastore } from "./store.js"; +import config from "./config.js"; +console.log("confi", config); + +const storage = multer.memoryStorage(); +const upload = multer({ storage }); + +const helia = await createHelia({ + datastore, + blockstore, + libp2p: { + peerDiscovery: [ + bootstrap({ + list: [ + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + ], + }), + ], + addresses: { + listen: ["/ip4/0.0.0.0/tcp/4001", "/ip6/::/tcp/4002", "/webrtc"], + }, + connectionManager: { + /** + * The total number of connections allowed to be open at one time + */ + // maxConnections: 10, + + /** + * If the number of open connections goes below this number, the node + * will try to connect to randomly selected peers from the peer store + */ + // minConnections: 5, + + /** + * How many connections can be open but not yet upgraded + */ + // maxIncomingPendingConnections: 10, + + /** + * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections + */ + allow: [ + "/ip4/5.161.75.77/tcp/4001/p2p/12D3KooWE849ZdcQhLP2yE4fbmU89APmjYXJPeuNChNUBGgeJJYe", // tweetbot + "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ", // ipf1 + ], + }, + }, +}); +// console.log("Created Helia instance"); +// await helia.libp2p.services.dht.setMode("server"); +// console.log("Switched DHT to server mode"); + +helia.libp2p.getMultiaddrs().forEach((addr) => { + console.log(`Listening on ${addr.toString()}`); +}); + +helia.libp2p.addEventListener("peer:discovery", (evt) => { + const peer = evt.detail; + if (logNewPeers) { + console.log("Discovered peer:", peer.id); + } +}); + +let logNewPeers = false; + +helia.libp2p.addEventListener("peer:connect", (evt) => { + const peerId = evt.detail; + + if (logNewPeers) { + console.log("Peer connected:", peerId); + } +}); + +helia.libp2p.addEventListener("peer:disconnect", (evt) => { + const peerId = evt.detail; + + if (logNewPeers) { + console.log("Peer disconnected:", peerId); + } +}); + +helia.libp2p.addEventListener("start", (event) => { + console.info("Libp2p node started"); +}); + +helia.libp2p.addEventListener("stop", (event) => { + console.info("Libp2p node stopped"); +}); + +const ifs = unixfs(helia); + +console.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`); + +// const cid = await ifs.addFile({ +// path: "/hello.txt", +// content: readFileSync("./files/hello.txt"), +// }); +// +// console.log("CID", cid); + +const PORT = 4000; +const app = express(); + +app.get("/", (req, res) => { + res.send("Hello World!"); +}); + +app.get("/libp2p/services/ping", async (req, res) => { + try { + const peerId = peerIdFromString(req.query.peerId); + + const pong = await helia.libp2p.services.ping.ping(peerId); + + res.send({ + pong, + }); + } catch (err) { + res.send({ + error: err.message, + }); + } +}); + +app.get("/libp2p/peerStore", async (req, res) => { + try { + const peers = await helia.libp2p.peerStore.all(); + + res.send({ + length: peers.length, + }); + } catch (err) { + res.send({ + error: err.message, + }); + } +}); + +app.get("/libp2p/dial", async (req, res) => { + let peerId; + let multiAddr; + try { + if (req.query.peerId) { + peerId = peerIdFromString(req.query.peerId); + } + + if (req.query.multiAddr) { + multiAddr = multiaddr(req.query.multiAddr); + } + } catch (err) { + console.log("Invalid peer ID:", err.message); + res.send({ + success: false, + error: "Invalid peer ID", + }); + return; + } + + if (multiAddr) { + console.log(`Peering by multiaddress: ${multiAddr}`); + } else if (peerId) { + console.log(`Peering by PeerID: ${peerId}`); + } + + try { + const connection = await helia.libp2p.dial(multiAddr || peerId); + res.send({ success: true, connection }); + } catch (err) { + console.log("Cannot dial peer", err.message); + + res.send({ + success: false, + error: err.message, + }); + console.log(err); + } +}); + +app.get("/libp2p/new-peers/log", async (req, res) => { + logNewPeers = !logNewPeers; + + res.send({ + logNewPeers, + }); +}); + +app.get("/libp2p/connections", async (req, res) => { + const connections = helia.libp2p.getConnections(); + + res.send({ + length: connections.length, + connections, + }); +}); + +app.get("/libp2p/status", async (req, res) => { + res.send({ + status: helia.libp2p.status, + }); +}); + +app.get("/file/:cid", async (req, res) => { + const cid = CID.parse(req.params.cid); + const file = ifs.cat(cid); + + const chunks = []; + for await (const chunk of file) { + chunks.push(chunk); + } + + res.set("Content-Type", "image/jpeg"); + res.send(Buffer.concat(chunks)); + + // res.send({ + // cid: cid?.toString(), + // file + // }); +}); + +app.get("/pins", async (req, res) => { + const pins = []; + + for await (const pin of helia.pins.ls()) { + console.log("PIN LS", pin); + pins.push(pin); + } + + res.send({ + pins, + }); +}); + +app.post("/pins/pin/:cid", async (req, res) => { + const cid = CID.parse(req.params.cid); + + try { + for await (const pin of helia.pins.add(cid)) { + console.log("PINNED", pin); + } + } catch (err) { + console.error("Error:", err.message); + res.statusCode = 500; + return res.send({ + error: err.message, + }); + } + + res.send({ + pinned: true, + cid: cid.toString(), + }); +}); + +app.get("/pins/isPinned/:cid", async (req, res) => { + const cid = CID.parse(req.params.cid); + + const isPinned = await helia.pins.isPinned(cid); + + res.send({ + cid: cid.toString(), + isPinned, + }); +}); + +app.post("/file/upload", upload.single("image"), async (req, res) => { + if (!req.file) { + res.statusCode = 400; + return res.send("No file uploaded."); + } + + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const uploadDir = path.join(__dirname, "uploads"); // Directory where files will be uploaded + + const { buffer, originalname } = req.file; + + const cid = await ifs.addFile({ + path: `/${originalname}`, + content: buffer, + }); + console.log("Successfully added file", cid.toString()); + + const isPinned = await helia.pins.isPinned(cid); + if (isPinned) { + console.log("File already pinned", cid.toString()); + } else { + // Pin the file + for await (const pinned of helia.pins.add(cid)) { + console.log("Filed pinned", pinned); + } + } + + // Tell the network we can provide content for the passed CID + await helia.libp2p.services.dht.provide(cid); + console.log("Provided CID via DHT", cid.toString()); + + // console.log("Provide"); + // void helia.routing.provide(cid); + // console.log("Provide DONE"); + + res.send({ + originalname, + cid: cid.toString(), + }); + + // for await (const data of helia.pins.add(cid)) { + // console.log('pin data', data) + // } + // try { + // for await (const data of helia.libp2p.services.dht.provide(cid)) { + // console.log('provide data', data) + // } + // } catch (err) { + // console.log('provide error', err) + // } + // + // try { + // for await (const provs of helia.libp2p.services.dht.findProviders(cid)) { + // console.log('found providers', provs) + // } + // } catch (err) { + // console.log('find providers error', err) + // } + + // Provide the CIDs you create (once you're connected to a peer) + try { + // @ts-ignore + // for await (const event of helia.libp2p.services.dht.provide(cid)) { + // console.log("PROVIDE", event); + // } + } catch (err) { + console.log("An error occured while providing", err); + } + + // fs.writeFile( + // path.join(uploadDir, "example.jpg"), + // buffer, + // "base64", + // (err) => { + // if (err) { + // res.statusCode = 500; + // } + // }, + // ); + + // res.send({ + // originalname, + // cid: cid.toString(), + // }); + console.log("DONE"); +}); + +app.post("/test", async (req, res) => { + const textEncoder = new TextEncoder(); + const cid = await ifs.addFile({ + content: textEncoder.encode("Hello world asldgfhkasjdghsk;adjflkasgjd!"), + }); + + // @ts-ignore + for await (const event of helia.libp2p.services.dht.provide(cid)) { + console.log("PROVIDE", event); + } + + res.send({ + cid: cid.toString(), + }); +}); + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`); +}); diff --git a/src/libp2p.js b/src/libp2p.js new file mode 100644 index 0000000..e1d794d --- /dev/null +++ b/src/libp2p.js @@ -0,0 +1,54 @@ +import { noise } from "@chainsafe/libp2p-noise"; +import { yamux } from "@chainsafe/libp2p-yamux"; +import { bootstrap } from "@libp2p/bootstrap"; +import { identify } from "@libp2p/identify"; +import { kadDHT, removePrivateAddressesMapper } from "@libp2p/kad-dht"; +import { tcp } from "@libp2p/tcp"; +import { createLibp2p } from "libp2p"; +import { createFromJSON } from "@libp2p/peer-id-factory"; +import { datastore } from "./store.js"; +import config from "./config.js"; + +/* const peerId = await createFromJSON(config.peer); +console.log("PeerID:", peerId.toString()); */ + +export const libp2p_old = await createLibp2p({ + peerId, + datastore, + addresses: { + listen: [ + // "/ip4/127.0.0.1/tcp/4001", + "/ip4/0.0.0.0/tcp/4001", + ], + }, + transports: [tcp()], + connectionEncryption: [noise()], + streamMuxers: [yamux()], + peerDiscovery: [ + bootstrap({ + list: [ + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", + ], + }), + ], + services: { + identify: identify(), + dht: kadDHT({}), + }, +}); + +export const libp2p = await createLibp2p({ + addresses: { + listen: ["/ip4/127.0.0.1/tcp/4001", "/ip4/0.0.0.0/tcp/4001"], + }, + transports: [tcp()], + services: { + aminoDHT: kadDHT({ + protocol: "/ipfs/kad/1.0.0", + peerInfoMapper: removePrivateAddressesMapper, + }), + }, +}); diff --git a/src/store.js b/src/store.js new file mode 100644 index 0000000..527e82d --- /dev/null +++ b/src/store.js @@ -0,0 +1,5 @@ +import { FsBlockstore } from 'blockstore-fs'; +import { FsDatastore } from 'datastore-fs'; + +export const blockstore = new FsBlockstore("./blockstore"); +export const datastore = new FsDatastore('./datastore'); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7642e08 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "ES2022", + "target": "ES2021", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + // only for development + "strict": false, + "strictNullChecks": true, + "skipLibCheck": true + } +} From 225b328b1b1da0175affa1144cfb3dfe183a1998 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 15:58:48 +0100 Subject: [PATCH 002/167] chore(config): add ipf2 and ipf3 multiaddresses --- config.default.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index ee2ef3b..41773e9 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -1,10 +1,10 @@ { nodes: [ - // ipfs0 - "/ip4/5.161.75.77/tcp/4001/p2p/12D3KooWE849ZdcQhLP2yE4fbmU89APmjYXJPeuNChNUBGgeJJYe", // ipf1 "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ", // ipf2 + "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", // ipf3 + "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" ], } From d2ed9c80d060f71f3c9e1b892eea88afb8db9932 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 20:52:39 +0100 Subject: [PATCH 003/167] chore(libp2p, connectionManager): add IPFS nodes multiaddrs to whitelist --- src/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 34f1c34..1620dc9 100644 --- a/src/index.js +++ b/src/index.js @@ -56,10 +56,7 @@ const helia = await createHelia({ /** * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections */ - allow: [ - "/ip4/5.161.75.77/tcp/4001/p2p/12D3KooWE849ZdcQhLP2yE4fbmU89APmjYXJPeuNChNUBGgeJJYe", // tweetbot - "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ", // ipf1 - ], + allow: config.nodes }, }, }); From 23fa684da87dc8968372e66fd7656970f5cd0ed1 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 20:53:05 +0100 Subject: [PATCH 004/167] chore: rm console.log --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index 1620dc9..42f8323 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,6 @@ import { multiaddr } from "@multiformats/multiaddr"; // import { libp2p } from "./libp2p.js"; import { blockstore, datastore } from "./store.js"; import config from "./config.js"; -console.log("confi", config); const storage = multer.memoryStorage(); const upload = multer({ storage }); From 9339d9b351d7878599551d71b6ff5b2e0e9f624f Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 21:06:46 +0100 Subject: [PATCH 005/167] chore: add node name --- config.default.json5 | 18 ++++++++++++------ src/index.js | 3 ++- src/utils.js | 10 ++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 src/utils.js diff --git a/config.default.json5 b/config.default.json5 index 41773e9..e4031d5 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -1,10 +1,16 @@ { nodes: [ - // ipf1 - "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ", - // ipf2 - "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", - // ipf3 - "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" + { + name: "ipf1", + multiaddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ" + }, + { + name: "ipf2", + multiaddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", + }, + { + name: "ipf3", + multiaddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" + } ], } diff --git a/src/index.js b/src/index.js index 42f8323..465e56e 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import { multiaddr } from "@multiformats/multiaddr"; // import { libp2p } from "./libp2p.js"; import { blockstore, datastore } from "./store.js"; import config from "./config.js"; +import { getNodesList } from './utils.js'; const storage = multer.memoryStorage(); const upload = multer({ storage }); @@ -55,7 +56,7 @@ const helia = await createHelia({ /** * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections */ - allow: config.nodes + allow: getNodesList() }, }, }); diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..4bd67de --- /dev/null +++ b/src/utils.js @@ -0,0 +1,10 @@ +import config from './config.js' + +/** + * Get a list of own IPFS nodes from the config file + */ +export function getNodesList(excludedMultiaddrs = []) { + return config.nodes + .map(node => node.multiaddr) + .filter(multiaddr => !excludedMultiaddrs.includes(multiaddr)) +} From 6943e1549190a5dc2aa2372b3bfa978289c1c9d4 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 23:19:03 +0100 Subject: [PATCH 006/167] feat: auto peering --- config.default.json5 | 6 +-- package-lock.json | 23 +++++++++ package.json | 1 + src/cron.js | 36 +++++++++++++++ src/helia.js | 48 +++++++++++++++++++ src/helia.ts | 32 ------------- src/index.js | 108 ++++++++++++++++++++++--------------------- src/utils.js | 44 ++++++++++++++++-- 8 files changed, 206 insertions(+), 92 deletions(-) create mode 100644 src/cron.js create mode 100644 src/helia.js delete mode 100644 src/helia.ts diff --git a/config.default.json5 b/config.default.json5 index e4031d5..f91dd63 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -1,15 +1,15 @@ { nodes: [ { - name: "ipf1", + name: "ipfs1", multiaddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ" }, { - name: "ipf2", + name: "ipfs2", multiaddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", }, { - name: "ipf3", + name: "ipfs3", multiaddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" } ], diff --git a/package-lock.json b/package-lock.json index 213aff4..60aca62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "blockstore-core": "^4.4.0", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", + "cron": "^3.1.6", "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", "express": "^4.19.2", @@ -4488,6 +4489,11 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/luxon": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.8.tgz", + "integrity": "sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5652,6 +5658,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.6.tgz", + "integrity": "sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==", + "dependencies": { + "@types/luxon": "~3.3.0", + "luxon": "~3.4.0" + } + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -7949,6 +7964,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", diff --git a/package.json b/package.json index 4f51784..48a59b3 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "blockstore-core": "^4.4.0", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", + "cron": "^3.1.6", "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", "express": "^4.19.2", diff --git a/src/cron.js b/src/cron.js new file mode 100644 index 0000000..9017f2e --- /dev/null +++ b/src/cron.js @@ -0,0 +1,36 @@ +import { CronJob } from "cron"; +import { helia } from "./helia.js"; +import { getNodesList } from "./utils.js"; + +/** + * Auto-peering between ADM IPFS nodes. + */ +export const autoPeering = new CronJob("*/10 * * * * *", autoPeeringHandler); + +export async function autoPeeringHandler() { + const peerId = helia.libp2p.peerId; + console.log( + `[Cron] Running "autoPeering" cronjob. Current node peerId: ${helia.libp2p.peerId}`, + ); + + const nodes = getNodesList([peerId]); + console.log( + "Peering nodes list:", + nodes.map((node) => node.name), + ); + + const successPeers = []; + + for await (const node of nodes) { + console.log(`Start peering ${node.name} node (${node.multiaddr})...`); + try { + const connection = await helia.libp2p.dial(node.multiaddr); + console.log(`Successfully peered with ${node.name}`); + successPeers.push(node.name); + } catch (err) { + console.log(`Peering with ${node.name} failed. Error:`, err.message); + } + } + + return successPeers; +} diff --git a/src/helia.js b/src/helia.js new file mode 100644 index 0000000..a01a64d --- /dev/null +++ b/src/helia.js @@ -0,0 +1,48 @@ +import { bootstrap } from "@libp2p/bootstrap"; +import { createHelia } from "helia"; +import { blockstore, datastore } from "./store.js"; +import { getAllowNodesMultiaddrs } from "./utils.js"; + +export const helia = await createHelia({ + datastore, + blockstore, + libp2p: { + peerDiscovery: [ + bootstrap({ + list: [ + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + ], + }), + ], + addresses: { + listen: ["/ip4/0.0.0.0/tcp/4001", "/ip6/::/tcp/4002", "/webrtc"], + }, + connectionManager: { + /** + * The total number of connections allowed to be open at one time + */ + // maxConnections: 10, + + /** + * If the number of open connections goes below this number, the node + * will try to connect to randomly selected peers from the peer store + */ + // minConnections: 5, + + /** + * How many connections can be open but not yet upgraded + */ + // maxIncomingPendingConnections: 10, + + /** + * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections + */ + allow: getAllowNodesMultiaddrs(), + }, + }, +}); diff --git a/src/helia.ts b/src/helia.ts deleted file mode 100644 index ac0372f..0000000 --- a/src/helia.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { identify } from '@libp2p/identify'; -import { createHelia } from "helia"; -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { noise } from "@chainsafe/libp2p-noise"; -import { yamux } from "@chainsafe/libp2p-yamux"; -import { MemoryDatastore } from "datastore-core"; -import { MemoryBlockstore } from "blockstore-core"; -import { unixfs } from "@helia/unixfs"; -import last from "it-last"; - -const blockstore = new MemoryBlockstore(); -const datastore = new MemoryDatastore(); - -const libp2p = await createLibp2p({ - addresses: { - listen: ["/ip4/0.0.0.0/tcp/0"], - }, - transports: [tcp()], - connectionEncryption: [noise()], - streamMuxers: [yamux()], - datastore, - services: { - identify: identify(), - } -}); - -const helia = await createHelia({ - datastore, - blockstore, - libp2p, -}); diff --git a/src/index.js b/src/index.js index 465e56e..0cd3f94 100644 --- a/src/index.js +++ b/src/index.js @@ -1,71 +1,26 @@ import { unixfs } from "@helia/unixfs"; -import { bootstrap } from "@libp2p/bootstrap"; import { peerIdFromString } from "@libp2p/peer-id"; -import { createHelia } from "helia"; import { CID } from "multiformats/cid"; import path from "node:path"; import express from "express"; import { fileURLToPath } from "url"; import multer from "multer"; import { multiaddr } from "@multiformats/multiaddr"; - -// import { libp2p } from "./libp2p.js"; -import { blockstore, datastore } from "./store.js"; -import config from "./config.js"; -import { getNodesList } from './utils.js'; +import { autoPeeringHandler } from "./cron.js"; +import { helia } from "./helia.js"; const storage = multer.memoryStorage(); const upload = multer({ storage }); -const helia = await createHelia({ - datastore, - blockstore, - libp2p: { - peerDiscovery: [ - bootstrap({ - list: [ - "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", - "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - ], - }), - ], - addresses: { - listen: ["/ip4/0.0.0.0/tcp/4001", "/ip6/::/tcp/4002", "/webrtc"], - }, - connectionManager: { - /** - * The total number of connections allowed to be open at one time - */ - // maxConnections: 10, - - /** - * If the number of open connections goes below this number, the node - * will try to connect to randomly selected peers from the peer store - */ - // minConnections: 5, - - /** - * How many connections can be open but not yet upgraded - */ - // maxIncomingPendingConnections: 10, - - /** - * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections - */ - allow: getNodesList() - }, - }, -}); // console.log("Created Helia instance"); // await helia.libp2p.services.dht.setMode("server"); // console.log("Switched DHT to server mode"); helia.libp2p.getMultiaddrs().forEach((addr) => { console.log(`Listening on ${addr.toString()}`); + + // @todo + // cron.autoPeering.start() }); helia.libp2p.addEventListener("peer:discovery", (evt) => { @@ -119,6 +74,14 @@ app.get("/", (req, res) => { res.send("Hello World!"); }); +app.get("/cron/autopeering", async (req, res) => { + const successPeers = await autoPeeringHandler(); + + res.send({ + peeredSuccessfullyTo: successPeers, + }); +}); + app.get("/libp2p/services/ping", async (req, res) => { try { const peerId = peerIdFromString(req.query.peerId); @@ -136,11 +99,49 @@ app.get("/libp2p/services/ping", async (req, res) => { }); app.get("/libp2p/peerStore", async (req, res) => { + const peerId = req.query.peerId; + + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [ + (peer) => { + if (!peerId) { + return true; + } + + return peer.id.toString() === peerId; + }, + ], + limit: 10, + }); + + res.send({ + length: peers.length, + peers: peers.map((peer) => { + return { + id: peer.id.toString(), + }; + }), + }); + } catch (err) { + res.send({ + error: err.message, + }); + } +}); + +app.get("/libp2p/peerInfo", async (req, res) => { + const peerId = req.query.peerId; + try { - const peers = await helia.libp2p.peerStore.all(); + const peers = await helia.libp2p.peerStore.all({ + filters: [(peer) => peer.id.toString() === peerId], + limit: 10, + }); res.send({ length: peers.length, + peer: peers, }); } catch (err) { res.send({ @@ -198,7 +199,10 @@ app.get("/libp2p/new-peers/log", async (req, res) => { }); app.get("/libp2p/connections", async (req, res) => { - const connections = helia.libp2p.getConnections(); + const peerId = req.query.peerId; + const connections = helia.libp2p.getConnections( + peerId ? peerIdFromString(peerId) : undefined, + ); res.send({ length: connections.length, diff --git a/src/utils.js b/src/utils.js index 4bd67de..374b80f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,10 +1,44 @@ -import config from './config.js' +import { peerIdFromString } from "@libp2p/peer-id"; +import { multiaddr } from "@multiformats/multiaddr"; +import config from "./config.js"; + +/** + * Get peerId from multiaddr string + * @param multiaddr E.g. "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ" + * @return Return instance of PeerId, e.g. "PeerId(12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ)" + */ +export function peerIdFromMultiaddr(multiaddr) { + // @todo parse multiaddrs like this: + // "/ip4/38.143.66.227/tcp/4001/p2p/12D3KooWCqayYQ5B8bF6NAaDgbZRcE9eyQFEdSYGjoSadBt1oTVT/p2p-circuit" + return peerIdFromString(multiaddr.split("/").pop()); +} + +export function parseNodes() { + return config.nodes.map((node) => ({ + name: node.name, + multiaddr: multiaddr(node.multiaddr), + peerId: peerIdFromMultiaddr(node.multiaddr), + })); +} /** * Get a list of own IPFS nodes from the config file */ -export function getNodesList(excludedMultiaddrs = []) { - return config.nodes - .map(node => node.multiaddr) - .filter(multiaddr => !excludedMultiaddrs.includes(multiaddr)) +export function getNodesList(excludedPeerIds = []) { + return parseNodes().filter( + (node) => !excludedPeerIds.includes(node.peerId.toString()) + ); +} + +/** + * Get node name by `multiaddr` + */ +export function getNodeName(multiaddr) { + return parseNodes().find( + (node) => node.multiaddr.toString() === multiaddr.toString(), + )?.name; +} + +export function getAllowNodesMultiaddrs() { + return parseNodes().map((node) => node.multiaddr.toString()); } From 0db56a81ce6d75020c4168092a77173bd1d06109 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 23:27:57 +0100 Subject: [PATCH 007/167] fix: fix ipfs1 multiaddr --- config.default.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.default.json5 b/config.default.json5 index f91dd63..da1a0d1 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -2,7 +2,7 @@ nodes: [ { name: "ipfs1", - multiaddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ" + multiaddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWDFkrfLKMt46J5uh77SpNkxhxtSURqDnKY7Bnfr9etSQi" }, { name: "ipfs2", From c06b36041e83ca32051cf66e4d242429876a0207 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 8 Apr 2024 23:30:25 +0100 Subject: [PATCH 008/167] fix: fix peerId string type --- src/cron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cron.js b/src/cron.js index 9017f2e..db815ad 100644 --- a/src/cron.js +++ b/src/cron.js @@ -13,7 +13,7 @@ export async function autoPeeringHandler() { `[Cron] Running "autoPeering" cronjob. Current node peerId: ${helia.libp2p.peerId}`, ); - const nodes = getNodesList([peerId]); + const nodes = getNodesList([peerId.toString()]); console.log( "Peering nodes list:", nodes.map((node) => node.name), From 618b47933786b962184ce8c39427d6e9f13f2348 Mon Sep 17 00:00:00 2001 From: bludnic Date: Tue, 9 Apr 2024 00:06:42 +0100 Subject: [PATCH 009/167] feat(/file/:cid): timeout out after 30s --- src/index.js | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/index.js b/src/index.js index 0cd3f94..7503d98 100644 --- a/src/index.js +++ b/src/index.js @@ -218,20 +218,38 @@ app.get("/libp2p/status", async (req, res) => { app.get("/file/:cid", async (req, res) => { const cid = CID.parse(req.params.cid); - const file = ifs.cat(cid); - const chunks = []; - for await (const chunk of file) { - chunks.push(chunk); - } - - res.set("Content-Type", "image/jpeg"); - res.send(Buffer.concat(chunks)); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Operation timed out')), 30000) + ); - // res.send({ - // cid: cid?.toString(), - // file - // }); + try { + const filePromise = (async () => { + const file = ifs.cat(cid); + const chunks = []; + for await (const chunk of file) { + chunks.push(chunk); + } + return Buffer.concat(chunks); + })(); + + const result = await Promise.race([filePromise, timeoutPromise]); + + // If filePromise wins the race, send the file + res.set("Content-Type", "image/jpeg"); + res.send(result); + } catch (error) { + console.error(error); + if (error.message === 'Operation timed out') { + res.status(408).send({ + error: 'Can not find requested CID. Operation timed out.', + }); + } else { + res.status(500).send({ + error: 'Internal Server Error. Check the logs for details.' + }); + } + } }); app.get("/pins", async (req, res) => { From fdca00ec595d04127a8aca96ef236799b8da38d7 Mon Sep 17 00:00:00 2001 From: bludnic Date: Tue, 9 Apr 2024 00:19:23 +0100 Subject: [PATCH 010/167] feat(/file/upload): upload multiple files --- src/index.js | 115 +++++++++++++++++---------------------------------- 1 file changed, 37 insertions(+), 78 deletions(-) diff --git a/src/index.js b/src/index.js index 7503d98..bd423e8 100644 --- a/src/index.js +++ b/src/index.js @@ -220,7 +220,7 @@ app.get("/file/:cid", async (req, res) => { const cid = CID.parse(req.params.cid); const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Operation timed out')), 30000) + setTimeout(() => reject(new Error("Operation timed out")), 30000), ); try { @@ -240,13 +240,13 @@ app.get("/file/:cid", async (req, res) => { res.send(result); } catch (error) { console.error(error); - if (error.message === 'Operation timed out') { + if (error.message === "Operation timed out") { res.status(408).send({ - error: 'Can not find requested CID. Operation timed out.', + error: "Can not find requested CID. Operation timed out.", }); } else { res.status(500).send({ - error: 'Internal Server Error. Check the logs for details.' + error: "Internal Server Error. Check the logs for details.", }); } } @@ -297,92 +297,51 @@ app.get("/pins/isPinned/:cid", async (req, res) => { }); }); -app.post("/file/upload", upload.single("image"), async (req, res) => { - if (!req.file) { +app.post("/file/upload", upload.array("files", 5), async (req, res) => { + if (!req.files) { res.statusCode = 400; - return res.send("No file uploaded."); + return res.send({ + error: "No file uploaded", + }); } + console.log("req.files", req.files); - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const uploadDir = path.join(__dirname, "uploads"); // Directory where files will be uploaded + const cids = []; + for (const file of req.files) { + console.log(`Adding ${file.originalname} to IPFS`); - const { buffer, originalname } = req.file; + const { buffer, originalname } = file; - const cid = await ifs.addFile({ - path: `/${originalname}`, - content: buffer, - }); - console.log("Successfully added file", cid.toString()); + const cid = await ifs.addFile({ + path: `/${originalname}`, + content: buffer, + }); + console.log("Successfully added file", cid.toString()); + cids.push(cid); - const isPinned = await helia.pins.isPinned(cid); - if (isPinned) { - console.log("File already pinned", cid.toString()); - } else { - // Pin the file - for await (const pinned of helia.pins.add(cid)) { - console.log("Filed pinned", pinned); + const isPinned = await helia.pins.isPinned(cid); + if (isPinned) { + console.log("File already pinned", cid.toString()); + } else { + // Pin the file + for await (const pinned of helia.pins.add(cid)) { + console.log("Filed pinned", pinned); + } } - } - // Tell the network we can provide content for the passed CID - await helia.libp2p.services.dht.provide(cid); - console.log("Provided CID via DHT", cid.toString()); + // Tell the network we can provide content for the passed CID + await helia.libp2p.services.dht.provide(cid); + console.log("Provided CID via DHT", cid.toString()); - // console.log("Provide"); - // void helia.routing.provide(cid); - // console.log("Provide DONE"); + // console.log("Provide"); + // void helia.routing.provide(cid); + // console.log("Provide DONE"); + } res.send({ - originalname, - cid: cid.toString(), + filesNames: req.files.map((file) => file.originalname), + cids: cids.map((cid) => cid.toString()), }); - - // for await (const data of helia.pins.add(cid)) { - // console.log('pin data', data) - // } - // try { - // for await (const data of helia.libp2p.services.dht.provide(cid)) { - // console.log('provide data', data) - // } - // } catch (err) { - // console.log('provide error', err) - // } - // - // try { - // for await (const provs of helia.libp2p.services.dht.findProviders(cid)) { - // console.log('found providers', provs) - // } - // } catch (err) { - // console.log('find providers error', err) - // } - - // Provide the CIDs you create (once you're connected to a peer) - try { - // @ts-ignore - // for await (const event of helia.libp2p.services.dht.provide(cid)) { - // console.log("PROVIDE", event); - // } - } catch (err) { - console.log("An error occured while providing", err); - } - - // fs.writeFile( - // path.join(uploadDir, "example.jpg"), - // buffer, - // "base64", - // (err) => { - // if (err) { - // res.statusCode = 500; - // } - // }, - // ); - - // res.send({ - // originalname, - // cid: cid.toString(), - // }); - console.log("DONE"); }); app.post("/test", async (req, res) => { From eb093c6be9160256899ab428e0d6da5710d1f9c6 Mon Sep 17 00:00:00 2001 From: bludnic Date: Tue, 9 Apr 2024 00:32:09 +0100 Subject: [PATCH 011/167] feat(/file/upload): automatically detect mimetype --- package-lock.json | 81 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/index.js | 4 ++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 60aca62..5cbebdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", "express": "^4.19.2", + "file-type": "^19.0.0", "helia": "^4.1.0", "it-last": "^3.0.4", "json5": "^2.2.3", @@ -4375,6 +4376,11 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -6279,6 +6285,22 @@ "bser": "2.1.1" } }, + "node_modules/file-type": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.0.0.tgz", + "integrity": "sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.2", + "strtok3": "^7.0.0", + "token-types": "^5.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -9197,6 +9219,18 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/peek-readable": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", + "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9741,6 +9775,21 @@ "node": ">= 6" } }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10516,6 +10565,22 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "peer": true }, + "node_modules/strtok3": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", + "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -10756,6 +10821,22 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", + "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", diff --git a/package.json b/package.json index 48a59b3..9b9d5d4 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", "express": "^4.19.2", + "file-type": "^19.0.0", "helia": "^4.1.0", "it-last": "^3.0.4", "json5": "^2.2.3", diff --git a/src/index.js b/src/index.js index bd423e8..da7d864 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import { unixfs } from "@helia/unixfs"; import { peerIdFromString } from "@libp2p/peer-id"; +import { fileTypeFromBuffer } from 'file-type'; import { CID } from "multiformats/cid"; import path from "node:path"; import express from "express"; @@ -236,7 +237,8 @@ app.get("/file/:cid", async (req, res) => { const result = await Promise.race([filePromise, timeoutPromise]); // If filePromise wins the race, send the file - res.set("Content-Type", "image/jpeg"); + const fileType = await fileTypeFromBuffer(result); + res.set("Content-Type", fileType.mime); res.send(result); } catch (error) { console.error(error); From 2d1a6e199af329769b799098b7d7b5f39f86ba3a Mon Sep 17 00:00:00 2001 From: bludnic Date: Tue, 9 Apr 2024 12:44:19 +0100 Subject: [PATCH 012/167] fix: fix mimetype for encrypted files --- src/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index da7d864..8322bed 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import { unixfs } from "@helia/unixfs"; import { peerIdFromString } from "@libp2p/peer-id"; -import { fileTypeFromBuffer } from 'file-type'; +import { fileTypeFromBuffer } from "file-type"; import { CID } from "multiformats/cid"; import path from "node:path"; import express from "express"; @@ -238,7 +238,10 @@ app.get("/file/:cid", async (req, res) => { // If filePromise wins the race, send the file const fileType = await fileTypeFromBuffer(result); - res.set("Content-Type", fileType.mime); + if (fileType) { + res.set("Content-Type", fileType.mime); + } + res.send(result); } catch (error) { console.error(error); From 2fcf3f4dbc88a866d5226015c5f5e0fcdb07d837 Mon Sep 17 00:00:00 2001 From: bludnic Date: Wed, 10 Apr 2024 05:16:47 +0100 Subject: [PATCH 013/167] feat: add findProviders endpoint --- src/index.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/index.js b/src/index.js index 8322bed..bf8c4b7 100644 --- a/src/index.js +++ b/src/index.js @@ -75,6 +75,29 @@ app.get("/", (req, res) => { res.send("Hello World!"); }); +app.get("/routing/findProviders/:cid", async (req, res) => { + try { + const cid = CID.parse(req.params.cid); + + const providers = []; + for await (const provider of helia.routing.findProviders(cid)) { + console.log( + `Found provider of CID:${cid.toString()}, PeerId:${provider.id.toString()}`, + ); + providers.push(provider.id.toString()); + } + + res.send({ + providers, + }); + } catch (err) { + res.send({ + error: err.message, + }); + console.error(err); + } +}); + app.get("/cron/autopeering", async (req, res) => { const successPeers = await autoPeeringHandler(); From 3745df0e37236fcd65909ad12e07466aed8cd12a Mon Sep 17 00:00:00 2001 From: bludnic Date: Wed, 10 Apr 2024 05:17:01 +0100 Subject: [PATCH 014/167] feat: enable routing provide --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index bf8c4b7..048fa2b 100644 --- a/src/index.js +++ b/src/index.js @@ -361,9 +361,9 @@ app.post("/file/upload", upload.array("files", 5), async (req, res) => { await helia.libp2p.services.dht.provide(cid); console.log("Provided CID via DHT", cid.toString()); - // console.log("Provide"); - // void helia.routing.provide(cid); - // console.log("Provide DONE"); + console.log(`Routing: Providing ${cid}`); + void helia.routing.provide(cid); + console.log("Routing: Provide DONE"); } res.send({ From 0f6aae39339a10e09e88c69defcacc2d01d6e1ef Mon Sep 17 00:00:00 2001 From: bludnic Date: Wed, 10 Apr 2024 05:55:36 +0100 Subject: [PATCH 015/167] feat: switch DTH from client to server --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 048fa2b..28a32c5 100644 --- a/src/index.js +++ b/src/index.js @@ -13,9 +13,9 @@ import { helia } from "./helia.js"; const storage = multer.memoryStorage(); const upload = multer({ storage }); -// console.log("Created Helia instance"); -// await helia.libp2p.services.dht.setMode("server"); -// console.log("Switched DHT to server mode"); +console.log("Created Helia instance"); +await helia.libp2p.services.dht.setMode("server"); +console.log("Switched DHT to server mode"); helia.libp2p.getMultiaddrs().forEach((addr) => { console.log(`Listening on ${addr.toString()}`); From e11b82353d0916ba5d29f1df63f291c2614858d0 Mon Sep 17 00:00:00 2001 From: bludnic Date: Wed, 10 Apr 2024 05:59:22 +0100 Subject: [PATCH 016/167] feat: switch DTH from server to client --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 28a32c5..048fa2b 100644 --- a/src/index.js +++ b/src/index.js @@ -13,9 +13,9 @@ import { helia } from "./helia.js"; const storage = multer.memoryStorage(); const upload = multer({ storage }); -console.log("Created Helia instance"); -await helia.libp2p.services.dht.setMode("server"); -console.log("Switched DHT to server mode"); +// console.log("Created Helia instance"); +// await helia.libp2p.services.dht.setMode("server"); +// console.log("Switched DHT to server mode"); helia.libp2p.getMultiaddrs().forEach((addr) => { console.log(`Listening on ${addr.toString()}`); From 54791b48fb412d5ff65f06ea42698ef18541464b Mon Sep 17 00:00:00 2001 From: bludnic Date: Wed, 17 Apr 2024 01:13:34 +0100 Subject: [PATCH 017/167] feat: use verifiedFetch --- package-lock.json | 536 ++++++++++++++++++++++++++++++++-------------- package.json | 1 + src/index.js | 18 +- 3 files changed, 384 insertions(+), 171 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5cbebdf..550b8d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@chainsafe/libp2p-yamux": "^6.0.2", "@helia/mfs": "^3.0.2", "@helia/unixfs": "^3.0.2", + "@helia/verified-fetch": "^1.3.12", "@libp2p/autonat": "^1.0.14", "@libp2p/bootstrap": "^10.0.17", "@libp2p/circuit-relay-v2": "^1.0.17", @@ -2387,19 +2388,144 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@helia/bitswap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.0.0.tgz", + "integrity": "sha512-ZcQNBp2TyU6OVvt8J3GvdQ3f5er8GrjEQrIKLl5jHSoXtbyNaCv9gBzLn85aMMXWBIUQcaom+5s1LIaJ5bqbjw==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@helia/utils": "^0.2.0", + "@libp2p/interface": "^1.1.2", + "@libp2p/logger": "^4.0.5", + "@libp2p/peer-collections": "^5.1.6", + "@libp2p/utils": "^5.2.3", + "@multiformats/multiaddr": "^12.1.14", + "any-signal": "^4.1.1", + "debug": "^4.3.4", + "interface-blockstore": "^5.2.9", + "interface-store": "^5.1.7", + "it-all": "^3.0.4", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-length-prefixed": "^9.0.0", + "it-length-prefixed-stream": "^1.1.6", + "it-map": "^3.0.5", + "it-pipe": "^3.0.1", + "it-take": "^3.0.1", + "multiformats": "^13.0.1", + "p-defer": "^4.0.0", + "progress-events": "^1.0.0", + "protons-runtime": "^5.0.0", + "race-event": "^1.2.0", + "uint8-varint": "^2.0.3", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^5.0.1" + } + }, + "node_modules/@helia/bitswap/node_modules/@helia/utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, "node_modules/@helia/block-brokers": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.0.3.tgz", - "integrity": "sha512-MlVlm7vWgQ/4Ga4/IVuKwhXIIHHQBr/+rfSxxPcO1Al30s+6SNSMDwwVKWY7tHNTOsnc9IC/bTURCwR/yBstrQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.1.0.tgz", + "integrity": "sha512-Y6iVROn1vH6dlQnEUerc7ULASAq2fY1xlrpBg93HO3JyCc0C1usCzsrtEcOMGd852dDM1SY7QN1wKPCVsq2w/g==", "dependencies": { - "@helia/interface": "^4.1.0", + "@helia/bitswap": "^1.0.0", + "@helia/interface": "^4.2.0", + "@helia/utils": "^0.2.0", "@libp2p/interface": "^1.1.4", + "@libp2p/utils": "^5.2.6", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "@multiformats/multiaddr-to-uri": "^10.0.1", "interface-blockstore": "^5.2.10", - "ipfs-bitswap": "^20.0.2", + "interface-store": "^5.1.8", "multiformats": "^13.1.0", "progress-events": "^1.0.0" } }, + "node_modules/@helia/block-brokers/node_modules/@helia/utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@helia/car": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@helia/car/-/car-3.1.3.tgz", + "integrity": "sha512-y+i/eyaUwi0XzfFOPw4VlGUH/DfonD38CoHfjqxWKW8dKxReKdVMkca6hjoDmt31buVSIMeDoAw17iwh1IjFvg==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@ipld/car": "^5.3.0", + "@libp2p/interfaces": "^3.3.2", + "interface-blockstore": "^5.2.10", + "it-drain": "^3.0.5", + "it-map": "^3.0.5", + "multiformats": "^13.1.0", + "p-defer": "^4.0.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0" + } + }, "node_modules/@helia/delegated-routing-v1-http-api-client": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.0.tgz", @@ -2421,10 +2547,57 @@ "uint8arrays": "^5.0.1" } }, + "node_modules/@helia/http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@helia/http/-/http-1.0.4.tgz", + "integrity": "sha512-lXKX14xbSi2pDA7UOu3U4S1rNvPTkpLJtQpZS/amvo0ooAtbni+fA2uHDx/lTO7lNCfJMD9YLP1hikHz6D+nAQ==", + "dependencies": { + "@helia/block-brokers": "^2.1.0", + "@helia/interface": "^4.2.0", + "@helia/routers": "^1.0.3", + "@helia/utils": "^0.2.0", + "blockstore-core": "^4.4.0", + "datastore-core": "^9.2.9" + } + }, + "node_modules/@helia/http/node_modules/@helia/utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, "node_modules/@helia/interface": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.1.0.tgz", - "integrity": "sha512-uSuUEhV9njhMRCc44EtMW9mvXsshEke4qxyIy4YNiZfgDs1vPfbn6/QHHgS7gIMPBCkg+v/mCRdBAyTjW3d2dg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.2.0.tgz", + "integrity": "sha512-8F1rZAskqxcIpXEcJOa050fsdYQLDbf6WRgUyPve+gPIZVTvwgfuGuOlZxxrXAFtH7CujRiP3yMAN3V6iTbuRA==", "dependencies": { "@libp2p/interface": "^1.1.4", "@multiformats/dns": "^1.0.1", @@ -2435,6 +2608,24 @@ "progress-events": "^1.0.0" } }, + "node_modules/@helia/ipns": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@helia/ipns/-/ipns-7.2.1.tgz", + "integrity": "sha512-HHfZr24H3iGFNHVvbOL+gwVXkmvo7umJ7US4fAGNbgXytXSEY/GmIqF+fDA0xBtPvFSrccv18XoKH7Dz+yNrfg==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/kad-dht": "^12.0.8", + "@libp2p/logger": "^4.0.7", + "@libp2p/peer-id": "^4.0.7", + "@multiformats/dns": "^1.0.1", + "interface-datastore": "^8.2.11", + "ipns": "^9.1.0", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, "node_modules/@helia/mfs": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.2.tgz", @@ -2452,12 +2643,12 @@ } }, "node_modules/@helia/routers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.2.tgz", - "integrity": "sha512-lLoDjVYZwwwav7UmEOnpNQJsL6UdQNCbAyFJxDcKk+T/DTG10B/LDItM20+FO1F35RjfW9LZ0Czz3/OokE9raw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.3.tgz", + "integrity": "sha512-bX6I5ir2YCK+LW/G0YrHhqJW0SjG4DDnkxLOk2dzmZNy2OCB+Ogt0+W2keyz5nHGfTuwcBBn4qH5ouN0vTZ74w==", "dependencies": { "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.1.0", + "@helia/interface": "^4.2.0", "@libp2p/interface": "^1.1.4", "ipns": "^9.0.0", "it-first": "^3.0.4", @@ -2522,6 +2713,53 @@ "uint8arrays": "^5.0.2" } }, + "node_modules/@helia/verified-fetch": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.12.tgz", + "integrity": "sha512-eLDGvrDLDs09vZk5PxYmPTB7WlJ88ff4GwF7gRbH5zaqZVVCUP3S6lkEvmNM+9MYPfElfR/wUOmN2GJIVqDQ/A==", + "dependencies": { + "@helia/block-brokers": "^2.1.0", + "@helia/car": "^3.1.3", + "@helia/http": "^1.0.4", + "@helia/interface": "^4.2.0", + "@helia/ipns": "^7.2.1", + "@helia/routers": "^1.0.3", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/interface": "^1.1.6", + "@libp2p/kad-dht": "^12.0.11", + "@libp2p/peer-id": "^4.0.9", + "@multiformats/dns": "^1.0.6", + "cborg": "^4.2.0", + "hashlru": "^2.3.0", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "ipfs-unixfs-exporter": "^13.5.0", + "it-map": "^3.0.5", + "it-pipe": "^3.0.1", + "it-tar": "^6.0.5", + "it-to-browser-readablestream": "^2.0.6", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.3" + } + }, + "node_modules/@ipld/car": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@ipld/car/-/car-5.3.0.tgz", + "integrity": "sha512-OB8LVvJeVAFFGluNIkZeDZ/aGeoekFKsuIvNT9I5sJIb5WekQuW5+lekjQ7Z7mZ7DBKuke/kI4jBT1j0/akU1w==", + "dependencies": { + "@ipld/dag-cbor": "^9.0.7", + "cborg": "^4.0.5", + "multiformats": "^13.0.0", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@ipld/dag-cbor": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz", @@ -2779,18 +3017,18 @@ } }, "node_modules/@libp2p/crypto": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.4.tgz", - "integrity": "sha512-sJR/516/yJFwWJGme7sbQhe2DQYfoNTwGqKOmCz3M6fdeucfSCCLkaNjphijzvUoXROTsDs5Lv4xCTh0/KanMw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.6.tgz", + "integrity": "sha512-AJ4i3DHOTlY961O26M3k1IjmU4rUd5WgeK4t9IRzFfLIbD6uwA+cevJMG2qr0UHJfbYdGKKQ2Po1wqZONoIA9Q==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", + "@libp2p/interface": "^1.2.0", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", "asn1js": "^3.0.5", "multiformats": "^13.1.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/dcutr": { @@ -2828,11 +3066,11 @@ } }, "node_modules/@libp2p/interface": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.1.5.tgz", - "integrity": "sha512-BjFgv/3VwEDNRcFKL4KW6g29IcUWUjaTJhyZVGWtodFuPjZsZHJgoQU7T/FFxDcfTdI90qpFbTREycOB+VL9NQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.2.0.tgz", + "integrity": "sha512-ImnGNl3El/AukgaojACT8i9SNW1FOsrThcQU/qA3w5tEBR5p84Uwgzl/nxa4X5vGinItUJ9jLEJmtkQJENoiGQ==", "dependencies": { - "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr": "^12.2.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", @@ -2841,13 +3079,13 @@ } }, "node_modules/@libp2p/interface-internal": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.0.10.tgz", - "integrity": "sha512-12mWf+H6Ue/qUzruzuVG2rKk78r810klBYDISvYdsdj+JO4sk/iIMlsFsWGu40LWVOG7eQLYf6QDP2jx4vo9qQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.1.0.tgz", + "integrity": "sha512-B6Cu3Mhp5kY2Z1cU0soCR4ZtjZtE4FuWE0qdJNauOpcQe9HOjPF8SanFmeEIZ0FKSOo0onQdQi2YdNUTtOVyvQ==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-collections": "^5.1.8", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/interface": "^1.2.0", + "@libp2p/peer-collections": "^5.1.10", + "@multiformats/multiaddr": "^12.2.1", "uint8arraylist": "^2.4.8" } }, @@ -2861,18 +3099,18 @@ } }, "node_modules/@libp2p/kad-dht": { - "version": "12.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.10.tgz", - "integrity": "sha512-GHeoyOFawAkUAr++9V/a7txUOjhQDKdXd8+Zx4dRPdFMlpHGAV6Tdjcewv49upf98ahWB7FHxwf2qs3kuF3jKg==", - "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", - "@types/sinon": "^17.0.3", + "version": "12.0.13", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.13.tgz", + "integrity": "sha512-as+pIHcrjVTXkf6kyULtnJg/RRvrbVjyycPD8HtqD5SKB0PDiYvJO0LY0oVAHFFi5oGMCqSG5777VQfC/tmRhQ==", + "dependencies": { + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.2.0", + "@libp2p/interface-internal": "^1.1.0", + "@libp2p/peer-collections": "^5.1.10", + "@libp2p/peer-id": "^4.0.10", + "@libp2p/record": "^4.0.0", + "@libp2p/utils": "^5.3.1", + "@multiformats/multiaddr": "^12.2.1", "any-signal": "^4.1.1", "hashlru": "^2.3.0", "interface-datastore": "^8.2.11", @@ -2887,15 +3125,15 @@ "it-pushable": "^3.2.3", "it-take": "^3.0.4", "multiformats": "^13.1.0", - "p-defer": "^4.0.0", - "p-event": "^6.0.0", + "p-defer": "^4.0.1", + "p-event": "^6.0.1", "p-queue": "^8.0.1", "progress-events": "^1.0.0", "protons-runtime": "^5.4.0", "race-signal": "^1.0.2", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/keychain": { @@ -2914,12 +3152,12 @@ } }, "node_modules/@libp2p/logger": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.8.tgz", - "integrity": "sha512-H8vdQdjkPxnR9JbjyWQpyc+hjuE18ONvZet3hyOFqUUi28LkNSNMaW3VozgL+Gvb/JyJ3Hzni4Wbiqs2+EOi3g==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.10.tgz", + "integrity": "sha512-JiRfJHO/D9Jlh2rJ6STnONoeQevBAdAZaGUxrtvBf4RFfucldSFEMOtdkFO8xFGuiA90Q2kj4BE2douG6fB3Lw==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/interface": "^1.2.0", + "@multiformats/multiaddr": "^12.2.1", "debug": "^4.3.4", "interface-datastore": "^8.2.11", "multiformats": "^13.1.0" @@ -2972,22 +3210,22 @@ } }, "node_modules/@libp2p/peer-collections": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.8.tgz", - "integrity": "sha512-g2KnQqEZ3lVLcVK2S0zJMo5NcqiO9jeKr2HyB2qc7EHDrsHeXIiD9Rs7EU2XPcYSEBVGJTrQpjUwya+QhTw23Q==", + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.10.tgz", + "integrity": "sha512-Edr4FBzCgE7FRgc0wfYfcmihQ4GDHwkQP7xMG4oOVoIxHEzuk9Nb2opK9cLbK+nU4oAROgFLzJEJuiG7BGV2hg==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8" + "@libp2p/interface": "^1.2.0", + "@libp2p/peer-id": "^4.0.10" } }, "node_modules/@libp2p/peer-id": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.8.tgz", - "integrity": "sha512-2aa3yN5RhaPe3iivuPEOdtnNbSp/EzPlmk1GAevKIpnztHi67ZmRPn+MzbllEwVPHZaRu+jEI1qugX1MLFv7aw==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.10.tgz", + "integrity": "sha512-cR5dQ5fPcxP4LLSXDgo+TSOhtElZSwRXVSSgT/GM/Vvbua5M91NzsksYfd/lg8XwTCSvTER0qmE6ZIR05vjQrA==", "dependencies": { - "@libp2p/interface": "^1.1.5", + "@libp2p/interface": "^1.2.0", "multiformats": "^13.1.0", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-id-factory": { @@ -3072,6 +3310,16 @@ "uint8arrays": "^5.0.2" } }, + "node_modules/@libp2p/record": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.0.tgz", + "integrity": "sha512-qEbLgKj1DY6K0B9LFXXm0hdCmMJdkfrxFMa5/pOe9PufaRPCAOABYmbPYQQmDNI8UrwWJbf47tj+pmDS57SLTw==", + "dependencies": { + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" + } + }, "node_modules/@libp2p/tcp": { "version": "9.0.17", "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.17.tgz", @@ -3099,22 +3347,22 @@ } }, "node_modules/@libp2p/utils": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.2.7.tgz", - "integrity": "sha512-Pxrj52DpLTGdP0IrLZJAvyZmu/mAq7QWTMrggwL6DqAM3CkzdWOwIrMysm2+RM0y60UQt05ZZSMFpKx3m8eeag==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.3.1.tgz", + "integrity": "sha512-FdGzRU50PJLYSEOmVXqqtq27yjUVXkU4QNRZzMVuXF9L/sKgSC2oXwj0Satc9fHx5tG3MCX1ZOSAmYEIl2fu+w==", "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/interface": "^1.1.5", - "@libp2p/logger": "^4.0.8", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "@libp2p/interface": "^1.2.0", + "@libp2p/logger": "^4.0.10", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "delay": "^6.0.0", "get-iterator": "^2.0.1", "is-loopback-addr": "^2.0.2", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "netmask": "^2.0.2", - "p-defer": "^4.0.0", + "p-defer": "^4.0.1", "race-event": "^1.2.0", "race-signal": "^1.0.2", "uint8arraylist": "^2.4.8" @@ -3190,9 +3438,9 @@ "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" }, "node_modules/@multiformats/dns": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.5.tgz", - "integrity": "sha512-qP42WXdmK5D0KTMervvkE9N1l+1WbReMk9UwCmvE6iPterZgtNcNO5LQVfUrl0xqajQG9wDlom+a8YwA+sa5KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", + "integrity": "sha512-nt/5UqjMPtyvkG9BQYdJ4GfLK3nMqGpFZOzf4hAmIa0sJh2LlS9YKXZ4FgwBDsaHvzZqR/rUFIywIc7pkHNNuw==", "dependencies": { "@types/dns-packet": "^5.6.5", "buffer": "^6.0.3", @@ -4524,6 +4772,11 @@ "@types/node": "*" } }, + "node_modules/@types/murmurhash3js-revisited": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.3.tgz", + "integrity": "sha512-QvlqvYtGBYIDeO8dFdY4djkRubcrc+yTJtBc7n8VZPlJDUS/00A+PssbvERM8f9bYRmcaSEHPZgZojeQj7kzAA==" + }, "node_modules/@types/node": { "version": "20.11.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", @@ -4612,11 +4865,6 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "peer": true }, - "node_modules/@vascosantos/moving-average": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vascosantos/moving-average/-/moving-average-1.1.0.tgz", - "integrity": "sha512-MVEJ4vWAPNbrGLjz7ITnHYg+YXZ6ijAqtH5/cHwSoCpbvuJ98aLXwFfPKAUfZpJMQR5uXB58UJajbY130IRF/w==" - }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4919,9 +5167,9 @@ } }, "node_modules/blockstore-core": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.0.tgz", - "integrity": "sha512-tjOJAJMPWlqahqCjn5awLJz2eZeJnrGOBA0OInBFK69/FfPZbSID2t7s5jFcBRhGaglca56BzG4t5XOV3MPxOQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.1.tgz", + "integrity": "sha512-peXfL9ZLx1cb84QALocMjhT8CsQ4JsreI/AitlN1inipSdC/G+jcYVJCqeCD5ecSTv/0LMpg8NlAPH/eBYZLjA==", "dependencies": { "@libp2p/logger": "^4.0.6", "err-code": "^3.0.1", @@ -5202,9 +5450,9 @@ "peer": true }, "node_modules/cborg": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.1.4.tgz", - "integrity": "sha512-cCw4IuvCnwjsrgrCQoreyLZDETsfw+AtFz+OFwWbWgA1yALo4nHC3Vv+zhgcVB2bor6GFRZVxrZ7Yt/3hBFAkA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.2.0.tgz", + "integrity": "sha512-q6cFW5m3KxfP/9xGI3yGLaC1l5DP6DWM9IvjiJojnIwohL5CQDl02EXViPV852mOfQo+7PJGPN01MI87vFGzyA==", "bin": { "cborg": "lib/bin.js" } @@ -6102,14 +6350,6 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/execa": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", @@ -6869,37 +7109,6 @@ "node": ">= 0.10" } }, - "node_modules/ipfs-bitswap": { - "version": "20.0.2", - "resolved": "https://registry.npmjs.org/ipfs-bitswap/-/ipfs-bitswap-20.0.2.tgz", - "integrity": "sha512-B/pPf0Dvnp/TzZ/jk7IElxCH+MwxTCe8YvyQzsQ4i8RhUh0IwyyaHPy0LSVGuJpADNjaaZQffe6DSxwmRKF7uA==", - "dependencies": { - "@libp2p/interface": "^1.0.0", - "@libp2p/logger": "^4.0.0", - "@libp2p/utils": "^5.0.0", - "@multiformats/multiaddr": "^12.1.0", - "@vascosantos/moving-average": "^1.1.0", - "any-signal": "^4.1.1", - "events": "^3.3.0", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.1.0", - "it-drain": "^3.0.5", - "it-foreach": "^2.0.2", - "it-length-prefixed": "^9.0.0", - "it-map": "^3.0.2", - "it-pipe": "^3.0.1", - "it-take": "^3.0.1", - "just-debounce-it": "^3.0.1", - "multiformats": "^13.0.0", - "progress-events": "^1.0.0", - "protons-runtime": "^5.0.0", - "timeout-abort-controller": "^3.0.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.1", - "varint": "^6.0.0", - "varint-decoder": "^1.0.0" - } - }, "node_modules/ipfs-unixfs": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.3.tgz", @@ -6958,9 +7167,9 @@ } }, "node_modules/ipns": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-9.0.0.tgz", - "integrity": "sha512-z/C/SfikvR23y0rhUOxL+zF5nG8bKv9xcdEcL8iO1IEnSBa0Y2pzke22V9YXw1r4uZ7CWukRQTC68+XHRu8y5Q==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/ipns/-/ipns-9.1.0.tgz", + "integrity": "sha512-up2o1Qx9tSSfh73k69j3/Acacua6JbffTe5xA8+/fv6ibkQyhriMPHlgae1896DwmQkJrusKgBs7EAOi3yrO2w==", "dependencies": { "@libp2p/crypto": "^4.0.0", "@libp2p/interface": "^1.1.0", @@ -7163,6 +7372,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/iso-constants": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/iso-constants/-/iso-constants-0.1.2.tgz", + "integrity": "sha512-OTCM5ZCQsHBCI4Wdu4tSxvDIkmDHd5EwJDps5mKqnQnWJSKlnwMs3EDZ4n3Fh1tmkWkDlyd2vCDbEYuPbyrUNQ==", + "hasInstallScript": true, + "engines": { + "node": ">=10" + } + }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -7410,6 +7628,37 @@ "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.4.tgz", "integrity": "sha512-RG8HDjAZlvkzz5Nav4xq6gK5zNT+Ff1UTIf+CrSJW8nIl6N1FpBH5e7clUshiCn+MmmMoSdIEpw4UaTolszxhA==" }, + "node_modules/it-tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/it-tar/-/it-tar-6.0.5.tgz", + "integrity": "sha512-GxKBamsX2EZA6jrNh6xoeKmRpbOELgYhCk1ojhN0TFSBdxwkRBqBRc3sI43vTHRpI7i2o6ptrSxK3LLKWNvudg==", + "dependencies": { + "iso-constants": "^0.1.2", + "it-reader": "^6.0.1", + "it-stream-types": "^2.0.1", + "it-to-buffer": "^4.0.5", + "p-defer": "^4.0.0", + "uint8arraylist": "^2.3.2", + "uint8arrays": "^5.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-to-browser-readablestream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/it-to-browser-readablestream/-/it-to-browser-readablestream-2.0.6.tgz", + "integrity": "sha512-VAOmkPv6S4x1m8WYR7QIBSh3qTgTd6syx146g5a4lPUjB7U7gZgqZFXRljk969UMvvROS3fJSzxnGxFmQaT2FA==" + }, + "node_modules/it-to-buffer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/it-to-buffer/-/it-to-buffer-4.0.5.tgz", + "integrity": "sha512-DoQWOBhYmVHa0ooMauJLVbZ8V8K3AsFgqBs7I+kX7f3KbFMEy0MA9w7TJo9Utd4T4H+iUScyLFwo7REA4dWreA==", + "dependencies": { + "uint8arrays": "^5.0.0" + } + }, "node_modules/it-ws": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.1.tgz", @@ -7738,11 +7987,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/just-debounce-it": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/just-debounce-it/-/just-debounce-it-3.2.0.tgz", - "integrity": "sha512-WXzwLL0745uNuedrCsCs3rpmfD6DBaf7uuVwaq98/8dafURfgQaBsSpjiPp5+CW6Vjltwy9cOGI6qE71b3T8iQ==" - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -9059,9 +9303,9 @@ } }, "node_modules/p-defer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.0.tgz", - "integrity": "sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", + "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==", "engines": { "node": ">=12" }, @@ -9981,11 +10225,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/retimer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", - "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==" - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -10771,14 +11010,6 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, - "node_modules/timeout-abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-3.0.0.tgz", - "integrity": "sha512-O3e+2B8BKrQxU2YRyEjC/2yFdb33slI22WRdUaDx6rvysfi9anloNZyR2q0l6LnePo5qH7gSM7uZtvvwZbc2yA==", - "dependencies": { - "retimer": "^3.0.0" - } - }, "node_modules/timestamp-nano": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", @@ -11121,23 +11352,6 @@ "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" }, - "node_modules/varint-decoder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/varint-decoder/-/varint-decoder-1.0.0.tgz", - "integrity": "sha512-JkOvdztASWGUAsXshCFHrB9f6AgR2Q8W08CEyJ+43b1qtFocmI8Sp1R/M0E/hDOY2FzVIqk63tOYLgDYWuJ7IQ==", - "dependencies": { - "varint": "^5.0.0" - }, - "engines": { - "node": ">=4.0.0", - "npm": ">=3.0.0" - } - }, - "node_modules/varint-decoder/node_modules/varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 9b9d5d4..5eca614 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@chainsafe/libp2p-yamux": "^6.0.2", "@helia/mfs": "^3.0.2", "@helia/unixfs": "^3.0.2", + "@helia/verified-fetch": "^1.3.12", "@libp2p/autonat": "^1.0.14", "@libp2p/bootstrap": "^10.0.17", "@libp2p/circuit-relay-v2": "^1.0.17", diff --git a/src/index.js b/src/index.js index 048fa2b..82c1c0c 100644 --- a/src/index.js +++ b/src/index.js @@ -7,9 +7,11 @@ import express from "express"; import { fileURLToPath } from "url"; import multer from "multer"; import { multiaddr } from "@multiformats/multiaddr"; +import { createVerifiedFetch } from "@helia/verified-fetch"; import { autoPeeringHandler } from "./cron.js"; import { helia } from "./helia.js"; +const verifiedFetch = await createVerifiedFetch(helia); const storage = multer.memoryStorage(); const upload = multer({ storage }); @@ -248,24 +250,20 @@ app.get("/file/:cid", async (req, res) => { ); try { - const filePromise = (async () => { - const file = ifs.cat(cid); - const chunks = []; - for await (const chunk of file) { - chunks.push(chunk); - } - return Buffer.concat(chunks); - })(); + const filePromise = await verifiedFetch(`ipfs://${cid}`, { + headers: req.headers, + }); const result = await Promise.race([filePromise, timeoutPromise]); + const data = Buffer.from(await result.arrayBuffer()); // If filePromise wins the race, send the file - const fileType = await fileTypeFromBuffer(result); + const fileType = await fileTypeFromBuffer(data); if (fileType) { res.set("Content-Type", fileType.mime); } - res.send(result); + res.send(data); } catch (error) { console.error(error); if (error.message === "Operation timed out") { From 9ed4c5feca01ddfd91a60aedaae3e680aecee723 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 20 Apr 2024 19:29:29 +0400 Subject: [PATCH 018/167] feat: typescript + todo fixes --- .babelrc | 15 + .editorconfig | 11 + .eslintignore | 1 + .eslintrc.json | 3 + .gitignore | 1 + .husky/commit-msg | 4 + .husky/pre-commit | 3 + .prettierrc.json | 8 + commitlint.config.js | 1 + config.default.json5 | 7 +- package-lock.json | 12479 ++++++++++++++++++++++++++++----------- package.json | 40 +- src/config.js | 17 - src/config.ts | 18 + src/cron.js | 36 - src/cron.ts | 36 + src/dto/peer-id.dto.ts | 3 + src/helia.js | 48 - src/helia.ts | 48 + src/index.js | 391 -- src/index.ts | 397 ++ src/libp2p.js | 54 - src/store.js | 5 - src/store.ts | 26 + src/utils.js | 44 - src/utils/logger.ts | 12 + src/utils/types.ts | 8 + src/utils/utils.ts | 66 + tsconfig.json | 24 +- 29 files changed, 9757 insertions(+), 4049 deletions(-) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .husky/commit-msg create mode 100644 .husky/pre-commit create mode 100644 .prettierrc.json create mode 100644 commitlint.config.js delete mode 100644 src/config.js create mode 100644 src/config.ts delete mode 100644 src/cron.js create mode 100644 src/cron.ts create mode 100644 src/dto/peer-id.dto.ts delete mode 100644 src/helia.js create mode 100644 src/helia.ts delete mode 100644 src/index.js create mode 100644 src/index.ts delete mode 100644 src/libp2p.js delete mode 100644 src/store.js create mode 100644 src/store.ts delete mode 100644 src/utils.js create mode 100644 src/utils/logger.ts create mode 100644 src/utils/types.ts create mode 100644 src/utils/utils.ts diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..d1f2f5a --- /dev/null +++ b/.babelrc @@ -0,0 +1,15 @@ +{ + "presets": [ + [ + "@babel/env", + { + "modules": false, + "targets": { + "node": "18" + } + } + ], + "@babel/typescript" + ], + "plugins": ["add-import-extension"] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5c08d46 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ + diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..f95bb33 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/gts/" +} diff --git a/.gitignore b/.gitignore index 7b35a16..7cbe395 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules blockstore/ datastore/ .idea +dist/ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..c160a77 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..f654052 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" +npx lint-staged diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..ecdf3e0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..7c4ff4d --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1 @@ +export default { extends: ['@commitlint/config-conventional'] } diff --git a/config.default.json5 b/config.default.json5 index da1a0d1..68948ad 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -2,15 +2,16 @@ nodes: [ { name: "ipfs1", - multiaddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWDFkrfLKMt46J5uh77SpNkxhxtSURqDnKY7Bnfr9etSQi" + multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWDFkrfLKMt46J5uh77SpNkxhxtSURqDnKY7Bnfr9etSQi" }, { name: "ipfs2", - multiaddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", + multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", }, { name: "ipfs3", - multiaddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" + multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" } ], + storeFolder: '.adm-ipfs' } diff --git a/package-lock.json b/package-lock.json index 550b8d1..40972e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,16 +38,33 @@ "json5": "^2.2.3", "libp2p": "^1.3.1", "multer": "^1.4.5-lts.1", - "multiformats": "^13.1.0" + "multiformats": "^13.1.0", + "pino": "^8.20.0", + "pino-http": "^9.0.0" }, "devDependencies": { + "@babel/cli": "^7.22.15", + "@babel/preset-typescript": "^7.22.15", + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", + "babel-plugin-add-import-extension": "^1.6.0", "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-jest": "^27.6.0", + "gts": "^5.2.0", + "husky": "^9.0.11", + "lint-staged": "^15.2.2", "nodemon": "^3.1.0", + "npm-run-all": "^4.1.5", + "pino-pretty": "^11.0.0", "prettier": "^3.2.5", + "rimraf": "^5.0.5", "ts-node": "^10.9.2", "typescript": "^5.4.3" }, @@ -55,6 +72,15 @@ "node": ">=20.0.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@achingbrain/nat-port-mapper": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/@achingbrain/nat-port-mapper/-/nat-port-mapper-1.0.13.tgz", @@ -109,11 +135,67 @@ "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, + "node_modules/@babel/cli": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.24.1.tgz", + "integrity": "sha512-HbmrtxyFUr34LwAlV9jS+sSIjUp4FpdtIMGwgufY3AsxrIfsh/HxlMTywsONAZsU0RMYbZtbZFpUCrSGs7o0EA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "commander": "^4.0.1", + "convert-source-map": "^2.0.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", - "peer": true, "dependencies": { "@babel/highlight": "^7.24.2", "picocolors": "^1.0.0" @@ -199,7 +281,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -263,7 +344,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -286,7 +366,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -337,7 +416,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -346,7 +424,6 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "peer": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" @@ -371,7 +448,6 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "peer": true, "dependencies": { "@babel/types": "^7.23.0" }, @@ -383,7 +459,6 @@ "version": "7.24.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", - "peer": true, "dependencies": { "@babel/types": "^7.24.0" }, @@ -395,7 +470,6 @@ "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -414,7 +488,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -426,7 +499,6 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -452,7 +524,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", - "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.23.0", @@ -469,7 +540,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -481,7 +551,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -493,7 +562,6 @@ "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -505,7 +573,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -514,7 +581,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -523,7 +589,6 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -560,7 +625,6 @@ "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -575,7 +639,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -587,7 +650,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -601,7 +663,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -609,14 +670,12 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "peer": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -625,7 +684,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", - "peer": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -985,7 +1043,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -1102,7 +1159,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -1505,7 +1561,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.24.0", @@ -1950,7 +2005,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz", "integrity": "sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-create-class-features-plugin": "^7.24.1", @@ -2165,7 +2219,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", @@ -2227,7 +2280,6 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", - "peer": true, "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/parser": "^7.24.0", @@ -2262,7 +2314,6 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", - "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -2361,1510 +2412,4244 @@ "@chainsafe/is-ip": "^2.0.1" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@commitlint/cli": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.2.2.tgz", + "integrity": "sha512-P8cbOHfg2PQRzfICLSrzUVOCVMqjEZ8Hlth6mtJ4yOEjT47Q5PbIGymgX3rLVylNw+3IAT2Djn9IJ2wHbXFzBg==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@commitlint/format": "^19.0.3", + "@commitlint/lint": "^19.2.2", + "@commitlint/load": "^19.2.0", + "@commitlint/read": "^19.2.1", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" }, "engines": { - "node": ">=12" + "node": ">=v18" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "peer": true - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "peer": true, + "node_modules/@commitlint/cli/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, "dependencies": { - "@hapi/hoek": "^9.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@helia/bitswap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.0.0.tgz", - "integrity": "sha512-ZcQNBp2TyU6OVvt8J3GvdQ3f5er8GrjEQrIKLl5jHSoXtbyNaCv9gBzLn85aMMXWBIUQcaom+5s1LIaJ5bqbjw==", - "dependencies": { - "@helia/interface": "^4.2.0", - "@helia/utils": "^0.2.0", - "@libp2p/interface": "^1.1.2", - "@libp2p/logger": "^4.0.5", - "@libp2p/peer-collections": "^5.1.6", - "@libp2p/utils": "^5.2.3", - "@multiformats/multiaddr": "^12.1.14", - "any-signal": "^4.1.1", - "debug": "^4.3.4", - "interface-blockstore": "^5.2.9", - "interface-store": "^5.1.7", - "it-all": "^3.0.4", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-length-prefixed": "^9.0.0", - "it-length-prefixed-stream": "^1.1.6", - "it-map": "^3.0.5", - "it-pipe": "^3.0.1", - "it-take": "^3.0.1", - "multiformats": "^13.0.1", - "p-defer": "^4.0.0", - "progress-events": "^1.0.0", - "protons-runtime": "^5.0.0", - "race-event": "^1.2.0", - "uint8-varint": "^2.0.3", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.1" + "node_modules/@commitlint/cli/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@helia/bitswap/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", - "dependencies": { - "@helia/interface": "^4.2.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.1", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "murmurhash3js-revisited": "^3.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "node_modules/@commitlint/cli/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" } }, - "node_modules/@helia/block-brokers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.1.0.tgz", - "integrity": "sha512-Y6iVROn1vH6dlQnEUerc7ULASAq2fY1xlrpBg93HO3JyCc0C1usCzsrtEcOMGd852dDM1SY7QN1wKPCVsq2w/g==", + "node_modules/@commitlint/cli/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.2.2.tgz", + "integrity": "sha512-mLXjsxUVLYEGgzbxbxicGPggDuyWNkf25Ht23owXIH+zV2pv1eJuzLK3t1gDY5Gp6pxdE60jZnWUY5cvgL3ufw==", + "dev": true, "dependencies": { - "@helia/bitswap": "^1.0.0", - "@helia/interface": "^4.2.0", - "@helia/utils": "^0.2.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/utils": "^5.2.6", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", - "@multiformats/multiaddr-to-uri": "^10.0.1", - "interface-blockstore": "^5.2.10", - "interface-store": "^5.1.8", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0" + "@commitlint/types": "^19.0.3", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/block-brokers/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "node_modules/@commitlint/config-validator": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.0.3.tgz", + "integrity": "sha512-2D3r4PKjoo59zBc2auodrSCaUnCSALCx54yveOFwwP/i2kfEAQrygwOleFWswLqK0UL/F9r07MFi5ev2ohyM4Q==", + "dev": true, "dependencies": { - "@helia/interface": "^4.2.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.1", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "murmurhash3js-revisited": "^3.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "@commitlint/types": "^19.0.3", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/car": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@helia/car/-/car-3.1.3.tgz", - "integrity": "sha512-y+i/eyaUwi0XzfFOPw4VlGUH/DfonD38CoHfjqxWKW8dKxReKdVMkca6hjoDmt31buVSIMeDoAw17iwh1IjFvg==", + "node_modules/@commitlint/config-validator/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, "dependencies": { - "@helia/interface": "^4.2.0", - "@ipld/car": "^5.3.0", - "@libp2p/interfaces": "^3.3.2", - "interface-blockstore": "^5.2.10", - "it-drain": "^3.0.5", - "it-map": "^3.0.5", - "multiformats": "^13.1.0", - "p-defer": "^4.0.0", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@helia/delegated-routing-v1-http-api-client": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.0.tgz", - "integrity": "sha512-NQRG5djXKI0GcFx35JMKSQ92RscGOhER6ti66ExjreAffaO2/9vk2ZbAbfCoZnjyTnx66HWAgAyB4GDDTAeFLA==", + "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@commitlint/ensure": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.0.3.tgz", + "integrity": "sha512-SZEpa/VvBLoT+EFZVb91YWbmaZ/9rPH3ESrINOl0HD2kMYsjvl0tF7nMHh0EpTcv4+gTtZBAe1y/SS6/OhfZzQ==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.1", - "@libp2p/logger": "^4.0.4", - "@libp2p/peer-id": "^4.0.4", - "@multiformats/multiaddr": "^12.1.3", - "any-signal": "^4.1.1", - "browser-readablestream-to-it": "^2.0.3", - "ipns": "^9.0.0", - "it-first": "^3.0.3", - "it-map": "^3.0.4", - "it-ndjson": "^1.0.4", - "multiformats": "^13.0.0", - "p-defer": "^4.0.0", - "p-queue": "^8.0.1", - "uint8arrays": "^5.0.1" + "@commitlint/types": "^19.0.3", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@helia/http/-/http-1.0.4.tgz", - "integrity": "sha512-lXKX14xbSi2pDA7UOu3U4S1rNvPTkpLJtQpZS/amvo0ooAtbni+fA2uHDx/lTO7lNCfJMD9YLP1hikHz6D+nAQ==", + "node_modules/@commitlint/execute-rule": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.0.0.tgz", + "integrity": "sha512-mtsdpY1qyWgAO/iOK0L6gSGeR7GFcdW7tIjcNFxcWkfLDF5qVbPHKuGATFqRMsxcO8OUKNj0+3WOHB7EHm4Jdw==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.0.3.tgz", + "integrity": "sha512-QjjyGyoiVWzx1f5xOteKHNLFyhyweVifMgopozSgx1fGNrGV8+wp7k6n1t6StHdJ6maQJ+UUtO2TcEiBFRyR6Q==", + "dev": true, "dependencies": { - "@helia/block-brokers": "^2.1.0", - "@helia/interface": "^4.2.0", - "@helia/routers": "^1.0.3", - "@helia/utils": "^0.2.0", - "blockstore-core": "^4.4.0", - "datastore-core": "^9.2.9" + "@commitlint/types": "^19.0.3", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/http/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "node_modules/@commitlint/format/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.2.2.tgz", + "integrity": "sha512-eNX54oXMVxncORywF4ZPFtJoBm3Tvp111tg1xf4zWXGfhBPKpfKG6R+G3G4v5CPlRROXpAOpQ3HMhA9n1Tck1g==", + "dev": true, "dependencies": { - "@helia/interface": "^4.2.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.1", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "murmurhash3js-revisited": "^3.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "@commitlint/types": "^19.0.3", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/interface": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.2.0.tgz", - "integrity": "sha512-8F1rZAskqxcIpXEcJOa050fsdYQLDbf6WRgUyPve+gPIZVTvwgfuGuOlZxxrXAFtH7CujRiP3yMAN3V6iTbuRA==", + "node_modules/@commitlint/lint": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.2.2.tgz", + "integrity": "sha512-xrzMmz4JqwGyKQKTpFzlN0dx0TAiT7Ran1fqEBgEmEj+PU98crOFtysJgY+QdeSagx6EDRigQIXJVnfrI0ratA==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.4", - "@multiformats/dns": "^1.0.1", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0" + "@commitlint/is-ignored": "^19.2.2", + "@commitlint/parse": "^19.0.3", + "@commitlint/rules": "^19.0.3", + "@commitlint/types": "^19.0.3" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/ipns": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@helia/ipns/-/ipns-7.2.1.tgz", - "integrity": "sha512-HHfZr24H3iGFNHVvbOL+gwVXkmvo7umJ7US4fAGNbgXytXSEY/GmIqF+fDA0xBtPvFSrccv18XoKH7Dz+yNrfg==", + "node_modules/@commitlint/load": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.2.0.tgz", + "integrity": "sha512-XvxxLJTKqZojCxaBQ7u92qQLFMMZc4+p9qrIq/9kJDy8DOrEa7P1yx7Tjdc2u2JxIalqT4KOGraVgCE7eCYJyQ==", + "dev": true, "dependencies": { - "@helia/interface": "^4.2.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/kad-dht": "^12.0.8", - "@libp2p/logger": "^4.0.7", - "@libp2p/peer-id": "^4.0.7", - "@multiformats/dns": "^1.0.1", - "interface-datastore": "^8.2.11", - "ipns": "^9.1.0", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" + "@commitlint/config-validator": "^19.0.3", + "@commitlint/execute-rule": "^19.0.0", + "@commitlint/resolve-extends": "^19.1.0", + "@commitlint/types": "^19.0.3", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^5.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@helia/mfs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.2.tgz", - "integrity": "sha512-vFhU2AMBGcw54FHoJPKG74lBkFGheJFJA69Tl0n531QKdaZwEgnjZB97zhx+Hxyx2S3a92gEdQiCTNRYAkOJ6g==", + "node_modules/@commitlint/load/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@commitlint/load/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/load/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, "dependencies": { - "@helia/unixfs": "^3.0.2", - "@libp2p/interfaces": "^3.3.2", - "@libp2p/logger": "^4.0.7", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "ipfs-unixfs": "^11.1.3", - "ipfs-unixfs-exporter": "^13.5.0", - "ipfs-unixfs-importer": "^15.2.4", - "multiformats": "^13.1.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@helia/routers": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.3.tgz", - "integrity": "sha512-bX6I5ir2YCK+LW/G0YrHhqJW0SjG4DDnkxLOk2dzmZNy2OCB+Ogt0+W2keyz5nHGfTuwcBBn4qH5ouN0vTZ74w==", + "node_modules/@commitlint/load/node_modules/cosmiconfig-typescript-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz", + "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==", + "dev": true, "dependencies": { - "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.2.0", - "@libp2p/interface": "^1.1.4", - "ipns": "^9.0.0", - "it-first": "^3.0.4", - "it-map": "^3.0.5", - "multiformats": "^13.1.0", - "uint8arrays": "^5.0.2" + "jiti": "^1.19.1" + }, + "engines": { + "node": ">=v16" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=8.2", + "typescript": ">=4" } }, - "node_modules/@helia/unixfs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.2.tgz", - "integrity": "sha512-zGOCFPJn8aAxNCImo67PReV2WJIdmjh5pPBDp2MTyeMoa1G5VuhfW6GnSYzQQNagbY/LYdSxDIE+a33rgjGtdw==", + "node_modules/@commitlint/load/node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { - "@helia/interface": "^4.1.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@multiformats/murmur3": "^2.1.8", - "hamt-sharding": "^3.0.6", - "interface-blockstore": "^5.2.10", - "ipfs-unixfs": "^11.1.3", - "ipfs-unixfs-exporter": "^13.5.0", - "ipfs-unixfs-importer": "^15.2.4", - "it-all": "^3.0.4", - "it-glob": "^2.0.6", - "it-last": "^3.0.4", - "it-pipe": "^3.0.1", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "sparse-array": "^1.3.2", - "uint8arrays": "^5.0.2" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@helia/utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.1.0.tgz", - "integrity": "sha512-uRUDzGpeWbPANFlq3J3x14tE89enVEBoaRXM1CKpE25vXUOij9qoYjE8GlXDNCQgkazAzrVswdjcRn6Eza+bIg==", + "node_modules/@commitlint/load/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { - "@helia/interface": "^4.1.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/peer-collections": "^5.1.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.0", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@helia/verified-fetch": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.12.tgz", - "integrity": "sha512-eLDGvrDLDs09vZk5PxYmPTB7WlJ88ff4GwF7gRbH5zaqZVVCUP3S6lkEvmNM+9MYPfElfR/wUOmN2GJIVqDQ/A==", + "node_modules/@commitlint/load/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { - "@helia/block-brokers": "^2.1.0", - "@helia/car": "^3.1.3", - "@helia/http": "^1.0.4", - "@helia/interface": "^4.2.0", - "@helia/ipns": "^7.2.1", - "@helia/routers": "^1.0.3", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/interface": "^1.1.6", - "@libp2p/kad-dht": "^12.0.11", - "@libp2p/peer-id": "^4.0.9", - "@multiformats/dns": "^1.0.6", - "cborg": "^4.2.0", - "hashlru": "^2.3.0", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "ipfs-unixfs-exporter": "^13.5.0", - "it-map": "^3.0.5", - "it-pipe": "^3.0.1", - "it-tar": "^6.0.5", - "it-to-browser-readablestream": "^2.0.6", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.3" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ipld/car": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@ipld/car/-/car-5.3.0.tgz", - "integrity": "sha512-OB8LVvJeVAFFGluNIkZeDZ/aGeoekFKsuIvNT9I5sJIb5WekQuW5+lekjQ7Z7mZ7DBKuke/kI4jBT1j0/akU1w==", - "dependencies": { - "@ipld/dag-cbor": "^9.0.7", - "cborg": "^4.0.5", - "multiformats": "^13.0.0", - "varint": "^6.0.0" - }, + "node_modules/@commitlint/load/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=4" } }, - "node_modules/@ipld/dag-cbor": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz", - "integrity": "sha512-N14oMy0q4gM6OuZkIpisKe0JBSjf1Jb39VI+7jMLiWX9124u1Z3Fdj/Tag1NA0cVxxqWDh0CqsjcVfOKtelPDA==", + "node_modules/@commitlint/message": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.0.0.tgz", + "integrity": "sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.0.3.tgz", + "integrity": "sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==", + "dev": true, "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" + "@commitlint/types": "^19.0.3", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=v18" } }, - "node_modules/@ipld/dag-json": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.0.tgz", - "integrity": "sha512-O9YLUrl3d3WbVz7v1WkajFkyfOLEe2Fep+wor4fgVe0ywxzrivrj437NiPcVyB+2EDdFn/Q7tCHFf8YVhDf8ZA==", + "node_modules/@commitlint/read": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.2.1.tgz", + "integrity": "sha512-qETc4+PL0EUv7Q36lJbPG+NJiBOGg7SSC7B5BsPWOmei+Dyif80ErfWQ0qXoW9oCh7GTpTNRoaVhiI8RbhuaNw==", + "dev": true, "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" + "@commitlint/top-level": "^19.0.0", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=v18" } }, - "node_modules/@ipld/dag-pb": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.0.tgz", - "integrity": "sha512-LJU451Drqs5zjFm7jI4Hs3kHlilOqkjcSfPiQgVsZnWaYb2C7YdfhnclrVn/X+ucKejlU9BL3+gXFCZUXkMuCg==", + "node_modules/@commitlint/read/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, "dependencies": { - "multiformats": "^13.1.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "peer": true, + "node_modules/@commitlint/read/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3" - }, + "node_modules/@commitlint/read/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=16.17.0" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "peer": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, + "node_modules/@commitlint/read/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "peer": true, + "node_modules/@commitlint/resolve-extends": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.1.0.tgz", + "integrity": "sha512-z2riI+8G3CET5CPgXJPlzftH+RiWYLMYv4C9tSLdLXdr6pBNimSKukYP9MS27ejmscqCTVA4almdLh0ODD2KYg==", + "dev": true, "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@commitlint/config-validator": "^19.0.3", + "@commitlint/types": "^19.0.3", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=v18" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, + "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "peer": true, + "node_modules/@commitlint/rules": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.0.3.tgz", + "integrity": "sha512-TspKb9VB6svklxNCKKwxhELn7qhtY1rFF8ls58DcFd0F97XoG07xugPjjbVnLqmMkRjZDbDIwBKt9bddOfLaPw==", + "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@commitlint/ensure": "^19.0.3", + "@commitlint/message": "^19.0.0", + "@commitlint/to-lines": "^19.0.0", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=v18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "peer": true, + "node_modules/@commitlint/rules/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "node_modules/@commitlint/rules/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@commitlint/rules/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, "engines": { - "node": ">=6.0.0" + "node": ">=16.17.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "peer": true, + "node_modules/@commitlint/rules/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "engines": { - "node": ">=6.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "node_modules/@commitlint/to-lines": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.0.0.tgz", + "integrity": "sha512-vkxWo+VQU5wFhiP9Ub9Sre0FYe019JxFikrALVoD5UGa8/t3yOJEpEhxC5xKiENKKhUkTpEItMTRAjHw2SCpZw==", + "dev": true, + "engines": { + "node": ">=v18" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "peer": true, + "node_modules/@commitlint/top-level": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.0.0.tgz", + "integrity": "sha512-KKjShd6u1aMGNkCkaX4aG1jOGdn7f8ZI8TR1VEuNqUOjWTOdcDSsmglinglJ18JTjuBX5I1PtjrhQCRcixRVFQ==", + "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@commitlint/top-level/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@libp2p/autonat": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.14.tgz", - "integrity": "sha512-yd6Qlltzt7/kkec9wOtgUJzzDXXreVXyKVR1E5zms58sQmVavc3PWEJE1cFxK0GYMF2QjjzJY13/z9WLeQBzIA==", + "node_modules/@commitlint/top-level/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", - "it-first": "^3.0.4", - "it-length-prefixed": "^9.0.4", - "it-map": "^3.0.5", - "it-parallel": "^3.0.6", - "it-pipe": "^3.0.1", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8" + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/bootstrap": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.17.tgz", - "integrity": "sha512-NO+YTigMn62YMbPAz4jK8sOC6tY2K5Q7wnRI2lmHzP9albIX+Nf4/Tyj7tu8Owi5CP+v07eKXWejmrXm+MkJPg==", + "node_modules/@commitlint/top-level/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14" + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/circuit-relay-v2": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.17.tgz", - "integrity": "sha512-FSTqidxco90MjMdKTGhFZoGxf/EaZMuwRWyEq5oGw1aG8lBKrH2fwP1fTRfwAQVhQ8Pxd9B9/SfBU6QSHA5YKQ==", + "node_modules/@commitlint/top-level/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@libp2p/utils": "^5.2.7", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", - "any-signal": "^4.1.1", - "it-protobuf-stream": "^1.1.2", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "p-defer": "^4.0.0", - "p-retry": "^6.2.0", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/crypto": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.6.tgz", - "integrity": "sha512-AJ4i3DHOTlY961O26M3k1IjmU4rUd5WgeK4t9IRzFfLIbD6uwA+cevJMG2qr0UHJfbYdGKKQ2Po1wqZONoIA9Q==", - "dependencies": { - "@libp2p/interface": "^1.2.0", - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.4.0", - "asn1js": "^3.0.5", - "multiformats": "^13.1.0", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "node_modules/@commitlint/top-level/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/@libp2p/dcutr": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.0.14.tgz", - "integrity": "sha512-0x5Sn21KVe1Fz0YeIudPIg2GB+XDVwr5IHblXVVKq5PNHjUvqarO4XM5GAaLyK0tZqwFkkgDNE4BjmHWZkejng==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", - "delay": "^6.0.0", - "it-protobuf-stream": "^1.1.2", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8" + "node_modules/@commitlint/top-level/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/identify": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-1.0.16.tgz", - "integrity": "sha512-uDXae1DGok+rFF5T8yixf3lkadr5U3O+x72BjgZaIeduWLCbPDczlKMNJuFJF3+ys45qcO8WuySuZqyK2aq8kw==", + "node_modules/@commitlint/types": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.0.3.tgz", + "integrity": "sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", - "it-protobuf-stream": "^1.1.2", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2", - "wherearewe": "^2.0.1" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" } }, - "node_modules/@libp2p/interface": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.2.0.tgz", - "integrity": "sha512-ImnGNl3El/AukgaojACT8i9SNW1FOsrThcQU/qA3w5tEBR5p84Uwgzl/nxa4X5vGinItUJ9jLEJmtkQJENoiGQ==", - "dependencies": { - "@multiformats/multiaddr": "^12.2.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" + "node_modules/@commitlint/types/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@libp2p/interface-internal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.1.0.tgz", - "integrity": "sha512-B6Cu3Mhp5kY2Z1cU0soCR4ZtjZtE4FuWE0qdJNauOpcQe9HOjPF8SanFmeEIZ0FKSOo0onQdQi2YdNUTtOVyvQ==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.2.0", - "@libp2p/peer-collections": "^5.1.10", - "@multiformats/multiaddr": "^12.2.1", - "uint8arraylist": "^2.4.8" + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@libp2p/interfaces": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", - "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@libp2p/kad-dht": { - "version": "12.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.13.tgz", - "integrity": "sha512-as+pIHcrjVTXkf6kyULtnJg/RRvrbVjyycPD8HtqD5SKB0PDiYvJO0LY0oVAHFFi5oGMCqSG5777VQfC/tmRhQ==", - "dependencies": { - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.2.0", - "@libp2p/interface-internal": "^1.1.0", - "@libp2p/peer-collections": "^5.1.10", - "@libp2p/peer-id": "^4.0.10", - "@libp2p/record": "^4.0.0", - "@libp2p/utils": "^5.3.1", - "@multiformats/multiaddr": "^12.2.1", - "any-signal": "^4.1.1", - "hashlru": "^2.3.0", - "interface-datastore": "^8.2.11", - "it-drain": "^3.0.5", - "it-length": "^3.0.4", - "it-length-prefixed": "^9.0.4", - "it-map": "^3.0.5", - "it-merge": "^3.0.3", - "it-parallel": "^3.0.6", - "it-pipe": "^3.0.1", - "it-protobuf-stream": "^1.1.2", - "it-pushable": "^3.2.3", - "it-take": "^3.0.4", - "multiformats": "^13.1.0", - "p-defer": "^4.0.1", - "p-event": "^6.0.1", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0", - "protons-runtime": "^5.4.0", - "race-signal": "^1.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@libp2p/keychain": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.0.10.tgz", - "integrity": "sha512-yI7Z3H4hNky7uRyJUHE/4lcc42jboFlGnveW+nrUEm+3/IrCt7T+Ci74Ighhh41V90iKJyFAfQeR+HAnwpT9GA==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "interface-datastore": "^8.2.11", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.0.2" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@libp2p/logger": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.10.tgz", - "integrity": "sha512-JiRfJHO/D9Jlh2rJ6STnONoeQevBAdAZaGUxrtvBf4RFfucldSFEMOtdkFO8xFGuiA90Q2kj4BE2douG6fB3Lw==", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.2.0", - "@multiformats/multiaddr": "^12.2.1", - "debug": "^4.3.4", - "interface-datastore": "^8.2.11", - "multiformats": "^13.1.0" + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/mdns": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.0.17.tgz", - "integrity": "sha512-hflI9xnA1vWtwiZnl9Y4HXGp0QoDIt38kFdYqlgvcRIAJuH4Gw0dD/pFvzYUT3/BPAM1sZb9xU8sK1dwmPwWzQ==", + "node_modules/@eslint/eslintrc/node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", - "@types/multicast-dns": "^7.2.4", - "dns-packet": "^5.6.1", - "multicast-dns": "^7.2.5" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/mplex": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.0.17.tgz", - "integrity": "sha512-/+AW5J5Zh/M2vEE7nDU00zaLyCtbmBstcUnps+MBKEKoZ4nQaGtAox2dq9dvCOSWrFl3zhHDrLQy1Kt2xIfxMA==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@libp2p/multistream-select": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.5.tgz", - "integrity": "sha512-fQSF3bjlaSqneD50/cj9Kbl2YjFVy45kHxtjnMGb04cnyL8CefJ89dY2aF3bWCbbEqUxzc4yCCNi48y+Pfo34w==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "it-length-prefixed": "^9.0.4", - "it-length-prefixed-stream": "^1.1.6", - "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0", - "race-signal": "^1.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "node_modules/@eslint/eslintrc/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/@libp2p/peer-collections": { - "version": "5.1.10", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.10.tgz", - "integrity": "sha512-Edr4FBzCgE7FRgc0wfYfcmihQ4GDHwkQP7xMG4oOVoIxHEzuk9Nb2opK9cLbK+nU4oAROgFLzJEJuiG7BGV2hg==", - "dependencies": { - "@libp2p/interface": "^1.2.0", - "@libp2p/peer-id": "^4.0.10" + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/peer-id": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.10.tgz", - "integrity": "sha512-cR5dQ5fPcxP4LLSXDgo+TSOhtElZSwRXVSSgT/GM/Vvbua5M91NzsksYfd/lg8XwTCSvTER0qmE6ZIR05vjQrA==", - "dependencies": { - "@libp2p/interface": "^1.2.0", - "multiformats": "^13.1.0", - "uint8arrays": "^5.0.3" + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/peer-id-factory": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.0.8.tgz", - "integrity": "sha512-Kk9TqhSY0dPr2RmLUSJj+V5j4vgxrB25G5bf+nNfY79s/YNh0uBIq2ZmeBezXnG4q5egH1e9vbrgHdl7PUllUA==", + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "peer": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "peer": true, "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "@hapi/hoek": "^9.0.0" } }, - "node_modules/@libp2p/peer-record": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.11.tgz", - "integrity": "sha512-jAi/TX1NSdNPsO0TmJd5fEKQfjdkxxBPmlwofBx2dPqtud/Aac4wa6dvIFQKqGkh8ZeC8FAQbv8jE/a8jgg3MQ==", + "node_modules/@helia/bitswap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.0.0.tgz", + "integrity": "sha512-ZcQNBp2TyU6OVvt8J3GvdQ3f5er8GrjEQrIKLl5jHSoXtbyNaCv9gBzLn85aMMXWBIUQcaom+5s1LIaJ5bqbjw==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", + "@helia/interface": "^4.2.0", + "@helia/utils": "^0.2.0", + "@libp2p/interface": "^1.1.2", + "@libp2p/logger": "^4.0.5", + "@libp2p/peer-collections": "^5.1.6", + "@libp2p/utils": "^5.2.3", "@multiformats/multiaddr": "^12.1.14", - "protons-runtime": "^5.4.0", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "any-signal": "^4.1.1", + "debug": "^4.3.4", + "interface-blockstore": "^5.2.9", + "interface-store": "^5.1.7", + "it-all": "^3.0.4", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-length-prefixed": "^9.0.0", + "it-length-prefixed-stream": "^1.1.6", + "it-map": "^3.0.5", + "it-pipe": "^3.0.1", + "it-take": "^3.0.1", + "multiformats": "^13.0.1", + "p-defer": "^4.0.0", + "progress-events": "^1.0.0", + "protons-runtime": "^5.0.0", + "race-event": "^1.2.0", + "uint8-varint": "^2.0.3", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^5.0.1" } }, - "node_modules/@libp2p/peer-store": { - "version": "10.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.12.tgz", - "integrity": "sha512-lX8wdgX0NNxIcnutJ3hxfhyt+QXl0fVxYY3O1tKtEPoyilbxBA66qVVdU3nVmETkku7TcUuSOEfwevwmQC7qhw==", + "node_modules/@helia/bitswap/node_modules/@helia/utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@multiformats/multiaddr": "^12.1.14", + "@helia/interface": "^4.2.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", "interface-datastore": "^8.2.11", - "it-all": "^3.0.4", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", "mortice": "^3.0.4", "multiformats": "^13.1.0", - "protons-runtime": "^5.4.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.0.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.2" } }, - "node_modules/@libp2p/ping": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.0.13.tgz", - "integrity": "sha512-x3q0IOYKmLzwKbsXKL5fH9EAf/M4/LoGctCbffpr+dxn3cYZOJIMO2ZgJkEBW7DslNTskufgeTsh+wgG0EsWrw==", + "node_modules/@helia/block-brokers": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.1.0.tgz", + "integrity": "sha512-Y6iVROn1vH6dlQnEUerc7ULASAq2fY1xlrpBg93HO3JyCc0C1usCzsrtEcOMGd852dDM1SY7QN1wKPCVsq2w/g==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@multiformats/multiaddr": "^12.1.14", - "it-first": "^3.0.4", - "it-pipe": "^3.0.1", - "uint8arrays": "^5.0.2" + "@helia/bitswap": "^1.0.0", + "@helia/interface": "^4.2.0", + "@helia/utils": "^0.2.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/utils": "^5.2.6", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "@multiformats/multiaddr-to-uri": "^10.0.1", + "interface-blockstore": "^5.2.10", + "interface-store": "^5.1.8", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0" } }, - "node_modules/@libp2p/pubsub": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-9.0.12.tgz", - "integrity": "sha512-PTrdRXoluKFhnhQVMwk5iDuwrEvOTqM/fjcOE5YZ5mJCCu5NuHGmFPy5hkVeu19IC8vM9LCTCdNLuA71/GUEZg==", + "node_modules/@helia/block-brokers/node_modules/@helia/utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "it-length-prefixed": "^9.0.4", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", + "@helia/interface": "^4.2.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", "multiformats": "^13.1.0", - "p-queue": "^8.0.1", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.0.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.2" } }, - "node_modules/@libp2p/record": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.0.tgz", - "integrity": "sha512-qEbLgKj1DY6K0B9LFXXm0hdCmMJdkfrxFMa5/pOe9PufaRPCAOABYmbPYQQmDNI8UrwWJbf47tj+pmDS57SLTw==", + "node_modules/@helia/car": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@helia/car/-/car-3.1.3.tgz", + "integrity": "sha512-y+i/eyaUwi0XzfFOPw4VlGUH/DfonD38CoHfjqxWKW8dKxReKdVMkca6hjoDmt31buVSIMeDoAw17iwh1IjFvg==", "dependencies": { - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "@helia/interface": "^4.2.0", + "@ipld/car": "^5.3.0", + "@libp2p/interfaces": "^3.3.2", + "interface-blockstore": "^5.2.10", + "it-drain": "^3.0.5", + "it-map": "^3.0.5", + "multiformats": "^13.1.0", + "p-defer": "^4.0.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0" } }, - "node_modules/@libp2p/tcp": { - "version": "9.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.17.tgz", - "integrity": "sha512-joGy4Zs/2jQ1pe8fD7yr9lLqJ2ZXxZN6dZhhzknf3Opq1YRQ3yyz2eu5G1LNdN0jqyO259UO+TnLtEkruel/QQ==", + "node_modules/@helia/delegated-routing-v1-http-api-client": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.0.tgz", + "integrity": "sha512-NQRG5djXKI0GcFx35JMKSQ92RscGOhER6ti66ExjreAffaO2/9vk2ZbAbfCoZnjyTnx66HWAgAyB4GDDTAeFLA==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", - "@types/sinon": "^17.0.3", - "stream-to-it": "^0.2.4" + "@libp2p/interface": "^1.1.1", + "@libp2p/logger": "^4.0.4", + "@libp2p/peer-id": "^4.0.4", + "@multiformats/multiaddr": "^12.1.3", + "any-signal": "^4.1.1", + "browser-readablestream-to-it": "^2.0.3", + "ipns": "^9.0.0", + "it-first": "^3.0.3", + "it-map": "^3.0.4", + "it-ndjson": "^1.0.4", + "multiformats": "^13.0.0", + "p-defer": "^4.0.0", + "p-queue": "^8.0.1", + "uint8arrays": "^5.0.1" } }, - "node_modules/@libp2p/upnp-nat": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.15.tgz", - "integrity": "sha512-F9NStswMhpKNfzFf91mHxWCw+yKTZfbeadgb5wW6sFh6gQNpMWHdvUDoC8hg2qL8snHcMn5OqGNgE+KvNvSKAw==", + "node_modules/@helia/http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@helia/http/-/http-1.0.4.tgz", + "integrity": "sha512-lXKX14xbSi2pDA7UOu3U4S1rNvPTkpLJtQpZS/amvo0ooAtbni+fA2uHDx/lTO7lNCfJMD9YLP1hikHz6D+nAQ==", "dependencies": { - "@achingbrain/nat-port-mapper": "^1.0.13", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", - "wherearewe": "^2.0.1" + "@helia/block-brokers": "^2.1.0", + "@helia/interface": "^4.2.0", + "@helia/routers": "^1.0.3", + "@helia/utils": "^0.2.0", + "blockstore-core": "^4.4.0", + "datastore-core": "^9.2.9" } }, - "node_modules/@libp2p/utils": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.3.1.tgz", - "integrity": "sha512-FdGzRU50PJLYSEOmVXqqtq27yjUVXkU4QNRZzMVuXF9L/sKgSC2oXwj0Satc9fHx5tG3MCX1ZOSAmYEIl2fu+w==", + "node_modules/@helia/http/node_modules/@helia/utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", "dependencies": { - "@chainsafe/is-ip": "^2.0.2", - "@libp2p/interface": "^1.2.0", - "@libp2p/logger": "^4.0.10", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", - "delay": "^6.0.0", - "get-iterator": "^2.0.1", - "is-loopback-addr": "^2.0.2", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "netmask": "^2.0.2", + "@helia/interface": "^4.2.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", "p-defer": "^4.0.1", - "race-event": "^1.2.0", - "race-signal": "^1.0.2", - "uint8arraylist": "^2.4.8" + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" } }, - "node_modules/@libp2p/webrtc": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.23.tgz", - "integrity": "sha512-/HI60R9KLivtFCB2vIKDcwyppnKpB2T7BYBej5CCO6KQQM2zEA9/jd/dZ94hs7DUQZCOynJHcUjRKG/rIDyncQ==", + "node_modules/@helia/interface": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.2.0.tgz", + "integrity": "sha512-8F1rZAskqxcIpXEcJOa050fsdYQLDbf6WRgUyPve+gPIZVTvwgfuGuOlZxxrXAFtH7CujRiP3yMAN3V6iTbuRA==", "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", - "detect-browser": "^5.3.0", - "it-length-prefixed": "^9.0.4", - "it-protobuf-stream": "^1.1.2", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", + "@libp2p/interface": "^1.1.4", + "@multiformats/dns": "^1.0.1", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", "multiformats": "^13.1.0", - "multihashes": "^4.0.3", - "node-datachannel": "^0.5.5", - "p-defer": "^4.0.0", - "p-event": "^6.0.0", - "p-timeout": "^6.1.2", - "protons-runtime": "^5.4.0", - "race-signal": "^1.0.2", - "react-native-webrtc": "^118.0.1", - "uint8arraylist": "^2.4.8", + "progress-events": "^1.0.0" + } + }, + "node_modules/@helia/ipns": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@helia/ipns/-/ipns-7.2.1.tgz", + "integrity": "sha512-HHfZr24H3iGFNHVvbOL+gwVXkmvo7umJ7US4fAGNbgXytXSEY/GmIqF+fDA0xBtPvFSrccv18XoKH7Dz+yNrfg==", + "dependencies": { + "@helia/interface": "^4.2.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/kad-dht": "^12.0.8", + "@libp2p/logger": "^4.0.7", + "@libp2p/peer-id": "^4.0.7", + "@multiformats/dns": "^1.0.1", + "interface-datastore": "^8.2.11", + "ipns": "^9.1.0", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", "uint8arrays": "^5.0.2" } }, - "node_modules/@libp2p/websockets": { - "version": "8.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.0.17.tgz", - "integrity": "sha512-b8CebwVYSL+tvoh3yvDzwbY7KgLwdkPb22aPjc7ogB/+XHu/R4cTXSPRHAuzNXNdM4XN5NFtwjyHVBUXSqYRBQ==", + "node_modules/@helia/mfs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.2.tgz", + "integrity": "sha512-vFhU2AMBGcw54FHoJPKG74lBkFGheJFJA69Tl0n531QKdaZwEgnjZB97zhx+Hxyx2S3a92gEdQiCTNRYAkOJ6g==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-to-uri": "^10.0.1", - "@types/ws": "^8.5.10", - "it-ws": "^6.1.1", - "p-defer": "^4.0.0", - "wherearewe": "^2.0.1", - "ws": "^8.16.0" + "@helia/unixfs": "^3.0.2", + "@libp2p/interfaces": "^3.3.2", + "@libp2p/logger": "^4.0.7", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "ipfs-unixfs": "^11.1.3", + "ipfs-unixfs-exporter": "^13.5.0", + "ipfs-unixfs-importer": "^15.2.4", + "multiformats": "^13.1.0" + } + }, + "node_modules/@helia/routers": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.3.tgz", + "integrity": "sha512-bX6I5ir2YCK+LW/G0YrHhqJW0SjG4DDnkxLOk2dzmZNy2OCB+Ogt0+W2keyz5nHGfTuwcBBn4qH5ouN0vTZ74w==", + "dependencies": { + "@helia/delegated-routing-v1-http-api-client": "^3.0.0", + "@helia/interface": "^4.2.0", + "@libp2p/interface": "^1.1.4", + "ipns": "^9.0.0", + "it-first": "^3.0.4", + "it-map": "^3.0.5", + "multiformats": "^13.1.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@helia/unixfs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.2.tgz", + "integrity": "sha512-zGOCFPJn8aAxNCImo67PReV2WJIdmjh5pPBDp2MTyeMoa1G5VuhfW6GnSYzQQNagbY/LYdSxDIE+a33rgjGtdw==", + "dependencies": { + "@helia/interface": "^4.1.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@multiformats/murmur3": "^2.1.8", + "hamt-sharding": "^3.0.6", + "interface-blockstore": "^5.2.10", + "ipfs-unixfs": "^11.1.3", + "ipfs-unixfs-exporter": "^13.5.0", + "ipfs-unixfs-importer": "^15.2.4", + "it-all": "^3.0.4", + "it-glob": "^2.0.6", + "it-last": "^3.0.4", + "it-pipe": "^3.0.1", + "merge-options": "^3.0.4", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "sparse-array": "^1.3.2", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@helia/utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.1.0.tgz", + "integrity": "sha512-uRUDzGpeWbPANFlq3J3x14tE89enVEBoaRXM1CKpE25vXUOij9qoYjE8GlXDNCQgkazAzrVswdjcRn6Eza+bIg==", + "dependencies": { + "@helia/interface": "^4.1.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/peer-collections": "^5.1.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.0", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@helia/verified-fetch": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.12.tgz", + "integrity": "sha512-eLDGvrDLDs09vZk5PxYmPTB7WlJ88ff4GwF7gRbH5zaqZVVCUP3S6lkEvmNM+9MYPfElfR/wUOmN2GJIVqDQ/A==", + "dependencies": { + "@helia/block-brokers": "^2.1.0", + "@helia/car": "^3.1.3", + "@helia/http": "^1.0.4", + "@helia/interface": "^4.2.0", + "@helia/ipns": "^7.2.1", + "@helia/routers": "^1.0.3", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/interface": "^1.1.6", + "@libp2p/kad-dht": "^12.0.11", + "@libp2p/peer-id": "^4.0.9", + "@multiformats/dns": "^1.0.6", + "cborg": "^4.2.0", + "hashlru": "^2.3.0", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "ipfs-unixfs-exporter": "^13.5.0", + "it-map": "^3.0.5", + "it-pipe": "^3.0.1", + "it-tar": "^6.0.5", + "it-to-browser-readablestream": "^2.0.6", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.3" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@ipld/car": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@ipld/car/-/car-5.3.0.tgz", + "integrity": "sha512-OB8LVvJeVAFFGluNIkZeDZ/aGeoekFKsuIvNT9I5sJIb5WekQuW5+lekjQ7Z7mZ7DBKuke/kI4jBT1j0/akU1w==", + "dependencies": { + "@ipld/dag-cbor": "^9.0.7", + "cborg": "^4.0.5", + "multiformats": "^13.0.0", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-cbor": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz", + "integrity": "sha512-N14oMy0q4gM6OuZkIpisKe0JBSjf1Jb39VI+7jMLiWX9124u1Z3Fdj/Tag1NA0cVxxqWDh0CqsjcVfOKtelPDA==", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-json": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.0.tgz", + "integrity": "sha512-O9YLUrl3d3WbVz7v1WkajFkyfOLEe2Fep+wor4fgVe0ywxzrivrj437NiPcVyB+2EDdFn/Q7tCHFf8YVhDf8ZA==", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.0.tgz", + "integrity": "sha512-LJU451Drqs5zjFm7jI4Hs3kHlilOqkjcSfPiQgVsZnWaYb2C7YdfhnclrVn/X+ucKejlU9BL3+gXFCZUXkMuCg==", + "dependencies": { + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + }, + "node_modules/@libp2p/autonat": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.14.tgz", + "integrity": "sha512-yd6Qlltzt7/kkec9wOtgUJzzDXXreVXyKVR1E5zms58sQmVavc3PWEJE1cFxK0GYMF2QjjzJY13/z9WLeQBzIA==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-id-factory": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "it-first": "^3.0.4", + "it-length-prefixed": "^9.0.4", + "it-map": "^3.0.5", + "it-parallel": "^3.0.6", + "it-pipe": "^3.0.1", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/bootstrap": { + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.17.tgz", + "integrity": "sha512-NO+YTigMn62YMbPAz4jK8sOC6tY2K5Q7wnRI2lmHzP9albIX+Nf4/Tyj7tu8Owi5CP+v07eKXWejmrXm+MkJPg==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14" + } + }, + "node_modules/@libp2p/circuit-relay-v2": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.17.tgz", + "integrity": "sha512-FSTqidxco90MjMdKTGhFZoGxf/EaZMuwRWyEq5oGw1aG8lBKrH2fwP1fTRfwAQVhQ8Pxd9B9/SfBU6QSHA5YKQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-record": "^7.0.11", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "any-signal": "^4.1.1", + "it-protobuf-stream": "^1.1.2", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "p-defer": "^4.0.0", + "p-retry": "^6.2.0", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/crypto": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.6.tgz", + "integrity": "sha512-AJ4i3DHOTlY961O26M3k1IjmU4rUd5WgeK4t9IRzFfLIbD6uwA+cevJMG2qr0UHJfbYdGKKQ2Po1wqZONoIA9Q==", + "dependencies": { + "@libp2p/interface": "^1.2.0", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "asn1js": "^3.0.5", + "multiformats": "^13.1.0", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" + } + }, + "node_modules/@libp2p/dcutr": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.0.14.tgz", + "integrity": "sha512-0x5Sn21KVe1Fz0YeIudPIg2GB+XDVwr5IHblXVVKq5PNHjUvqarO4XM5GAaLyK0tZqwFkkgDNE4BjmHWZkejng==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "delay": "^6.0.0", + "it-protobuf-stream": "^1.1.2", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/identify": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-1.0.16.tgz", + "integrity": "sha512-uDXae1DGok+rFF5T8yixf3lkadr5U3O+x72BjgZaIeduWLCbPDczlKMNJuFJF3+ys45qcO8WuySuZqyK2aq8kw==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-record": "^7.0.11", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "it-protobuf-stream": "^1.1.2", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2", + "wherearewe": "^2.0.1" + } + }, + "node_modules/@libp2p/interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.2.0.tgz", + "integrity": "sha512-ImnGNl3El/AukgaojACT8i9SNW1FOsrThcQU/qA3w5tEBR5p84Uwgzl/nxa4X5vGinItUJ9jLEJmtkQJENoiGQ==", + "dependencies": { + "@multiformats/multiaddr": "^12.2.1", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/interface-internal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.1.0.tgz", + "integrity": "sha512-B6Cu3Mhp5kY2Z1cU0soCR4ZtjZtE4FuWE0qdJNauOpcQe9HOjPF8SanFmeEIZ0FKSOo0onQdQi2YdNUTtOVyvQ==", + "dependencies": { + "@libp2p/interface": "^1.2.0", + "@libp2p/peer-collections": "^5.1.10", + "@multiformats/multiaddr": "^12.2.1", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/interfaces": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", + "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/kad-dht": { + "version": "12.0.13", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.13.tgz", + "integrity": "sha512-as+pIHcrjVTXkf6kyULtnJg/RRvrbVjyycPD8HtqD5SKB0PDiYvJO0LY0oVAHFFi5oGMCqSG5777VQfC/tmRhQ==", + "dependencies": { + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.2.0", + "@libp2p/interface-internal": "^1.1.0", + "@libp2p/peer-collections": "^5.1.10", + "@libp2p/peer-id": "^4.0.10", + "@libp2p/record": "^4.0.0", + "@libp2p/utils": "^5.3.1", + "@multiformats/multiaddr": "^12.2.1", + "any-signal": "^4.1.1", + "hashlru": "^2.3.0", + "interface-datastore": "^8.2.11", + "it-drain": "^3.0.5", + "it-length": "^3.0.4", + "it-length-prefixed": "^9.0.4", + "it-map": "^3.0.5", + "it-merge": "^3.0.3", + "it-parallel": "^3.0.6", + "it-pipe": "^3.0.1", + "it-protobuf-stream": "^1.1.2", + "it-pushable": "^3.2.3", + "it-take": "^3.0.4", + "multiformats": "^13.1.0", + "p-defer": "^4.0.1", + "p-event": "^6.0.1", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0", + "protons-runtime": "^5.4.0", + "race-signal": "^1.0.2", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" + } + }, + "node_modules/@libp2p/keychain": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.0.10.tgz", + "integrity": "sha512-yI7Z3H4hNky7uRyJUHE/4lcc42jboFlGnveW+nrUEm+3/IrCt7T+Ci74Ighhh41V90iKJyFAfQeR+HAnwpT9GA==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "interface-datastore": "^8.2.11", + "merge-options": "^3.0.4", + "multiformats": "^13.1.0", + "sanitize-filename": "^1.6.3", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/logger": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.10.tgz", + "integrity": "sha512-JiRfJHO/D9Jlh2rJ6STnONoeQevBAdAZaGUxrtvBf4RFfucldSFEMOtdkFO8xFGuiA90Q2kj4BE2douG6fB3Lw==", + "dependencies": { + "@libp2p/interface": "^1.2.0", + "@multiformats/multiaddr": "^12.2.1", + "debug": "^4.3.4", + "interface-datastore": "^8.2.11", + "multiformats": "^13.1.0" + } + }, + "node_modules/@libp2p/mdns": { + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.0.17.tgz", + "integrity": "sha512-hflI9xnA1vWtwiZnl9Y4HXGp0QoDIt38kFdYqlgvcRIAJuH4Gw0dD/pFvzYUT3/BPAM1sZb9xU8sK1dwmPwWzQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "@types/multicast-dns": "^7.2.4", + "dns-packet": "^5.6.1", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/@libp2p/mplex": { + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.0.17.tgz", + "integrity": "sha512-/+AW5J5Zh/M2vEE7nDU00zaLyCtbmBstcUnps+MBKEKoZ4nQaGtAox2dq9dvCOSWrFl3zhHDrLQy1Kt2xIfxMA==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/utils": "^5.2.7", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/multistream-select": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.5.tgz", + "integrity": "sha512-fQSF3bjlaSqneD50/cj9Kbl2YjFVy45kHxtjnMGb04cnyL8CefJ89dY2aF3bWCbbEqUxzc4yCCNi48y+Pfo34w==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "it-length-prefixed": "^9.0.4", + "it-length-prefixed-stream": "^1.1.6", + "it-stream-types": "^2.0.1", + "p-defer": "^4.0.0", + "race-signal": "^1.0.2", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-collections": { + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.10.tgz", + "integrity": "sha512-Edr4FBzCgE7FRgc0wfYfcmihQ4GDHwkQP7xMG4oOVoIxHEzuk9Nb2opK9cLbK+nU4oAROgFLzJEJuiG7BGV2hg==", + "dependencies": { + "@libp2p/interface": "^1.2.0", + "@libp2p/peer-id": "^4.0.10" + } + }, + "node_modules/@libp2p/peer-id": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.10.tgz", + "integrity": "sha512-cR5dQ5fPcxP4LLSXDgo+TSOhtElZSwRXVSSgT/GM/Vvbua5M91NzsksYfd/lg8XwTCSvTER0qmE6ZIR05vjQrA==", + "dependencies": { + "@libp2p/interface": "^1.2.0", + "multiformats": "^13.1.0", + "uint8arrays": "^5.0.3" + } + }, + "node_modules/@libp2p/peer-id-factory": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.0.8.tgz", + "integrity": "sha512-Kk9TqhSY0dPr2RmLUSJj+V5j4vgxrB25G5bf+nNfY79s/YNh0uBIq2ZmeBezXnG4q5egH1e9vbrgHdl7PUllUA==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-record": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.11.tgz", + "integrity": "sha512-jAi/TX1NSdNPsO0TmJd5fEKQfjdkxxBPmlwofBx2dPqtud/Aac4wa6dvIFQKqGkh8ZeC8FAQbv8jE/a8jgg3MQ==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "protons-runtime": "^5.4.0", + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/peer-store": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.12.tgz", + "integrity": "sha512-lX8wdgX0NNxIcnutJ3hxfhyt+QXl0fVxYY3O1tKtEPoyilbxBA66qVVdU3nVmETkku7TcUuSOEfwevwmQC7qhw==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/peer-record": "^7.0.11", + "@multiformats/multiaddr": "^12.1.14", + "interface-datastore": "^8.2.11", + "it-all": "^3.0.4", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/ping": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.0.13.tgz", + "integrity": "sha512-x3q0IOYKmLzwKbsXKL5fH9EAf/M4/LoGctCbffpr+dxn3cYZOJIMO2ZgJkEBW7DslNTskufgeTsh+wgG0EsWrw==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@multiformats/multiaddr": "^12.1.14", + "it-first": "^3.0.4", + "it-pipe": "^3.0.1", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/pubsub": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-9.0.12.tgz", + "integrity": "sha512-PTrdRXoluKFhnhQVMwk5iDuwrEvOTqM/fjcOE5YZ5mJCCu5NuHGmFPy5hkVeu19IC8vM9LCTCdNLuA71/GUEZg==", + "dependencies": { + "@libp2p/crypto": "^4.0.4", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-collections": "^5.1.8", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "it-length-prefixed": "^9.0.4", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "multiformats": "^13.1.0", + "p-queue": "^8.0.1", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/record": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.0.tgz", + "integrity": "sha512-qEbLgKj1DY6K0B9LFXXm0hdCmMJdkfrxFMa5/pOe9PufaRPCAOABYmbPYQQmDNI8UrwWJbf47tj+pmDS57SLTw==", + "dependencies": { + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" + } + }, + "node_modules/@libp2p/tcp": { + "version": "9.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.17.tgz", + "integrity": "sha512-joGy4Zs/2jQ1pe8fD7yr9lLqJ2ZXxZN6dZhhzknf3Opq1YRQ3yyz2eu5G1LNdN0jqyO259UO+TnLtEkruel/QQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "@types/sinon": "^17.0.3", + "stream-to-it": "^0.2.4" + } + }, + "node_modules/@libp2p/upnp-nat": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.15.tgz", + "integrity": "sha512-F9NStswMhpKNfzFf91mHxWCw+yKTZfbeadgb5wW6sFh6gQNpMWHdvUDoC8hg2qL8snHcMn5OqGNgE+KvNvSKAw==", + "dependencies": { + "@achingbrain/nat-port-mapper": "^1.0.13", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/utils": "^5.2.7", + "@multiformats/multiaddr": "^12.1.14", + "wherearewe": "^2.0.1" + } + }, + "node_modules/@libp2p/utils": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.3.1.tgz", + "integrity": "sha512-FdGzRU50PJLYSEOmVXqqtq27yjUVXkU4QNRZzMVuXF9L/sKgSC2oXwj0Satc9fHx5tG3MCX1ZOSAmYEIl2fu+w==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.2", + "@libp2p/interface": "^1.2.0", + "@libp2p/logger": "^4.0.10", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "delay": "^6.0.0", + "get-iterator": "^2.0.1", + "is-loopback-addr": "^2.0.2", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "netmask": "^2.0.2", + "p-defer": "^4.0.1", + "race-event": "^1.2.0", + "race-signal": "^1.0.2", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@libp2p/webrtc": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.23.tgz", + "integrity": "sha512-/HI60R9KLivtFCB2vIKDcwyppnKpB2T7BYBej5CCO6KQQM2zEA9/jd/dZ94hs7DUQZCOynJHcUjRKG/rIDyncQ==", + "dependencies": { + "@chainsafe/libp2p-noise": "^15.0.0", + "@libp2p/interface": "^1.1.5", + "@libp2p/interface-internal": "^1.0.10", + "@libp2p/peer-id": "^4.0.8", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "detect-browser": "^5.3.0", + "it-length-prefixed": "^9.0.4", + "it-protobuf-stream": "^1.1.2", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "multihashes": "^4.0.3", + "node-datachannel": "^0.5.5", + "p-defer": "^4.0.0", + "p-event": "^6.0.0", + "p-timeout": "^6.1.2", + "protons-runtime": "^5.4.0", + "race-signal": "^1.0.2", + "react-native-webrtc": "^118.0.1", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@libp2p/websockets": { + "version": "8.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.0.17.tgz", + "integrity": "sha512-b8CebwVYSL+tvoh3yvDzwbY7KgLwdkPb22aPjc7ogB/+XHu/R4cTXSPRHAuzNXNdM4XN5NFtwjyHVBUXSqYRBQ==", + "dependencies": { + "@libp2p/interface": "^1.1.5", + "@libp2p/utils": "^5.2.7", + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-to-uri": "^10.0.1", + "@types/ws": "^8.5.10", + "it-ws": "^6.1.1", + "p-defer": "^4.0.0", + "wherearewe": "^2.0.1", + "ws": "^8.16.0" + } + }, + "node_modules/@libp2p/webtransport": { + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.0.22.tgz", + "integrity": "sha512-oHI9xJBmknO51P9XqQDa3mN/ed0s043eYvnpp1useealowXedA/4Eth84s9cvVC54o6mCNyPzqIgg2UMBWb7Bw==", + "dependencies": { + "@chainsafe/libp2p-noise": "^15.0.0", + "@libp2p/interface": "^1.1.5", + "@libp2p/peer-id": "^4.0.8", + "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr-matcher": "^1.1.2", + "it-stream-types": "^2.0.1", + "multiformats": "^13.1.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" + }, + "node_modules/@multiformats/dns": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", + "integrity": "sha512-nt/5UqjMPtyvkG9BQYdJ4GfLK3nMqGpFZOzf4hAmIa0sJh2LlS9YKXZ4FgwBDsaHvzZqR/rUFIywIc7pkHNNuw==", + "dependencies": { + "@types/dns-packet": "^5.6.5", + "buffer": "^6.0.3", + "dns-packet": "^5.6.1", + "hashlru": "^2.3.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/mafmt": { + "version": "12.1.6", + "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", + "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/multiaddr": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.2.1.tgz", + "integrity": "sha512-UwjoArBbv64FlaetV4DDwh+PUMfzXUBltxQwdh+uTYnGFzVa8ZfJsn1vt1RJlJ6+Xtrm3RMekF/B+K338i2L5Q==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/interface": "^1.0.0", + "@multiformats/dns": "^1.0.3", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@multiformats/multiaddr-matcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.0.tgz", + "integrity": "sha512-LH6yR7h3HSNKcxuvvi2UpLuowuVkYC6H9Y3jqmKuTai8XtKnXtW6NcDZFD/ooTBY+H4yX/scoJpjOalHrk5qdQ==", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@multiformats/multiaddr": "^12.0.0", + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.0.1.tgz", + "integrity": "sha512-RtOBRJucMCzINPytvt1y7tJ2jr4aSKJmv3DF7/C515RJO9+nu9sZHdsk9vn251OtN8k21rAGlIHESt/BSJWAnQ==", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/murmur3": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.8.tgz", + "integrity": "sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA==", + "dependencies": { + "multiformats": "^13.0.0", + "murmurhash3js-revisited": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@noble/ciphers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", + "integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@react-native-community/cli": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz", + "integrity": "sha512-647OSi6xBb8FbwFqX9zsJxOzu685AWtrOUWHfOkbKD+5LOpGORw+GQo0F9rWZnB68rLQyfKUZWJeaD00pGv5fw==", + "peer": true, + "dependencies": { + "@react-native-community/cli-clean": "12.3.6", + "@react-native-community/cli-config": "12.3.6", + "@react-native-community/cli-debugger-ui": "12.3.6", + "@react-native-community/cli-doctor": "12.3.6", + "@react-native-community/cli-hermes": "12.3.6", + "@react-native-community/cli-plugin-metro": "12.3.6", + "@react-native-community/cli-server-api": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "@react-native-community/cli-types": "12.3.6", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz", + "integrity": "sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.6.tgz", + "integrity": "sha512-JGWSYQ9EAK6m2v0abXwFLEfsqJ1zkhzZ4CV261QZF9MoUNB6h57a274h1MLQR9mG6Tsh38wBUuNfEPUvS1vYew==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.6.tgz", + "integrity": "sha512-SjUKKsx5FmcK9G6Pb6UBFT0s9JexVStK5WInmANw75Hm7YokVvHEgtprQDz2Uvy5znX5g2ujzrkIU//T15KQzA==", + "peer": true, + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.6.tgz", + "integrity": "sha512-fvBDv2lTthfw4WOQKkdTop2PlE9GtfrlNnpjB818MhcdEnPjfQw5YaTUcnNEGsvGomdCs1MVRMgYXXwPSN6OvQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-config": "12.3.6", + "@react-native-community/cli-platform-android": "12.3.6", + "@react-native-community/cli-platform-ios": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-hermes": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz", + "integrity": "sha512-sNGwfOCl8OAIjWCkwuLpP8NZbuO0dhDI/2W7NeOGDzIBsf4/c4MptTrULWtGIH9okVPLSPX0NnRyGQ+mSwWyuQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-android": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.6.tgz", + "integrity": "sha512-DeDDAB8lHpuGIAPXeeD9Qu2+/wDTFPo99c8uSW49L0hkmZJixzvvvffbGQAYk32H0TmaI7rzvzH+qzu7z3891g==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.6.tgz", + "integrity": "sha512-3eZ0jMCkKUO58wzPWlvAPRqezVKm9EPZyaPyHbRPWU8qw7JqkvnRlWIaYDGpjCJgVW4k2hKsEursLtYKb188tg==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.6", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.6.tgz", + "integrity": "sha512-3jxSBQt4fkS+KtHCPSyB5auIT+KKIrPCv9Dk14FbvOaEh9erUWEm/5PZWmtboW1z7CYeNbFMeXm9fM2xwtVOpg==", + "peer": true + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.6.tgz", + "integrity": "sha512-80NIMzo8b2W+PL0Jd7NjiJW9mgaT8Y8wsIT/lh6mAvYH7mK0ecDJUYUTAAv79Tbo1iCGPAr3T295DlVtS8s4yQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.6.tgz", + "integrity": "sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==", + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.6.tgz", + "integrity": "sha512-xPqTgcUtZowQ8WKOkI9TLGBwH2bGggOC4d2FFaIRST3gTcjrEeGRNeR5aXCzJFIgItIft8sd7p2oKEdy90+01Q==", + "peer": true, + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "peer": true, + "dependencies": { + "@react-native/codegen": "0.73.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.4", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.73.17", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.17.tgz", + "integrity": "sha512-F3PXZkcHg+1ARIr6FRQCQiB7ZAA+MQXGmq051metRscoLvgYJwj7dgC8pvgy0kexzUkHu5BNKrZeySzUft3xuQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-server-api": "12.3.6", + "@react-native-community/cli-tools": "12.3.6", + "@react-native/dev-middleware": "0.73.8", + "@react-native/metro-babel-transformer": "0.73.15", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.73.8", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", + "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0", + "ws": "^6.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", + "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.21", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "peer": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "peer": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@swc/core": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.18.tgz", + "integrity": "sha512-VChk3ldLhmVoX3Hd2M3Y4j960T0lo2Zus60iZoWST6P65RVPt8BatFVVPAB9dABy1dB5zn1BCpHlH85yXVysQw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "swcx": "run_swcx.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.18", + "@swc/core-darwin-x64": "1.3.18", + "@swc/core-linux-arm-gnueabihf": "1.3.18", + "@swc/core-linux-arm64-gnu": "1.3.18", + "@swc/core-linux-arm64-musl": "1.3.18", + "@swc/core-linux-x64-gnu": "1.3.18", + "@swc/core-linux-x64-musl": "1.3.18", + "@swc/core-win32-arm64-msvc": "1.3.18", + "@swc/core-win32-ia32-msvc": "1.3.18", + "@swc/core-win32-x64-msvc": "1.3.18" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.18.tgz", + "integrity": "sha512-PA3Cc97Kc6W6RtpBLeJaoXLCRL5dJLYd2dszf+f5hGHHJybh6eXGIU0ZkZr898NUHoL8fT6Mg6I4JCNImq/yBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.18.tgz", + "integrity": "sha512-RiZXHwED8cfD/zoBG01iY8YZtOF/8t9XHZ1JqCx9PWOMjXD3Vc8F2I7bp1Qg6ahzWEaP+2+/rqGO1kSwaJjJLw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/busboy": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.3.tgz", + "integrity": "sha512-YMBLFN/xBD8bnqywIlGyYqsNFXu6bsiY7h3Ae0kO17qEuTjsqeyYMRPSUDacIKIquws2Y6KjmxAyNx8xB3xQbw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/dns-packet": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", + "integrity": "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/luxon": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.8.tgz", + "integrity": "sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true + }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/multicast-dns": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", + "integrity": "sha512-ib5K4cIDR4Ro5SR3Sx/LROkMDa0BHz0OPaCBL/OSPDsAXEGZ3/KQeS6poBKYVN7BfjXDL9lWNwzyHVgt/wkyCw==", + "dependencies": { + "@types/dns-packet": "*", + "@types/node": "*" + } + }, + "node_modules/@types/murmurhash3js-revisited": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.3.tgz", + "integrity": "sha512-QvlqvYtGBYIDeO8dFdY4djkRubcrc+yTJtBc7n8VZPlJDUS/00A+PssbvERM8f9bYRmcaSEHPZgZojeQj7kzAA==" + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "peer": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" } }, - "node_modules/@libp2p/webtransport": { - "version": "4.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.0.22.tgz", - "integrity": "sha512-oHI9xJBmknO51P9XqQDa3mN/ed0s043eYvnpp1useealowXedA/4Eth84s9cvVC54o6mCNyPzqIgg2UMBWb7Bw==", + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "peer": true, "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "@types/yargs-parser": "*" } }, - "node_modules/@multiformats/base-x": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", - "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "peer": true }, - "node_modules/@multiformats/dns": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", - "integrity": "sha512-nt/5UqjMPtyvkG9BQYdJ4GfLK3nMqGpFZOzf4hAmIa0sJh2LlS9YKXZ4FgwBDsaHvzZqR/rUFIywIc7pkHNNuw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, "dependencies": { - "@types/dns-packet": "^5.6.5", - "buffer": "^6.0.3", - "dns-packet": "^5.6.1", - "hashlru": "^2.3.0", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@multiformats/mafmt": { - "version": "12.1.6", - "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", - "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, "dependencies": { - "@multiformats/multiaddr": "^12.0.0" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@multiformats/multiaddr": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.2.1.tgz", - "integrity": "sha512-UwjoArBbv64FlaetV4DDwh+PUMfzXUBltxQwdh+uTYnGFzVa8ZfJsn1vt1RJlJ6+Xtrm3RMekF/B+K338i2L5Q==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@libp2p/interface": "^1.0.0", - "@multiformats/dns": "^1.0.3", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@multiformats/multiaddr-matcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.0.tgz", - "integrity": "sha512-LH6yR7h3HSNKcxuvvi2UpLuowuVkYC6H9Y3jqmKuTai8XtKnXtW6NcDZFD/ooTBY+H4yX/scoJpjOalHrk5qdQ==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@multiformats/multiaddr": "^12.0.0", - "multiformats": "^13.0.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@multiformats/multiaddr-to-uri": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.0.1.tgz", - "integrity": "sha512-RtOBRJucMCzINPytvt1y7tJ2jr4aSKJmv3DF7/C515RJO9+nu9sZHdsk9vn251OtN8k21rAGlIHESt/BSJWAnQ==", - "dependencies": { - "@multiformats/multiaddr": "^12.0.0" + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@multiformats/murmur3": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.8.tgz", - "integrity": "sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, "dependencies": { - "multiformats": "^13.0.0", - "murmurhash3js-revisited": "^3.0.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@noble/ciphers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", - "integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, "funding": { - "url": "https://paulmillr.com/funding/" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, "dependencies": { - "@noble/hashes": "1.4.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, "engines": { - "node": ">= 16" + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://paulmillr.com/funding/" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz", - "integrity": "sha512-647OSi6xBb8FbwFqX9zsJxOzu685AWtrOUWHfOkbKD+5LOpGORw+GQo0F9rWZnB68rLQyfKUZWJeaD00pGv5fw==", - "peer": true, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dependencies": { - "@react-native-community/cli-clean": "12.3.6", - "@react-native-community/cli-config": "12.3.6", - "@react-native-community/cli-debugger-ui": "12.3.6", - "@react-native-community/cli-doctor": "12.3.6", - "@react-native-community/cli-hermes": "12.3.6", - "@react-native-community/cli-plugin-metro": "12.3.6", - "@react-native-community/cli-server-api": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "@react-native-community/cli-types": "12.3.6", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { - "react-native": "build/bin.js" + "acorn": "bin/acorn" }, "engines": { - "node": ">=18" + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@react-native-community/cli-clean": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz", - "integrity": "sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==", - "peer": true, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "execa": "^5.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "peer": true + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", "peer": true, + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { - "node": ">=10.17.0" + "node": ">=8" } }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, + "node_modules/any-signal": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.1.1.tgz", + "integrity": "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA==", "engines": { - "node": ">=6" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dependencies": { - "path-key": "^3.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/@react-native-community/cli-clean/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "peer": true + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "peer": true, "dependencies": { - "mimic-fn": "^2.1.0" + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@react-native-community/cli-clean/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/@react-native-community/cli-config": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.6.tgz", - "integrity": "sha512-JGWSYQ9EAK6m2v0abXwFLEfsqJ1zkhzZ4CV261QZF9MoUNB6h57a274h1MLQR9mG6Tsh38wBUuNfEPUvS1vYew==", - "peer": true, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "glob": "^7.1.3", - "joi": "^17.2.1" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.6.tgz", - "integrity": "sha512-SjUKKsx5FmcK9G6Pb6UBFT0s9JexVStK5WInmANw75Hm7YokVvHEgtprQDz2Uvy5znX5g2ujzrkIU//T15KQzA==", - "peer": true, - "dependencies": { - "serve-static": "^1.13.1" + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@react-native-community/cli-doctor": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.6.tgz", - "integrity": "sha512-fvBDv2lTthfw4WOQKkdTop2PlE9GtfrlNnpjB818MhcdEnPjfQw5YaTUcnNEGsvGomdCs1MVRMgYXXwPSN6OvQ==", - "peer": true, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "peer": true + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", "dependencies": { - "@react-native-community/cli-config": "12.3.6", - "@react-native-community/cli-platform-android": "12.3.6", - "@react-native-community/cli-platform-ios": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "tslib": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "peer": true, + "engines": { + "node": ">=4" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "peer": true + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", "engines": { - "node": ">=10.17.0" + "node": ">=8.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", "peer": true, - "engines": { - "node": ">=6" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/babel-plugin-add-import-extension": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-add-import-extension/-/babel-plugin-add-import-extension-1.6.0.tgz", + "integrity": "sha512-JVSQPMzNzN/S4wPRoKQ7+u8PlkV//BPUMnfWVbr63zcE+6yHdU2Mblz10Vf7qe+6Rmu4svF5jG7JxdcPi9VvKg==", + "dev": true, "dependencies": { - "path-key": "^3.0.0" + "@babel/helper-plugin-utils": "^7.14.5" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": ">=7.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", "peer": true, "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.1", + "semver": "^6.3.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "peer": true, - "engines": { - "node": ">=6" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz", - "integrity": "sha512-sNGwfOCl8OAIjWCkwuLpP8NZbuO0dhDI/2W7NeOGDzIBsf4/c4MptTrULWtGIH9okVPLSPX0NnRyGQ+mSwWyuQ==", + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", "peer": true, "dependencies": { - "@react-native-community/cli-platform-android": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6" + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.6.tgz", - "integrity": "sha512-DeDDAB8lHpuGIAPXeeD9Qu2+/wDTFPo99c8uSW49L0hkmZJixzvvvffbGQAYk32H0TmaI7rzvzH+qzu7z3891g==", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", + "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-xml-parser": "^4.2.4", - "glob": "^7.1.3", - "logkitty": "^0.7.1" + "@babel/helper-define-polyfill-provider": "^0.6.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "@babel/plugin-syntax-flow": "^7.12.1" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, - "engines": { - "node": ">=10.17.0" - } + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "engines": { "node": ">=8" }, @@ -3872,323 +6657,289 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, - "engines": { - "node": ">=6" + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/blockstore-core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.1.tgz", + "integrity": "sha512-peXfL9ZLx1cb84QALocMjhT8CsQ4JsreI/AitlN1inipSdC/G+jcYVJCqeCD5ecSTv/0LMpg8NlAPH/eBYZLjA==", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@libp2p/logger": "^4.0.6", + "err-code": "^3.0.1", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.0", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-merge": "^3.0.3", + "it-pushable": "^3.2.3", + "multiformats": "^13.0.1" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, + "node_modules/blockstore-fs": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-1.1.10.tgz", + "integrity": "sha512-Dg0mbdma0OY4NEk78efcAAiG5ZrMcIVrM7s+0e2p4uavnvrcBT6vDj5ITfnRfid3idKHOoCYShGEi9ENNgJg1A==", "dependencies": { - "mimic-fn": "^2.1.0" + "blockstore-core": "^4.0.0", + "fast-write-atomic": "^0.2.1", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.0", + "it-glob": "^2.0.6", + "it-map": "^3.0.5", + "it-parallel-batch": "^3.0.4", + "multiformats": "^13.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.6.tgz", - "integrity": "sha512-3eZ0jMCkKUO58wzPWlvAPRqezVKm9EPZyaPyHbRPWU8qw7JqkvnRlWIaYDGpjCJgVW4k2hKsEursLtYKb188tg==", - "peer": true, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", - "ora": "^5.4.1" + "ms": "2.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" + "ee-first": "1.1.1" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, - "engines": { - "node": ">=6" + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dependencies": { - "path-key": "^3.0.0" + "fill-range": "^7.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/browser-readablestream-to-it": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.5.tgz", + "integrity": "sha512-obLCT9jnxAeZlbaRWluUiZrcSJEoi2JkM0eoiJqlIP7MFwZwZjcB6giZvD343PXfr96ilD91M/wFqFvyAZq+Gg==" + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "peer": true, "dependencies": { - "mimic-fn": "^2.1.0" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, - "engines": { - "node": ">=6" + "bin": { + "browserslist": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, "engines": { - "node": ">=6" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.6.tgz", - "integrity": "sha512-3jxSBQt4fkS+KtHCPSyB5auIT+KKIrPCv9Dk14FbvOaEh9erUWEm/5PZWmtboW1z7CYeNbFMeXm9fM2xwtVOpg==", - "peer": true - }, - "node_modules/@react-native-community/cli-server-api": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.6.tgz", - "integrity": "sha512-80NIMzo8b2W+PL0Jd7NjiJW9mgaT8Y8wsIT/lh6mAvYH7mK0ecDJUYUTAAv79Tbo1iCGPAr3T295DlVtS8s4yQ==", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "peer": true, "dependencies": { - "@react-native-community/cli-debugger-ui": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" + "node-int64": "^0.4.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "utf-8-validate": { - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.6.tgz", - "integrity": "sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==", - "peer": true, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "peer": true, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "p-locate": "^5.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", "peer": true, "dependencies": { - "yocto-queue": "^0.1.0" + "callsites": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", "peer": true, "dependencies": { - "p-limit": "^3.0.2" + "caller-callsite": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/@react-native-community/cli-types": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.6.tgz", - "integrity": "sha512-xPqTgcUtZowQ8WKOkI9TLGBwH2bGggOC4d2FFaIRST3gTcjrEeGRNeR5aXCzJFIgItIft8sd7p2oKEdy90+01Q==", + "node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", "peer": true, - "dependencies": { - "joi": "^17.2.1" + "engines": { + "node": ">=4" } }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, - "engines": { - "node": ">=10.17.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, "engines": { "node": ">=8" }, @@ -4196,1435 +6947,1692 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, "engines": { "node": ">=6" } }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "node_modules/caniuse-lite": { + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/cborg": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.2.0.tgz", + "integrity": "sha512-q6cFW5m3KxfP/9xGI3yGLaC1l5DP6DWM9IvjiJojnIwohL5CQDl02EXViPV852mOfQo+7PJGPN01MI87vFGzyA==", + "bin": { + "cborg": "lib/bin.js" } }, - "node_modules/@react-native-community/cli/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "mimic-fn": "^2.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, - "engines": { - "node": ">=6" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native/assets-registry": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", - "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", - "peer": true, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", - "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", - "peer": true, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "@react-native/codegen": "0.73.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@react-native/babel-preset": { - "version": "0.73.21", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", - "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", - "peer": true, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.4", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=18" + "node": ">= 8.10.0" }, - "peerDependencies": { - "@babel/core": "*" + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/@react-native/codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", - "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "peer": true, "dependencies": { - "@babel/parser": "^7.20.0", - "flow-parser": "^0.206.0", - "glob": "^7.1.1", - "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" }, - "engines": { - "node": ">=18" + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "engines": { + "node": ">=12.13.0" } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.17", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.17.tgz", - "integrity": "sha512-F3PXZkcHg+1ARIr6FRQCQiB7ZAA+MQXGmq051metRscoLvgYJwj7dgC8pvgy0kexzUkHu5BNKrZeySzUft3xuQ==", + "node_modules/chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", "peer": true, "dependencies": { - "@react-native-community/cli-server-api": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "@react-native/dev-middleware": "0.73.8", - "@react-native/metro-babel-transformer": "0.73.15", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "readline": "^1.3.0" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/chromium-edge-launcher/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=10" + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "peer": true, "engines": { - "node": ">=10.17.0" + "node": ">=8" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "peer": true, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, "dependencies": { - "path-key": "^3.0.0" + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, - "dependencies": { - "mimic-fn": "^2.1.0" + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", - "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", - "peer": true, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/@react-native/dev-middleware": { - "version": "0.73.8", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", - "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", - "peer": true, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.73.3", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^1.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "open": "^7.0.3", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { - "ms": "2.0.0" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } }, - "node_modules/@react-native/dev-middleware/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "peer": true, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "peer": true, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", - "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", - "peer": true, + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", - "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "peer": true, "engines": { - "node": ">=18" + "node": ">=0.8" } }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.15", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", - "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "peer": true, "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.21", - "hermes-parser": "0.15.0", - "nullthrows": "^1.1.1" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">=6" } }, - "node_modules/@react-native/normalize-colors": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", - "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", - "peer": true - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", - "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", - "peer": true, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react-native": "*" + "node": ">=7.0.0" } }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "peer": true }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", "peer": true }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "peer": true }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "peer": true, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, "dependencies": { - "type-detect": "4.0.8" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "peer": true, "dependencies": { - "@sinonjs/commons": "^3.0.0" + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "peer": true, "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/@types/busboy": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.3.tgz", - "integrity": "sha512-YMBLFN/xBD8bnqywIlGyYqsNFXu6bsiY7h3Ae0kO17qEuTjsqeyYMRPSUDacIKIquws2Y6KjmxAyNx8xB3xQbw==", - "dev": true, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, "dependencies": { - "@types/node": "*" + "ms": "2.0.0" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], "dependencies": { - "@types/node": "*" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/@types/dns-packet": { - "version": "5.6.5", - "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", - "integrity": "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==", + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { - "@types/node": "*" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "safe-buffer": "~5.1.0" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", - "dev": true, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "peer": true, "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" } }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "peer": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "peer": true, "dependencies": { - "@types/istanbul-lib-coverage": "*" + "ms": "2.0.0" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "peer": true, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "@types/istanbul-lib-report": "*" + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/luxon": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.8.tgz", - "integrity": "sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==" + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/@types/multer": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", - "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", "dev": true, "dependencies": { - "@types/express": "*" + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" } }, - "node_modules/@types/multicast-dns": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", - "integrity": "sha512-ib5K4cIDR4Ro5SR3Sx/LROkMDa0BHz0OPaCBL/OSPDsAXEGZ3/KQeS6poBKYVN7BfjXDL9lWNwzyHVgt/wkyCw==", + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, "dependencies": { - "@types/dns-packet": "*", - "@types/node": "*" + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" } }, - "node_modules/@types/murmurhash3js-revisited": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.3.tgz", - "integrity": "sha512-QvlqvYtGBYIDeO8dFdY4djkRubcrc+yTJtBc7n8VZPlJDUS/00A+PssbvERM8f9bYRmcaSEHPZgZojeQj7kzAA==" - }, - "node_modules/@types/node": { - "version": "20.11.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", - "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" } }, - "node_modules/@types/qs": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", - "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true + "node_modules/conventional-commits-parser/node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", - "dev": true, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js-compat": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "peer": true, "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/@types/sinon": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", - "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "peer": true, "dependencies": { - "@types/sinonjs__fake-timers": "*" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", - "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==" + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "peer": true + "node_modules/cron": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.6.tgz", + "integrity": "sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==", + "dependencies": { + "@types/luxon": "~3.3.0", + "luxon": "~3.4.0" + } }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, "dependencies": { - "@types/node": "*" + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "peer": true, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dependencies": { - "@types/yargs-parser": "*" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "peer": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "peer": true, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, "dependencies": { - "event-target-shim": "^5.0.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=6.5" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "bin": { - "acorn": "bin/acorn" + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "node_modules/datastore-core": { + "version": "9.2.9", + "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-9.2.9.tgz", + "integrity": "sha512-wraWTPsbtdE7FFaVo3pwPuTB/zXsgwGGAm8BgBYwYAuzZCTS0MfXmd/HH1vR9s0/NFFjOVmBkGiWCvKxZ+QjVw==", + "dependencies": { + "@libp2p/logger": "^4.0.6", + "err-code": "^3.0.1", + "interface-datastore": "^8.0.0", + "interface-store": "^5.0.0", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-map": "^3.0.5", + "it-merge": "^3.0.3", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-sort": "^3.0.4", + "it-take": "^3.0.4" + } + }, + "node_modules/datastore-fs": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-9.1.8.tgz", + "integrity": "sha512-NM/f7/yHkgnVSiYJFPbKlEIJZ49sWz2XkvYY+Kl3bl/esQILzDt4uXUhNMRF4xfLTpQKcr+ZOfrArxVYsfnj7g==", + "dependencies": { + "datastore-core": "^9.0.0", + "fast-write-atomic": "^0.2.1", + "interface-datastore": "^8.0.0", + "interface-store": "^5.0.0", + "it-glob": "^2.0.6", + "it-map": "^3.0.5", + "it-parallel-batch": "^3.0.4" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true, "engines": { - "node": ">=0.4.0" + "node": "*" } }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", "peer": true }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "peer": true, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "peer": true, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/any-signal": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.1.1.tgz", - "integrity": "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA==", + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=0.10.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "peer": true - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-7.2.2.tgz", + "integrity": "sha512-AD7TrdNNPXRZIGw63dw+lnGmT4v7ggZC5NHNJgAYWm5njrwoze1q5JSAW9YuLy2tjnoLUG/r8FEB93MCh9QJPg==", + "dependencies": { + "execa": "^7.1.1" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "peer": true, "dependencies": { - "sprintf-js": "~1.0.2" + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "peer": true - }, - "node_modules/asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "node_modules/define-data-property": { + "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==", "dependencies": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ast-types": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", - "peer": true, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "dependencies": { - "tslib": "^2.0.1" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "peer": true, + "node_modules/delay": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", + "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", "engines": { - "node": ">=4" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", "peer": true }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "peer": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", - "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", "peer": true, "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.1", - "semver": "^6.3.1" + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=18" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", - "peer": true, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "path-type": "^4.0.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", - "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", - "peer": true, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1" + "@leichtgewicht/ip-codec": "^2.0.1" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "peer": true, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, - "node_modules/blockstore-core": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.1.tgz", - "integrity": "sha512-peXfL9ZLx1cb84QALocMjhT8CsQ4JsreI/AitlN1inipSdC/G+jcYVJCqeCD5ecSTv/0LMpg8NlAPH/eBYZLjA==", - "dependencies": { - "@libp2p/logger": "^4.0.6", - "err-code": "^3.0.1", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.0", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-merge": "^3.0.3", - "it-pushable": "^3.2.3", - "multiformats": "^13.0.1" + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.721", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", + "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" } }, - "node_modules/blockstore-fs": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-1.1.10.tgz", - "integrity": "sha512-Dg0mbdma0OY4NEk78efcAAiG5ZrMcIVrM7s+0e2p4uavnvrcBT6vDj5ITfnRfid3idKHOoCYShGEi9ENNgJg1A==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dependencies": { - "blockstore-core": "^4.0.0", - "fast-write-atomic": "^0.2.1", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.0", - "it-glob": "^2.0.6", - "it-map": "^3.0.5", - "it-parallel-batch": "^3.0.4", - "multiformats": "^13.0.1" + "once": "^1.4.0" } }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=6" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/envinfo": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "peer": true, + "bin": { + "envinfo": "dist/cli.js" + }, "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dependencies": { - "ms": "2.0.0" + "is-arrayish": "^0.2.1" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "peer": true, + "dependencies": { + "stackframe": "^1.3.4" + } }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "peer": true, "dependencies": { - "ee-first": "1.1.1" + "accepts": "~1.3.7", + "escape-html": "~1.0.3" }, "engines": { "node": ">= 0.8" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "dependencies": { - "fill-range": "^7.0.1" + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/browser-readablestream-to-it": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.5.tgz", - "integrity": "sha512-obLCT9jnxAeZlbaRWluUiZrcSJEoi2JkM0eoiJqlIP7MFwZwZjcB6giZvD343PXfr96ilD91M/wFqFvyAZq+Gg==" + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" + "es-errors": "^1.3.0" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.4" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "peer": true, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, "dependencies": { - "node-int64": "^0.4.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, "dependencies": { - "streamsearch": "^1.1.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=10.16.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "peer": true, + "node_modules/eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "peer": true, + "node_modules/eslint-plugin-jest": { + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", + "dev": true, "dependencies": { - "callsites": "^2.0.0" + "@typescript-eslint/utils": "^5.10.0" }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } } }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "peer": true, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, "dependencies": { - "caller-callsite": "^2.0.0" + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" }, "engines": { - "node": ">=4" + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" } }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "peer": true, - "engines": { - "node": ">=4" + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "peer": true, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, "engines": { - "node": ">=10" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001600", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", - "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" + "eslint-config-prettier": { + "optional": true } - ], - "peer": true - }, - "node_modules/cborg": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.2.0.tgz", - "integrity": "sha512-q6cFW5m3KxfP/9xGI3yGLaC1l5DP6DWM9IvjiJojnIwohL5CQDl02EXViPV852mOfQo+7PJGPN01MI87vFGzyA==", - "bin": { - "cborg": "lib/bin.js" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, "engines": { - "node": ">=8" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, "engines": { - "node": ">= 8.10.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://opencollective.com/eslint" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "peer": true, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=12.13.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", - "peer": true, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "node_modules/chromium-edge-launcher/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=10" + "node": ">=10.13.0" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "peer": true, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "peer": true, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "peer": true, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "peer": true, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui/node_modules/strip-ansi": { + "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "peer": true, + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5632,202 +8640,236 @@ "node": ">=8" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "peer": true, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { - "node": ">=0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "peer": true, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "peer": true, - "dependencies": { - "color-name": "~1.1.4" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=4" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "peer": true - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "peer": true - }, - "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "peer": true, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": "^12.20.0 || >=14" + "node": ">=0.10" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "peer": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "peer": true, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.6" + "node": ">=4.0" } }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "peer": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { - "node": ">= 0.8.0" + "node": ">=4.0" } }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, - "dependencies": { - "ms": "2.0.0" + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/compression/node_modules/ms": { + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-iterator": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", + "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "peer": true, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", "debug": "2.6.9", - "finalhandler": "1.1.2", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" } }, - "node_modules/connect/node_modules/debug": { + "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } }, - "node_modules/connect/node_modules/ms": { + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "safe-buffer": "5.2.1" + "ee-first": "1.1.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { + "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", @@ -5846,903 +8888,942 @@ } ] }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "peer": true - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", - "peer": true, - "dependencies": { - "browserslist": "^4.23.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "node": ">= 0.8" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "peer": true, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" }, "engines": { "node": ">=4" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", "dev": true }, - "node_modules/cron": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.6.tgz", - "integrity": "sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==", - "dependencies": { - "@types/luxon": "~3.3.0", - "luxon": "~3.4.0" - } + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=8.6.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, - "node_modules/datastore-core": { - "version": "9.2.9", - "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-9.2.9.tgz", - "integrity": "sha512-wraWTPsbtdE7FFaVo3pwPuTB/zXsgwGGAm8BgBYwYAuzZCTS0MfXmd/HH1vR9s0/NFFjOVmBkGiWCvKxZ+QjVw==", - "dependencies": { - "@libp2p/logger": "^4.0.6", - "err-code": "^3.0.1", - "interface-datastore": "^8.0.0", - "interface-store": "^5.0.0", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-map": "^3.0.5", - "it-merge": "^3.0.3", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-sort": "^3.0.4", - "it-take": "^3.0.4" - } + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, - "node_modules/datastore-fs": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-9.1.8.tgz", - "integrity": "sha512-NM/f7/yHkgnVSiYJFPbKlEIJZ49sWz2XkvYY+Kl3bl/esQILzDt4uXUhNMRF4xfLTpQKcr+ZOfrArxVYsfnj7g==", - "dependencies": { - "datastore-core": "^9.0.0", - "fast-write-atomic": "^0.2.1", - "interface-datastore": "^8.0.0", - "interface-store": "^5.0.0", - "it-glob": "^2.0.6", - "it-map": "^3.0.5", - "it-parallel-batch": "^3.0.4" + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" } }, - "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "peer": true + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/fast-write-atomic": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz", + "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" + }, + "node_modules/fast-xml-parser": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", + "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "peer": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "strnum": "^1.0.5" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "peer": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "bser": "2.1.1" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, "dependencies": { - "mimic-response": "^3.1.0" + "escape-string-regexp": "^1.0.5" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { - "node": ">=4.0.0" + "node": ">=0.8.0" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "peer": true, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/default-gateway": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-7.2.2.tgz", - "integrity": "sha512-AD7TrdNNPXRZIGw63dw+lnGmT4v7ggZC5NHNJgAYWm5njrwoze1q5JSAW9YuLy2tjnoLUG/r8FEB93MCh9QJPg==", + "node_modules/file-type": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.0.0.tgz", + "integrity": "sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==", "dependencies": { - "execa": "^7.1.1" + "readable-web-to-node-stream": "^3.0.2", + "strtok3": "^7.0.0", + "token-types": "^5.0.1" }, "engines": { - "node": ">= 16" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "peer": true, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dependencies": { - "clone": "^1.0.2" + "to-regex-range": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/define-data-property": { - "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==", + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "peer": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8" } }, - "node_modules/delay": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", - "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "peer": true }, - "node_modules/denque": { + "node_modules/find-cache-dir": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "peer": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, "engines": { - "node": ">=0.10" + "node": ">=6" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/deprecated-react-native-prop-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", - "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", - "peer": true, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, "dependencies": { - "@react-native/normalize-colors": "^0.73.0", - "invariant": "^2.2.4", - "prop-types": "^15.8.1" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=18" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/detect-browser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", - "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==" + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "peer": true + }, + "node_modules/flow-parser": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", + "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "peer": true, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, - "engines": { - "node": ">=0.3.1" + "dependencies": { + "is-callable": "^1.1.3" } }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.721", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", - "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==", - "peer": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "peer": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/envinfo": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", - "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", - "peer": true, - "bin": { - "envinfo": "dist/cli.js" - }, + "node_modules/freeport-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/freeport-promise/-/freeport-promise-2.0.0.tgz", + "integrity": "sha512-dwWpT1DdQcwrhmRwnDnPM/ZFny+FtzU+k50qF2eid3KxaQDsMiBrwo1i0G3qSugkN5db6Cb0zgfc68QeTOpEFg==", "engines": { - "node": ">=4" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "peer": true, - "dependencies": { - "is-arrayish": "^0.2.1" + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "peer": true, - "dependencies": { - "stackframe": "^1.3.4" - } + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "peer": true, "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">= 0.8" + "node": ">=6 <7 || >=8" } }, - "node_modules/es-define-property": { + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "peer": true, "engines": { - "node": ">=6" + "node": ">=6.9.0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "peer": true, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/event-iterator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", - "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==" + "node_modules/get-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", + "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "peer": true, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "engines": { - "node": ">=6" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" }, "engines": { - "node": ">= 0.10.0" + "node": ">=16" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node_modules/git-raw-commits/node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { - "ee-first": "1.1.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 6" } }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "dependencies": { + "ini": "4.1.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-write-atomic": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz", - "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "node_modules/fast-xml-parser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", - "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "peer": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "peer": true, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, "dependencies": { - "bser": "2.1.1" + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-type": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.0.0.tgz", - "integrity": "sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dependencies": { - "to-regex-range": "^5.0.1" + "get-intrinsic": "^1.1.3" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "peer": true, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gts": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gts/-/gts-5.3.0.tgz", + "integrity": "sha512-/V0nbaWLBv8g0v2kol5M7Vf+kHXk19Ew5sa3wQJXeUaccesXg7AFo7eEInoIpR+aIJ3QDjoYt4zHYeFr8w8rng==", + "dev": true, "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "@typescript-eslint/eslint-plugin": "5.62.0", + "@typescript-eslint/parser": "5.62.0", + "chalk": "^4.1.2", + "eslint": "8.53.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "5.1.3", + "execa": "^5.0.0", + "inquirer": "^7.3.3", + "json5": "^2.1.3", + "meow": "^9.0.0", + "ncp": "^2.0.0", + "prettier": "3.1.1", + "rimraf": "3.0.2", + "write-file-atomic": "^4.0.0" + }, + "bin": { + "gts": "build/src/cli.js" }, "engines": { - "node": ">= 0.8" + "node": ">=14" + }, + "peerDependencies": { + "typescript": ">=3" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, - "dependencies": { - "ms": "2.0.0" + "node_modules/gts/node_modules/@eslint/js": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "node_modules/gts/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "peer": true, + "node_modules/gts/node_modules/eslint": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "dev": true, "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/gts/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "peer": true, + "node_modules/gts/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "peer": true - }, - "node_modules/flow-parser": { - "version": "0.206.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", - "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", - "peer": true, + "node_modules/gts/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">=0.4.0" + "node": ">=10.13.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/gts/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/freeport-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/freeport-promise/-/freeport-promise-2.0.0.tgz", - "integrity": "sha512-dwWpT1DdQcwrhmRwnDnPM/ZFny+FtzU+k50qF2eid3KxaQDsMiBrwo1i0G3qSugkN5db6Cb0zgfc68QeTOpEFg==", + "node_modules/gts/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=10.17.0" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/gts/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "peer": true, + "node_modules/gts/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=6 <7 || >=8" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "peer": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/gts/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "peer": true, + "node_modules/gts/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "engines": { - "node": ">=6.9.0" + "node": ">=6" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "peer": true, + "node_modules/gts/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=8" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "node_modules/gts/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", - "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" + "node_modules/gts/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/gts/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { "node": ">=10" }, @@ -6750,69 +9831,82 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "node_modules/gts/node_modules/prettier": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "peer": true, + "node_modules/gts/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": "*" + "bin": { + "rimraf": "bin.js" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/gts/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "peer": true, + "node_modules/gts/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/gts/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gts/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "peer": true - }, "node_modules/hamt-sharding": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.6.tgz", @@ -6822,6 +9916,24 @@ "uint8arrays": "^5.0.1" } }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -6863,6 +9975,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hashlru": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", @@ -6918,6 +10045,12 @@ "multiformats": "^13.1.0" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true + }, "node_modules/hermes-estree": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", @@ -6945,6 +10078,18 @@ "node": ">=8" } }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6976,6 +10121,21 @@ "node": ">=14.18.0" } }, + "node_modules/husky": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "dev": true, + "bin": { + "husky": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7006,6 +10166,15 @@ } ] }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -7040,20 +10209,37 @@ "node": ">=4" } }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "peer": true, "engines": { "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "peer": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -7069,6 +10255,42 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/interface-blockstore": { "version": "5.2.10", "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.2.10.tgz", @@ -7092,6 +10314,20 @@ "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.8.tgz", "integrity": "sha512-7na81Uxkl0vqk0CBPO5PvyTkdaJBaezwUJGsMOz7riPOq0rJt+7W31iaopaMICWea/iykUsvNlPx/Tc+MxC3/w==" }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7185,11 +10421,38 @@ "uint8arrays": "^5.0.1" } }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "peer": true + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -7203,11 +10466,38 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "peer": true, "dependencies": { "hasown": "^2.0.0" }, @@ -7215,6 +10505,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -7288,54 +10608,187 @@ "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==" }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-network-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", "engines": { - "node": ">=16" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "peer": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "peer": true, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, "dependencies": { - "isobject": "^3.0.1" + "text-extensions": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-unicode-supported": { @@ -7350,6 +10803,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7675,6 +11140,24 @@ "npm": ">=7.0.0" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -7872,6 +11355,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/joi": { "version": "17.12.2", "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", @@ -7885,11 +11377,19 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "peer": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -7961,11 +11461,34 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "peer": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/json5": { "version": "2.2.3", @@ -7987,11 +11510,44 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8014,6 +11570,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/libp2p": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.3.1.tgz", @@ -8041,36 +11610,289 @@ "uint8arrays": "^5.0.2" } }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "peer": true, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", + "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", + "dev": true, + "dependencies": { + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", + "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, "dependencies": { - "ms": "2.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -8078,18 +11900,78 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "peer": true }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", "peer": true }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -8106,6 +11988,202 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/logkitty": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", @@ -8242,7 +12320,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -8255,7 +12332,6 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "peer": true, "bin": { "semver": "bin/semver" } @@ -8275,6 +12351,18 @@ "tmpl": "1.0.5" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/marky": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", @@ -8286,14 +12374,70 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "peer": true + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "peer": true + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } }, "node_modules/merge-descriptors": { "version": "1.0.1", @@ -8316,6 +12460,15 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -8432,6 +12585,21 @@ "node": ">=18" } }, + "node_modules/metro-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/metro-config": { "version": "0.80.8", "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.8.tgz", @@ -8667,6 +12835,21 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "peer": true }, + "node_modules/metro/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/metro/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -8713,7 +12896,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "peer": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -8775,6 +12957,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8794,6 +12985,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -8912,11 +13135,38 @@ "node": ">=8.0.0" } }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "dev": true, + "bin": { + "ncp": "bin/ncp" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8939,6 +13189,12 @@ "node": ">= 0.4.0" } }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node_modules/nocache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", @@ -9095,6 +13351,21 @@ "node": "*" } }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9103,6 +13374,213 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/npm-run-all/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -9159,6 +13637,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/observable-webworkers": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/observable-webworkers/-/observable-webworkers-2.0.1.tgz", @@ -9168,6 +13673,14 @@ "npm": ">=7.0.0" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -9232,6 +13745,23 @@ "node": ">=4" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -9302,6 +13832,15 @@ "node": ">=8" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-defer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", @@ -9331,7 +13870,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -9346,7 +13884,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -9400,7 +13937,27 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -9409,7 +13966,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -9430,7 +13986,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "peer": true, "engines": { "node": ">=8" } @@ -9439,7 +13994,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9455,14 +14009,47 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "peer": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/peek-readable": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", @@ -9478,8 +14065,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "peer": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9492,15 +14078,146 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "peer": true, "engines": { "node": ">=6" } }, + "node_modules/pino": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz", + "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-http": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-9.0.0.tgz", + "integrity": "sha512-Q9QDNEz0vQmbJtMFjOVr2c9yL92vHudjmr3s3m6J1hbw3DBGFZJm3TIj9TWyynZ4GEsEA9SOtni4heRUr6lNOg==", + "dependencies": { + "get-caller-file": "^2.0.5", + "pino": "^8.17.1", + "pino-std-serializers": "^6.2.2", + "process-warning": "^3.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", + "dev": true, + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -9568,6 +14285,15 @@ "node": ">=4" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prebuild-install": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", @@ -9593,6 +14319,15 @@ "node": ">=10" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", @@ -9608,6 +14343,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -9648,11 +14395,24 @@ "@types/yargs-parser": "*" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, "node_modules/progress-events": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.0.tgz", @@ -9738,6 +14498,15 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pvtsutils": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", @@ -9777,6 +14546,40 @@ "inherits": "~2.0.3" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/rabin-wasm": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", @@ -10006,6 +14809,101 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -10052,6 +14950,14 @@ "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", "peer": true }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/recast": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", @@ -10076,6 +14982,19 @@ "node": ">=0.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -10100,13 +15019,43 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "peer": true }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.8.4" + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/regexpu-core": { @@ -10151,7 +15100,15 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10166,7 +15123,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -10192,7 +15148,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "peer": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -10205,7 +15160,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, "engines": { "node": ">=6" } @@ -10214,7 +15168,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -10233,26 +15186,190 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "peer": true, + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10280,6 +15397,12 @@ "loose-envify": "^1.1.0" } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -10410,6 +15533,21 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -10450,7 +15588,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10542,7 +15679,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "peer": true, "engines": { "node": ">=8" } @@ -10588,6 +15724,14 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "peer": true }, + "node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -10621,6 +15765,46 @@ "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -10723,39 +15907,148 @@ } ] }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "peer": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "peer": true, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { @@ -10770,6 +16063,19 @@ "node": ">=6" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", @@ -10779,6 +16085,15 @@ "node": ">=6" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -10790,6 +16105,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -10841,7 +16168,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -10849,6 +16175,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -10965,12 +16307,44 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "peer": true }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "peer": true }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -11018,6 +16392,18 @@ "node": ">= 4.5.0" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -11028,7 +16414,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "peer": true, "engines": { "node": ">=4" } @@ -11085,6 +16470,15 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -11141,6 +16535,27 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -11152,6 +16567,18 @@ "node": "*" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -11182,6 +16609,79 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -11225,6 +16725,21 @@ "multiformats": "^13.0.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -11276,6 +16791,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -11323,6 +16850,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", @@ -11347,6 +16883,16 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", @@ -11430,17 +16976,51 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "peer": true }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -11453,11 +17033,40 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -11533,7 +17142,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "peer": true, "engines": { "node": ">=10" } @@ -11559,7 +17167,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -11577,7 +17184,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "peer": true, "engines": { "node": ">=12" } @@ -11595,7 +17201,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "peer": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 5eca614..b864864 100644 --- a/package.json +++ b/package.json @@ -5,23 +5,43 @@ "main": "index.js", "type": "module", "scripts": { - "build": "tsc", - "start": "cross-env NODE_OPTIONS=\"--loader=ts-node/esm\" node src/index.ts", - "dev": "nodemon", - "test": "echo \"Error: no test specified\" && exit 1" + "build": "npx rimraf dist; run-p -s build:*", + "build:main": "babel -x .ts -d dist src", + "build:types": "tsc", + "start": "node dist/index.js", + "dev": "npm run build; node dist/index.js", + "prepare": "husky", + "bump-patch": "npm version patch", + "bump-minor": "npm version minor", + "bump-major": "npm version major" }, "author": "ADAMANT Foundation ", "license": "GPLv3", "devDependencies": { + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-jest": "^27.6.0", + "gts": "^5.2.0", + "husky": "^9.0.11", + "lint-staged": "^15.2.2", "nodemon": "^3.1.0", + "pino-pretty": "^11.0.0", "prettier": "^3.2.5", + "rimraf": "^5.0.5", "ts-node": "^10.9.2", - "typescript": "^5.4.3" + "typescript": "^5.4.3", + "@babel/cli": "^7.22.15", + "@babel/preset-typescript": "^7.22.15", + "babel-plugin-add-import-extension": "^1.6.0", + "npm-run-all": "^4.1.5" }, "dependencies": { "@chainsafe/libp2p-gossipsub": "^13.0.0", @@ -53,7 +73,15 @@ "json5": "^2.2.3", "libp2p": "^1.3.1", "multer": "^1.4.5-lts.1", - "multiformats": "^13.1.0" + "multiformats": "^13.1.0", + "pino": "^8.20.0", + "pino-http": "^9.0.0" + }, + "lint-staged": { + "*.(ts|js)": [ + "eslint --fix", + "prettier --write" + ] }, "engines": { "node": ">=20.0.0" diff --git a/src/config.js b/src/config.js deleted file mode 100644 index 1de0642..0000000 --- a/src/config.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as fs from "fs"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; -import JSON5 from "json5"; - -const currDir = dirname(fileURLToPath(import.meta.url)); -const rootDir = join(currDir, ".."); - -const configName = process.argv.slice(2)[0] || "default"; -const configFileName = `config.${configName}.json5`; -console.log("Using config file:", configFileName); - -const configPath = rootDir + `/${configFileName}`; - -const config = JSON5.parse(fs.readFileSync(configPath, "utf8")); - -export default config; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..14a1059 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,18 @@ +import * as fs from 'fs' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' +import JSON5 from 'json5' +import { pino } from './utils/logger' + +const currDir = dirname(fileURLToPath(import.meta.url)) +const rootDir = join(currDir, '..') + +const configName = process.argv.slice(2)[0] || 'default' +const configFileName = `config.${configName}.json5` +pino.logger.debug('Using config file:', configFileName) + +const configPath = rootDir + `/${configFileName}` + +const config = JSON5.parse(fs.readFileSync(configPath, 'utf8')) + +export default config diff --git a/src/cron.js b/src/cron.js deleted file mode 100644 index db815ad..0000000 --- a/src/cron.js +++ /dev/null @@ -1,36 +0,0 @@ -import { CronJob } from "cron"; -import { helia } from "./helia.js"; -import { getNodesList } from "./utils.js"; - -/** - * Auto-peering between ADM IPFS nodes. - */ -export const autoPeering = new CronJob("*/10 * * * * *", autoPeeringHandler); - -export async function autoPeeringHandler() { - const peerId = helia.libp2p.peerId; - console.log( - `[Cron] Running "autoPeering" cronjob. Current node peerId: ${helia.libp2p.peerId}`, - ); - - const nodes = getNodesList([peerId.toString()]); - console.log( - "Peering nodes list:", - nodes.map((node) => node.name), - ); - - const successPeers = []; - - for await (const node of nodes) { - console.log(`Start peering ${node.name} node (${node.multiaddr})...`); - try { - const connection = await helia.libp2p.dial(node.multiaddr); - console.log(`Successfully peered with ${node.name}`); - successPeers.push(node.name); - } catch (err) { - console.log(`Peering with ${node.name} failed. Error:`, err.message); - } - } - - return successPeers; -} diff --git a/src/cron.ts b/src/cron.ts new file mode 100644 index 0000000..7d34cda --- /dev/null +++ b/src/cron.ts @@ -0,0 +1,36 @@ +import { CronJob } from 'cron' +import { helia } from './helia' +import { getNodesList } from './utils/utils' +import { pino } from './utils/logger' + +/** + * Auto-peering between ADM IPFS nodes. + */ +export const autoPeering = new CronJob('*/10 * * * * *', () => { + autoPeeringHandler().catch(pino.logger.error) +}) + +export async function autoPeeringHandler() { + const peerId = helia.libp2p.peerId + pino.logger.info( + `[Cron] Running "autoPeering" cronjob. Current node peerId: ${helia.libp2p.peerId}` + ) + + const nodes = getNodesList([peerId.toString()]) + pino.logger.info(`Peering nodes list: ${nodes.map((node) => node.name)}`) + + const successPeers: string[] = [] + + for await (const node of nodes) { + pino.logger.info(`Start peering ${node.name} node (${node.multiAddr})...`) + try { + await helia.libp2p.dial(node.multiAddr) + pino.logger.info(`Successfully peered with ${node.name}`) + successPeers.push(node.name) + } catch (err) { + pino.logger.info(`Peering with ${node.name} failed. Error: ${err.message}`) + } + } + + return successPeers +} diff --git a/src/dto/peer-id.dto.ts b/src/dto/peer-id.dto.ts new file mode 100644 index 0000000..5b73441 --- /dev/null +++ b/src/dto/peer-id.dto.ts @@ -0,0 +1,3 @@ +export interface PeerIdDto { + peerId: string +} diff --git a/src/helia.js b/src/helia.js deleted file mode 100644 index a01a64d..0000000 --- a/src/helia.js +++ /dev/null @@ -1,48 +0,0 @@ -import { bootstrap } from "@libp2p/bootstrap"; -import { createHelia } from "helia"; -import { blockstore, datastore } from "./store.js"; -import { getAllowNodesMultiaddrs } from "./utils.js"; - -export const helia = await createHelia({ - datastore, - blockstore, - libp2p: { - peerDiscovery: [ - bootstrap({ - list: [ - "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", - "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - ], - }), - ], - addresses: { - listen: ["/ip4/0.0.0.0/tcp/4001", "/ip6/::/tcp/4002", "/webrtc"], - }, - connectionManager: { - /** - * The total number of connections allowed to be open at one time - */ - // maxConnections: 10, - - /** - * If the number of open connections goes below this number, the node - * will try to connect to randomly selected peers from the peer store - */ - // minConnections: 5, - - /** - * How many connections can be open but not yet upgraded - */ - // maxIncomingPendingConnections: 10, - - /** - * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections - */ - allow: getAllowNodesMultiaddrs(), - }, - }, -}); diff --git a/src/helia.ts b/src/helia.ts new file mode 100644 index 0000000..3fc092f --- /dev/null +++ b/src/helia.ts @@ -0,0 +1,48 @@ +import { bootstrap } from '@libp2p/bootstrap' +import { createHelia } from 'helia' +import { blockstore, datastore } from './store' +import { getAllowNodesMultiaddrs } from './utils/utils' + +export const helia = await createHelia({ + datastore, + blockstore, + libp2p: { + peerDiscovery: [ + bootstrap({ + list: [ + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', + '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', + '/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ' + ] + }) + ], + addresses: { + listen: ['/ip4/0.0.0.0/tcp/4001', '/ip6/::/tcp/4002', '/webrtc'] + }, + connectionManager: { + /** + * The total number of connections allowed to be open at one time + */ + // maxConnections: 10, + + /** + * If the number of open connections goes below this number, the node + * will try to connect to randomly selected peers from the peer store + */ + // minConnections: 5, + + /** + * How many connections can be open but not yet upgraded + */ + // maxIncomingPendingConnections: 10, + + /** + * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections + */ + allow: getAllowNodesMultiaddrs() + } + } +}) diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 82c1c0c..0000000 --- a/src/index.js +++ /dev/null @@ -1,391 +0,0 @@ -import { unixfs } from "@helia/unixfs"; -import { peerIdFromString } from "@libp2p/peer-id"; -import { fileTypeFromBuffer } from "file-type"; -import { CID } from "multiformats/cid"; -import path from "node:path"; -import express from "express"; -import { fileURLToPath } from "url"; -import multer from "multer"; -import { multiaddr } from "@multiformats/multiaddr"; -import { createVerifiedFetch } from "@helia/verified-fetch"; -import { autoPeeringHandler } from "./cron.js"; -import { helia } from "./helia.js"; - -const verifiedFetch = await createVerifiedFetch(helia); -const storage = multer.memoryStorage(); -const upload = multer({ storage }); - -// console.log("Created Helia instance"); -// await helia.libp2p.services.dht.setMode("server"); -// console.log("Switched DHT to server mode"); - -helia.libp2p.getMultiaddrs().forEach((addr) => { - console.log(`Listening on ${addr.toString()}`); - - // @todo - // cron.autoPeering.start() -}); - -helia.libp2p.addEventListener("peer:discovery", (evt) => { - const peer = evt.detail; - if (logNewPeers) { - console.log("Discovered peer:", peer.id); - } -}); - -let logNewPeers = false; - -helia.libp2p.addEventListener("peer:connect", (evt) => { - const peerId = evt.detail; - - if (logNewPeers) { - console.log("Peer connected:", peerId); - } -}); - -helia.libp2p.addEventListener("peer:disconnect", (evt) => { - const peerId = evt.detail; - - if (logNewPeers) { - console.log("Peer disconnected:", peerId); - } -}); - -helia.libp2p.addEventListener("start", (event) => { - console.info("Libp2p node started"); -}); - -helia.libp2p.addEventListener("stop", (event) => { - console.info("Libp2p node stopped"); -}); - -const ifs = unixfs(helia); - -console.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`); - -// const cid = await ifs.addFile({ -// path: "/hello.txt", -// content: readFileSync("./files/hello.txt"), -// }); -// -// console.log("CID", cid); - -const PORT = 4000; -const app = express(); - -app.get("/", (req, res) => { - res.send("Hello World!"); -}); - -app.get("/routing/findProviders/:cid", async (req, res) => { - try { - const cid = CID.parse(req.params.cid); - - const providers = []; - for await (const provider of helia.routing.findProviders(cid)) { - console.log( - `Found provider of CID:${cid.toString()}, PeerId:${provider.id.toString()}`, - ); - providers.push(provider.id.toString()); - } - - res.send({ - providers, - }); - } catch (err) { - res.send({ - error: err.message, - }); - console.error(err); - } -}); - -app.get("/cron/autopeering", async (req, res) => { - const successPeers = await autoPeeringHandler(); - - res.send({ - peeredSuccessfullyTo: successPeers, - }); -}); - -app.get("/libp2p/services/ping", async (req, res) => { - try { - const peerId = peerIdFromString(req.query.peerId); - - const pong = await helia.libp2p.services.ping.ping(peerId); - - res.send({ - pong, - }); - } catch (err) { - res.send({ - error: err.message, - }); - } -}); - -app.get("/libp2p/peerStore", async (req, res) => { - const peerId = req.query.peerId; - - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [ - (peer) => { - if (!peerId) { - return true; - } - - return peer.id.toString() === peerId; - }, - ], - limit: 10, - }); - - res.send({ - length: peers.length, - peers: peers.map((peer) => { - return { - id: peer.id.toString(), - }; - }), - }); - } catch (err) { - res.send({ - error: err.message, - }); - } -}); - -app.get("/libp2p/peerInfo", async (req, res) => { - const peerId = req.query.peerId; - - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [(peer) => peer.id.toString() === peerId], - limit: 10, - }); - - res.send({ - length: peers.length, - peer: peers, - }); - } catch (err) { - res.send({ - error: err.message, - }); - } -}); - -app.get("/libp2p/dial", async (req, res) => { - let peerId; - let multiAddr; - try { - if (req.query.peerId) { - peerId = peerIdFromString(req.query.peerId); - } - - if (req.query.multiAddr) { - multiAddr = multiaddr(req.query.multiAddr); - } - } catch (err) { - console.log("Invalid peer ID:", err.message); - res.send({ - success: false, - error: "Invalid peer ID", - }); - return; - } - - if (multiAddr) { - console.log(`Peering by multiaddress: ${multiAddr}`); - } else if (peerId) { - console.log(`Peering by PeerID: ${peerId}`); - } - - try { - const connection = await helia.libp2p.dial(multiAddr || peerId); - res.send({ success: true, connection }); - } catch (err) { - console.log("Cannot dial peer", err.message); - - res.send({ - success: false, - error: err.message, - }); - console.log(err); - } -}); - -app.get("/libp2p/new-peers/log", async (req, res) => { - logNewPeers = !logNewPeers; - - res.send({ - logNewPeers, - }); -}); - -app.get("/libp2p/connections", async (req, res) => { - const peerId = req.query.peerId; - const connections = helia.libp2p.getConnections( - peerId ? peerIdFromString(peerId) : undefined, - ); - - res.send({ - length: connections.length, - connections, - }); -}); - -app.get("/libp2p/status", async (req, res) => { - res.send({ - status: helia.libp2p.status, - }); -}); - -app.get("/file/:cid", async (req, res) => { - const cid = CID.parse(req.params.cid); - - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error("Operation timed out")), 30000), - ); - - try { - const filePromise = await verifiedFetch(`ipfs://${cid}`, { - headers: req.headers, - }); - - const result = await Promise.race([filePromise, timeoutPromise]); - const data = Buffer.from(await result.arrayBuffer()); - - // If filePromise wins the race, send the file - const fileType = await fileTypeFromBuffer(data); - if (fileType) { - res.set("Content-Type", fileType.mime); - } - - res.send(data); - } catch (error) { - console.error(error); - if (error.message === "Operation timed out") { - res.status(408).send({ - error: "Can not find requested CID. Operation timed out.", - }); - } else { - res.status(500).send({ - error: "Internal Server Error. Check the logs for details.", - }); - } - } -}); - -app.get("/pins", async (req, res) => { - const pins = []; - - for await (const pin of helia.pins.ls()) { - console.log("PIN LS", pin); - pins.push(pin); - } - - res.send({ - pins, - }); -}); - -app.post("/pins/pin/:cid", async (req, res) => { - const cid = CID.parse(req.params.cid); - - try { - for await (const pin of helia.pins.add(cid)) { - console.log("PINNED", pin); - } - } catch (err) { - console.error("Error:", err.message); - res.statusCode = 500; - return res.send({ - error: err.message, - }); - } - - res.send({ - pinned: true, - cid: cid.toString(), - }); -}); - -app.get("/pins/isPinned/:cid", async (req, res) => { - const cid = CID.parse(req.params.cid); - - const isPinned = await helia.pins.isPinned(cid); - - res.send({ - cid: cid.toString(), - isPinned, - }); -}); - -app.post("/file/upload", upload.array("files", 5), async (req, res) => { - if (!req.files) { - res.statusCode = 400; - return res.send({ - error: "No file uploaded", - }); - } - console.log("req.files", req.files); - - const cids = []; - for (const file of req.files) { - console.log(`Adding ${file.originalname} to IPFS`); - - const { buffer, originalname } = file; - - const cid = await ifs.addFile({ - path: `/${originalname}`, - content: buffer, - }); - console.log("Successfully added file", cid.toString()); - cids.push(cid); - - const isPinned = await helia.pins.isPinned(cid); - if (isPinned) { - console.log("File already pinned", cid.toString()); - } else { - // Pin the file - for await (const pinned of helia.pins.add(cid)) { - console.log("Filed pinned", pinned); - } - } - - // Tell the network we can provide content for the passed CID - await helia.libp2p.services.dht.provide(cid); - console.log("Provided CID via DHT", cid.toString()); - - console.log(`Routing: Providing ${cid}`); - void helia.routing.provide(cid); - console.log("Routing: Provide DONE"); - } - - res.send({ - filesNames: req.files.map((file) => file.originalname), - cids: cids.map((cid) => cid.toString()), - }); -}); - -app.post("/test", async (req, res) => { - const textEncoder = new TextEncoder(); - const cid = await ifs.addFile({ - content: textEncoder.encode("Hello world asldgfhkasjdghsk;adjflkasgjd!"), - }); - - // @ts-ignore - for await (const event of helia.libp2p.services.dht.provide(cid)) { - console.log("PROVIDE", event); - } - - res.send({ - cid: cid.toString(), - }); -}); - -app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); -}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..3a7f3e9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,397 @@ +import { unixfs } from '@helia/unixfs' +import { peerIdFromString } from '@libp2p/peer-id' +import { fileTypeFromBuffer } from 'file-type' +import { CID } from 'multiformats/cid' +import express from 'express' +import multer from 'multer' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' +import { autoPeeringHandler, autoPeering } from './cron' +import { helia } from './helia' +import { Pin } from 'helia' +import { flatFiles } from './utils/utils' +import { Request, Response } from 'express' +import { PeerIdDto } from './dto/peer-id.dto' +import { createVerifiedFetch } from '@helia/verified-fetch' +import { pino } from './utils/logger' +import type { PingService } from '@libp2p/ping' +import { KadDHT } from '@libp2p/kad-dht' +import { PeerId } from '@libp2p/interface' +import { filestore } from './store' + +const verifiedFetch = await createVerifiedFetch(helia) +const upload = multer({ storage: filestore }) + +let logNewPeers = false + +helia.libp2p.getMultiaddrs().forEach((addr) => { + pino.logger.info(`Listening on ${addr.toString()}`) +}) + +helia.libp2p.addEventListener('peer:discovery', (evt) => { + const peer = evt.detail + if (logNewPeers) { + pino.logger.info(`Discovered peer: ${peer.id}`) + } +}) + +helia.libp2p.addEventListener('peer:connect', (evt) => { + const peerId = evt.detail + + if (logNewPeers) { + pino.logger.info(`Peer connected: ${peerId}`) + } +}) + +helia.libp2p.addEventListener('peer:disconnect', (evt) => { + const peerId = evt.detail + + if (logNewPeers) { + pino.logger.info(`Peer disconnected: ${peerId}`) + } +}) + +helia.libp2p.addEventListener('start', (event) => { + pino.logger.info('Libp2p node started') +}) + +helia.libp2p.addEventListener('stop', (event) => { + pino.logger.info('Libp2p node stopped') +}) + +const ifs = unixfs(helia) + +pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) + +autoPeering.start() + +const PORT = 4000 +const app = express() + +app.use(pino) + +app.get('/', (req, res) => { + res.send('IPFS node') +}) + +app.get('/routing/findProviders/:cid', async (req, res) => { + try { + const cid = CID.parse(req.params.cid) + + const providers: string[] = [] + for await (const provider of helia.routing.findProviders(cid)) { + pino.logger.info(`Found provider of CID:${cid.toString()}, PeerId:${provider.id.toString()}`) + providers.push(provider.id.toString()) + } + + res.send({ + providers + }) + } catch (err) { + res.send({ + error: err.message + }) + pino.logger.error(err) + } +}) + +app.get('/cron/autopeering', async (req, res) => { + const successPeers = await autoPeeringHandler() + + res.send({ + peeredSuccessfullyTo: successPeers + }) +}) + +app.get( + '/libp2p/services/ping', + async (req: Request, res: Response) => { + try { + const peerId = peerIdFromString(req.query.peerId || '') + const pingService = helia.libp2p.services.ping as PingService + const pong = await pingService.ping(peerId) + + res.send({ + pong + }) + } catch (err) { + res.send({ + error: err.message + }) + } + } +) + +app.get( + '/libp2p/peerStore', + async (req: Request, res: Response) => { + const peerId = req.query.peerId + + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [ + (peer) => { + if (!peerId) { + return true + } + + return peer.id.toString() === peerId + } + ], + limit: 10 + }) + + res.send({ + length: peers.length, + peers: peers.map((peer) => { + return { + id: peer.id.toString() + } + }) + }) + } catch (err) { + res.send({ + error: err.message + }) + } + } +) + +app.get('/libp2p/peerInfo', async (req: Request, res: Response) => { + const peerId = req.query.peerId + + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [(peer) => peer.id.toString() === peerId], + limit: 10 + }) + + res.send({ + length: peers.length, + peer: peers + }) + } catch (err) { + res.send({ + error: err.message + }) + } +}) + +app.get( + '/libp2p/dial', + async (req: Request, res: Response) => { + let peerId: PeerId + let multiAddr: Multiaddr + try { + if (req.query.peerId) { + peerId = peerIdFromString(req.query.peerId || '') + } + + if (req.query.multiAddr) { + multiAddr = multiaddr(req.query.multiAddr || '') + } + } catch (err) { + pino.logger.error('Invalid peer ID:' + err.message) + res.send({ + success: false, + error: 'Invalid peer ID' + }) + return + } + + if (multiAddr) { + pino.logger.info(`Peering by multiAddress: ${multiAddr}`) + } else if (peerId) { + pino.logger.info(`Peering by PeerID: ${peerId}`) + } + + try { + const connection = await helia.libp2p.dial(multiAddr || peerId) + res.send({ success: true, connection }) + } catch (err) { + pino.logger.warn(`Cannot dial peer: ${err.message}`) + + res.send({ + success: false, + error: err.message + }) + pino.logger.error(err) + } + } +) + +app.get('/libp2p/new-peers/log', async (req, res) => { + logNewPeers = !logNewPeers + + res.send({ + logNewPeers + }) +}) + +app.get( + '/libp2p/connections', + async (req: Request, res: Response) => { + const peerId = req.query.peerId?.toString() || '' + const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) + + res.send({ + length: connections.length, + connections + }) + } +) + +app.get('/libp2p/status', async (req, res) => { + res.send({ + status: helia.libp2p.status + }) +}) + +app.get('/file/:cid', async (req, res) => { + const cid = CID.parse(req.params.cid) + + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Operation timed out')), 30000) + ) + + try { + const filePromise = await verifiedFetch(`ipfs://${cid}`, { + headers: req.headers as Record + }) + + const result = await Promise.race([filePromise, timeoutPromise]) + const data = Buffer.from(await result.arrayBuffer()) + + // If filePromise wins the race, send the file + const fileType = await fileTypeFromBuffer(data) + if (fileType) { + res.set('Content-Type', fileType.mime) + } + + res.send(data) + } catch (error) { + pino.logger.error(error) + if (error.message === 'Operation timed out') { + res.status(408).send({ + error: 'Can not find requested CID. Operation timed out.' + }) + } else { + res.status(500).send({ + error: 'Internal Server Error. Check the logs for details.' + }) + } + } +}) + +app.get('/pins', async (req, res) => { + const pins: Pin[] = [] + + for await (const pin of helia.pins.ls()) { + pino.logger.info('PIN LS', pin) + pins.push(pin) + } + + res.send({ + pins + }) +}) + +app.post('/pins/pin/:cid', async (req, res) => { + const cid = CID.parse(req.params.cid) + + try { + for await (const pin of helia.pins.add(cid)) { + pino.logger.info('PINNED', pin) + } + } catch (err) { + pino.logger.error(`Error: ${err.message}`) + res.statusCode = 500 + return res.send({ + error: err.message + }) + } + + res.send({ + pinned: true, + cid: cid.toString() + }) +}) + +app.get('/pins/isPinned/:cid', async (req, res) => { + const cid = CID.parse(req.params.cid) + + const isPinned = await helia.pins.isPinned(cid) + + res.send({ + cid: cid.toString(), + isPinned + }) +}) + +app.post('/file/upload', upload.array('files', 5), async (req, res) => { + if (!req.files) { + res.statusCode = 400 + return res.send({ + error: 'No file uploaded' + }) + } + const files = flatFiles(req.files) + pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) + + const cids: CID[] = [] + for (const file of files) { + console.log(`Adding ${file.originalname} to IPFS`) + + const { buffer, originalname } = file + + const cid = await ifs.addFile({ + path: `/${originalname}`, + content: buffer + }) + pino.logger.info(`Successfully added file ${cid}`) + cids.push(cid) + + const isPinned = await helia.pins.isPinned(cid) + if (isPinned) { + pino.logger.info(`File already pinned ${cid}`) + } else { + // Pin the file + for await (const pinned of helia.pins.add(cid)) { + pino.logger.info(`Filed pinned: ${pinned}`) + } + } + + // Tell the network we can provide content for the passed CID + const dht = helia.libp2p.services.dht as KadDHT + await dht.provide(cid) + pino.logger.info(`Provided CID via DHT ${cid}`) + + pino.logger.info(`Routing: Providing ${cid}`) + void helia.routing.provide(cid) + pino.logger.info('Routing: Provide DONE') + } + + res.send({ + filesNames: files.map((file) => file.originalname), + cids: cids.map((cid) => cid.toString()) + }) +}) + +app.post('/test', async (req, res) => { + const textEncoder = new TextEncoder() + const cid = await ifs.addFile({ + content: textEncoder.encode('Hello world asldgfhkasjdghsk;adjflkasgjd!') + }) + + const dht = helia.libp2p.services.dht as KadDHT + for await (const event of dht.provide(cid)) { + pino.logger.info('PROVIDE', event) + } + + res.send({ + cid: cid.toString() + }) +}) + +app.listen(PORT, () => { + pino.logger.info(`Server is running on http://localhost:${PORT}`) +}) diff --git a/src/libp2p.js b/src/libp2p.js deleted file mode 100644 index e1d794d..0000000 --- a/src/libp2p.js +++ /dev/null @@ -1,54 +0,0 @@ -import { noise } from "@chainsafe/libp2p-noise"; -import { yamux } from "@chainsafe/libp2p-yamux"; -import { bootstrap } from "@libp2p/bootstrap"; -import { identify } from "@libp2p/identify"; -import { kadDHT, removePrivateAddressesMapper } from "@libp2p/kad-dht"; -import { tcp } from "@libp2p/tcp"; -import { createLibp2p } from "libp2p"; -import { createFromJSON } from "@libp2p/peer-id-factory"; -import { datastore } from "./store.js"; -import config from "./config.js"; - -/* const peerId = await createFromJSON(config.peer); -console.log("PeerID:", peerId.toString()); */ - -export const libp2p_old = await createLibp2p({ - peerId, - datastore, - addresses: { - listen: [ - // "/ip4/127.0.0.1/tcp/4001", - "/ip4/0.0.0.0/tcp/4001", - ], - }, - transports: [tcp()], - connectionEncryption: [noise()], - streamMuxers: [yamux()], - peerDiscovery: [ - bootstrap({ - list: [ - "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", - ], - }), - ], - services: { - identify: identify(), - dht: kadDHT({}), - }, -}); - -export const libp2p = await createLibp2p({ - addresses: { - listen: ["/ip4/127.0.0.1/tcp/4001", "/ip4/0.0.0.0/tcp/4001"], - }, - transports: [tcp()], - services: { - aminoDHT: kadDHT({ - protocol: "/ipfs/kad/1.0.0", - peerInfoMapper: removePrivateAddressesMapper, - }), - }, -}); diff --git a/src/store.js b/src/store.js deleted file mode 100644 index 527e82d..0000000 --- a/src/store.js +++ /dev/null @@ -1,5 +0,0 @@ -import { FsBlockstore } from 'blockstore-fs'; -import { FsDatastore } from 'datastore-fs'; - -export const blockstore = new FsBlockstore("./blockstore"); -export const datastore = new FsDatastore('./datastore'); diff --git a/src/store.ts b/src/store.ts new file mode 100644 index 0000000..ac3a061 --- /dev/null +++ b/src/store.ts @@ -0,0 +1,26 @@ +import { FsBlockstore } from 'blockstore-fs' +import { FsDatastore } from 'datastore-fs' +import { homedir } from 'os' +import { existsSync, mkdirSync } from 'fs' +import { join } from 'path' +import config from './config' +import multer from 'multer' + +const userHomeDir = homedir() + +export const blockstore = new FsBlockstore(join(userHomeDir, config.storeFolder, 'blockstore')) +export const datastore = new FsDatastore(join(userHomeDir, config.storeFolder, 'datastore')) + +const filestorePath = join(userHomeDir, config.storeFolder, 'filestore') +if (!existsSync(filestorePath)) { + mkdirSync(filestorePath) +} +export const filestore = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, filestorePath) + }, + filename: function (req, file, cb) { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9) + cb(null, file.fieldname + '-' + uniqueSuffix) + } +}) diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 374b80f..0000000 --- a/src/utils.js +++ /dev/null @@ -1,44 +0,0 @@ -import { peerIdFromString } from "@libp2p/peer-id"; -import { multiaddr } from "@multiformats/multiaddr"; -import config from "./config.js"; - -/** - * Get peerId from multiaddr string - * @param multiaddr E.g. "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ" - * @return Return instance of PeerId, e.g. "PeerId(12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ)" - */ -export function peerIdFromMultiaddr(multiaddr) { - // @todo parse multiaddrs like this: - // "/ip4/38.143.66.227/tcp/4001/p2p/12D3KooWCqayYQ5B8bF6NAaDgbZRcE9eyQFEdSYGjoSadBt1oTVT/p2p-circuit" - return peerIdFromString(multiaddr.split("/").pop()); -} - -export function parseNodes() { - return config.nodes.map((node) => ({ - name: node.name, - multiaddr: multiaddr(node.multiaddr), - peerId: peerIdFromMultiaddr(node.multiaddr), - })); -} - -/** - * Get a list of own IPFS nodes from the config file - */ -export function getNodesList(excludedPeerIds = []) { - return parseNodes().filter( - (node) => !excludedPeerIds.includes(node.peerId.toString()) - ); -} - -/** - * Get node name by `multiaddr` - */ -export function getNodeName(multiaddr) { - return parseNodes().find( - (node) => node.multiaddr.toString() === multiaddr.toString(), - )?.name; -} - -export function getAllowNodesMultiaddrs() { - return parseNodes().map((node) => node.multiaddr.toString()); -} diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..0d9063c --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,12 @@ +import { pinoHttp } from 'pino-http' + +export const pino = pinoHttp({ + transport: { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'dd.mm.yy HH:MM:ss Z', + ignore: 'pid,hostname' + } + } +}) diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..30d8f3c --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,8 @@ +import { Multiaddr } from '@multiformats/multiaddr' +import { PeerId } from '@libp2p/interface' + +export type NodeWithPeerId = { + name: string + multiAddr: Multiaddr + peerId: PeerId +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..4a13fea --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,66 @@ +import { peerIdFromString } from '@libp2p/peer-id' +import { multiaddr } from '@multiformats/multiaddr' +import config from '../config' +import { NodeWithPeerId } from './types' + +/** + * Get peerId from multiaddr string + * @param multiAddr E.g. "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ" + * @return Return instance of PeerId, e.g. "PeerId(12D3KooWBiqmfA32nZ2KGa8tsqigVpdY9oJB7GfrXvfgySDajpiZ)" + */ +export function peerIdFromMultiaddr(multiAddr: string) { + const addrParts = multiAddr.split('/') + let addr + // parse multiAddr like this: "/ip4/38.143.66.227/tcp/4001/p2p/12D3KooWCqayYQ5B8bF6NAaDgbZRcE9eyQFEdSYGjoSadBt1oTVT/p2p-circuit" + addrParts.forEach((part, index) => { + if (part === 'p2p') { + addr = addrParts[index + 1] ? addrParts[index + 1] : addr + } + }) + if (!addr) { + throw new Error('Invalid multiAddr') + } + return peerIdFromString(addr) +} + +export function parseNodes(): NodeWithPeerId[] { + return config.nodes.map((node) => ({ + name: node.name, + multiAddr: multiaddr(node.multiAddr), + peerId: peerIdFromMultiaddr(node.multiAddr) + })) +} + +/** + * Get a list of own IPFS nodes from the config file + */ +export function getNodesList(excludedPeerIds: string[] = []) { + return parseNodes().filter((node) => !excludedPeerIds.includes(node.peerId.toString())) +} + +/** + * Get node name by `multiAddr` + */ +export function getNodeName(multiAddr: string) { + return parseNodes().find((node) => node.multiAddr.toString() === multiAddr.toString())?.name +} + +export function getAllowNodesMultiaddrs() { + return parseNodes().map((node) => node.multiAddr.toString()) +} + +export function flatFiles( + files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] +) { + let resultFiles: Express.Multer.File[] = [] + if (Array.isArray(files)) { + return files + } else { + for (const filename in files) { + if (Object.prototype.hasOwnProperty.call(files, filename)) { + resultFiles = [...resultFiles, ...files[filename]] + } + } + return resultFiles + } +} diff --git a/tsconfig.json b/tsconfig.json index 7642e08..2e9899b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,20 @@ { "compilerOptions": { + "allowJs": false, + "checkJs": false, + "declaration": true, + "declarationMap": false, + "emitDeclarationOnly": true, + "lib": ["ES2022", "DOM"], "module": "ES2022", - "target": "ES2021", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - // only for development - "strict": false, - "strictNullChecks": true, - "skipLibCheck": true - } + "moduleResolution": "Bundler", + "sourceMap": true, + "target": "ES2022", + "noImplicitAny": false, + "useUnknownInCatchVariables": false, + "skipLibCheck": true, + "outDir": "./dist" + }, + "exclude": ["node_modules"], + "include": ["src/index.ts", "src/**/*.ts"] } From b908fa7406ba015b9109e9ea31b640d4309e665e Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Wed, 1 May 2024 17:05:51 +0400 Subject: [PATCH 019/167] feat: remove babel and other fixes --- .babelrc | 15 ------ nodemon.json | 8 ++-- package-lock.json | 116 ++++++++++++--------------------------------- package.json | 9 +--- src/config.ts | 2 +- src/cron.ts | 6 +-- src/helia.ts | 4 +- src/index.ts | 22 +++++---- src/store.ts | 2 +- src/utils/types.ts | 5 ++ src/utils/utils.ts | 8 ++-- tsconfig.json | 10 ++-- 12 files changed, 69 insertions(+), 138 deletions(-) delete mode 100644 .babelrc diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d1f2f5a..0000000 --- a/.babelrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - [ - "@babel/env", - { - "modules": false, - "targets": { - "node": "18" - } - } - ], - "@babel/typescript" - ], - "plugins": ["add-import-extension"] -} diff --git a/nodemon.json b/nodemon.json index dcb0655..8f48bf4 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,6 +1,6 @@ { - "watch": ["src"], - "ext": "js,ts,json", - "ignore": ["src/**/*.spec.ts"], - "exec": "node src/index.js" + "watch": ["dist"], + "ext": "js, json", + "ignore": ["dist/**/*.spec.js"], + "exec": "node dist/index.js" } diff --git a/package-lock.json b/package-lock.json index 40972e6..bbc97a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,8 +43,6 @@ "pino-http": "^9.0.0" }, "devDependencies": { - "@babel/cli": "^7.22.15", - "@babel/preset-typescript": "^7.22.15", "@commitlint/cli": "^19.2.1", "@commitlint/config-conventional": "^19.1.0", "@swc/core": "1.3.18", @@ -52,7 +50,6 @@ "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", - "babel-plugin-add-import-extension": "^1.6.0", "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-config-google": "^0.14.0", @@ -135,63 +132,6 @@ "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, - "node_modules/@babel/cli": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.24.1.tgz", - "integrity": "sha512-HbmrtxyFUr34LwAlV9jS+sSIjUp4FpdtIMGwgufY3AsxrIfsh/HxlMTywsONAZsU0RMYbZtbZFpUCrSGs7o0EA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "commander": "^4.0.1", - "convert-source-map": "^2.0.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - }, - "bin": { - "babel": "bin/babel.js", - "babel-external-helpers": "bin/babel-external-helpers.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/cli/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@babel/cli/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@babel/cli/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", @@ -281,6 +221,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -344,6 +285,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -366,6 +308,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -416,6 +359,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -424,6 +368,7 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "peer": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" @@ -448,6 +393,7 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "peer": true, "dependencies": { "@babel/types": "^7.23.0" }, @@ -459,6 +405,7 @@ "version": "7.24.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "peer": true, "dependencies": { "@babel/types": "^7.24.0" }, @@ -470,6 +417,7 @@ "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -488,6 +436,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -499,6 +448,7 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -524,6 +474,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.23.0", @@ -540,6 +491,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -551,6 +503,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -562,6 +515,7 @@ "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -573,6 +527,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -589,6 +544,7 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -684,6 +640,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "peer": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -1043,6 +1000,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -1159,6 +1117,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -1561,6 +1520,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.24.0", @@ -2005,6 +1965,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz", "integrity": "sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-create-class-features-plugin": "^7.24.1", @@ -2219,6 +2180,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", @@ -2280,6 +2242,7 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "peer": true, "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/parser": "^7.24.0", @@ -2314,6 +2277,7 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -4485,13 +4449,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "dev": true, - "optional": true - }, "node_modules/@noble/ciphers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", @@ -6552,18 +6509,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/babel-plugin-add-import-extension": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-add-import-extension/-/babel-plugin-add-import-extension-1.6.0.tgz", - "integrity": "sha512-JVSQPMzNzN/S4wPRoKQ7+u8PlkV//BPUMnfWVbr63zcE+6yHdU2Mblz10Vf7qe+6Rmu4svF5jG7JxdcPi9VvKg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", @@ -7576,7 +7521,8 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "peer": true }, "node_modules/cookie": { "version": "0.6.0", @@ -9268,12 +9214,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -12320,6 +12260,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -12332,6 +12273,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "peer": true, "bin": { "semver": "bin/semver" } @@ -14094,6 +14036,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "peer": true, "engines": { "node": ">=6" } @@ -16414,6 +16357,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "peer": true, "engines": { "node": ">=4" } diff --git a/package.json b/package.json index b864864..9b464f5 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,9 @@ "main": "index.js", "type": "module", "scripts": { - "build": "npx rimraf dist; run-p -s build:*", - "build:main": "babel -x .ts -d dist src", - "build:types": "tsc", + "build": "npx rimraf dist; tsc", "start": "node dist/index.js", - "dev": "npm run build; node dist/index.js", + "dev": "npm run build; nodemon", "prepare": "husky", "bump-patch": "npm version patch", "bump-minor": "npm version minor", @@ -38,9 +36,6 @@ "rimraf": "^5.0.5", "ts-node": "^10.9.2", "typescript": "^5.4.3", - "@babel/cli": "^7.22.15", - "@babel/preset-typescript": "^7.22.15", - "babel-plugin-add-import-extension": "^1.6.0", "npm-run-all": "^4.1.5" }, "dependencies": { diff --git a/src/config.ts b/src/config.ts index 14a1059..5ae2a53 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,7 +2,7 @@ import * as fs from 'fs' import { fileURLToPath } from 'url' import { dirname, join } from 'path' import JSON5 from 'json5' -import { pino } from './utils/logger' +import { pino } from './utils/logger.js' const currDir = dirname(fileURLToPath(import.meta.url)) const rootDir = join(currDir, '..') diff --git a/src/cron.ts b/src/cron.ts index 7d34cda..358e299 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -1,7 +1,7 @@ import { CronJob } from 'cron' -import { helia } from './helia' -import { getNodesList } from './utils/utils' -import { pino } from './utils/logger' +import { helia } from './helia.js' +import { getNodesList } from './utils/utils.js' +import { pino } from './utils/logger.js' /** * Auto-peering between ADM IPFS nodes. diff --git a/src/helia.ts b/src/helia.ts index 3fc092f..a62ba56 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,7 +1,7 @@ import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' -import { blockstore, datastore } from './store' -import { getAllowNodesMultiaddrs } from './utils/utils' +import { blockstore, datastore } from './store.js' +import { getAllowNodesMultiaddrs } from './utils/utils.js' export const helia = await createHelia({ datastore, diff --git a/src/index.ts b/src/index.ts index 3a7f3e9..759053b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,18 +5,18 @@ import { CID } from 'multiformats/cid' import express from 'express' import multer from 'multer' import { Multiaddr, multiaddr } from '@multiformats/multiaddr' -import { autoPeeringHandler, autoPeering } from './cron' -import { helia } from './helia' +import { autoPeeringHandler, autoPeering } from './cron.js' +import { helia } from './helia.js' import { Pin } from 'helia' -import { flatFiles } from './utils/utils' +import { flatFiles } from './utils/utils.js' import { Request, Response } from 'express' -import { PeerIdDto } from './dto/peer-id.dto' +import { PeerIdDto } from './dto/peer-id.dto.js' import { createVerifiedFetch } from '@helia/verified-fetch' -import { pino } from './utils/logger' +import { pino } from './utils/logger.js' import type { PingService } from '@libp2p/ping' import { KadDHT } from '@libp2p/kad-dht' import { PeerId } from '@libp2p/interface' -import { filestore } from './store' +import { filestore } from './store.js' const verifiedFetch = await createVerifiedFetch(helia) const upload = multer({ storage: filestore }) @@ -179,8 +179,8 @@ app.get('/libp2p/peerInfo', async (req: Request, app.get( '/libp2p/dial', async (req: Request, res: Response) => { - let peerId: PeerId - let multiAddr: Multiaddr + let peerId: PeerId | undefined + let multiAddr: Multiaddr | undefined try { if (req.query.peerId) { peerId = peerIdFromString(req.query.peerId || '') @@ -205,7 +205,11 @@ app.get( } try { - const connection = await helia.libp2p.dial(multiAddr || peerId) + const peer = multiAddr || peerId + if (!peer) { + throw new Error('Empty peerId and MultiAddr') + } + const connection = await helia.libp2p.dial(peer) res.send({ success: true, connection }) } catch (err) { pino.logger.warn(`Cannot dial peer: ${err.message}`) diff --git a/src/store.ts b/src/store.ts index ac3a061..524d1c6 100644 --- a/src/store.ts +++ b/src/store.ts @@ -3,7 +3,7 @@ import { FsDatastore } from 'datastore-fs' import { homedir } from 'os' import { existsSync, mkdirSync } from 'fs' import { join } from 'path' -import config from './config' +import config from './config.js' import multer from 'multer' const userHomeDir = homedir() diff --git a/src/utils/types.ts b/src/utils/types.ts index 30d8f3c..6044526 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -6,3 +6,8 @@ export type NodeWithPeerId = { multiAddr: Multiaddr peerId: PeerId } + +export type ConfigNode = { + name: string + multiAddr: string +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4a13fea..90d878a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,7 +1,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' -import config from '../config' -import { NodeWithPeerId } from './types' +import config from '../config.js' +import { ConfigNode, NodeWithPeerId } from './types.js' /** * Get peerId from multiaddr string @@ -10,7 +10,7 @@ import { NodeWithPeerId } from './types' */ export function peerIdFromMultiaddr(multiAddr: string) { const addrParts = multiAddr.split('/') - let addr + let addr: string | undefined // parse multiAddr like this: "/ip4/38.143.66.227/tcp/4001/p2p/12D3KooWCqayYQ5B8bF6NAaDgbZRcE9eyQFEdSYGjoSadBt1oTVT/p2p-circuit" addrParts.forEach((part, index) => { if (part === 'p2p') { @@ -24,7 +24,7 @@ export function peerIdFromMultiaddr(multiAddr: string) { } export function parseNodes(): NodeWithPeerId[] { - return config.nodes.map((node) => ({ + return config.nodes.map((node: ConfigNode) => ({ name: node.name, multiAddr: multiaddr(node.multiAddr), peerId: peerIdFromMultiaddr(node.multiAddr) diff --git a/tsconfig.json b/tsconfig.json index 2e9899b..2d609d8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,15 +2,13 @@ "compilerOptions": { "allowJs": false, "checkJs": false, - "declaration": true, - "declarationMap": false, - "emitDeclarationOnly": true, "lib": ["ES2022", "DOM"], - "module": "ES2022", - "moduleResolution": "Bundler", + "module": "NodeNext", + "moduleResolution": "NodeNext", "sourceMap": true, "target": "ES2022", - "noImplicitAny": false, + "strict": true, + "noImplicitAny": true, "useUnknownInCatchVariables": false, "skipLibCheck": true, "outDir": "./dist" From 58976f8aa592d0888a151ed8d1a6f90498b3a3f4 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Wed, 1 May 2024 20:44:49 +0400 Subject: [PATCH 020/167] fix: loglevel, creation store folder and etc --- .gitignore | 2 - config.default.json5 | 3 +- package-lock.json | 1268 +----------------------------------------- package.json | 4 +- src/config.ts | 2 - src/store.ts | 2 +- src/utils/logger.ts | 3 + 7 files changed, 30 insertions(+), 1254 deletions(-) diff --git a/.gitignore b/.gitignore index 7cbe395..a3bc1f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ node_modules -blockstore/ -datastore/ .idea dist/ diff --git a/config.default.json5 b/config.default.json5 index 68948ad..797100f 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -13,5 +13,6 @@ multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" } ], - storeFolder: '.adm-ipfs' + storeFolder: '.adm-ipfs', + logLevel: 'debug' } diff --git a/package-lock.json b/package-lock.json index bbc97a7..cd0c0f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,11 +58,9 @@ "husky": "^9.0.11", "lint-staged": "^15.2.2", "nodemon": "^3.1.0", - "npm-run-all": "^4.1.5", "pino-pretty": "^11.0.0", "prettier": "^3.2.5", "rimraf": "^5.0.5", - "ts-node": "^10.9.2", "typescript": "^5.4.3" }, "engines": { @@ -3039,18 +3037,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3853,6 +3839,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "peer": true, "engines": { "node": ">=6.0.0" } @@ -3889,17 +3876,8 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "peer": true }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", @@ -5692,30 +5670,6 @@ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -6226,15 +6180,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6349,12 +6294,6 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -6364,22 +6303,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -6400,28 +6323,6 @@ "node": ">=8" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -6485,21 +6386,6 @@ "node": ">=8.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -7570,12 +7456,6 @@ "node": ">=4" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "node_modules/cron": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.6.tgz", @@ -7628,57 +7508,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/datastore-core": { "version": "9.2.9", "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-9.2.9.tgz", @@ -7852,23 +7681,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delay": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", @@ -7938,15 +7750,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -8088,66 +7891,6 @@ "node": ">= 0.8" } }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -8167,49 +7910,6 @@ "node": ">= 0.4" } }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -9133,15 +8833,6 @@ "node": ">=0.4.0" } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -9227,33 +8918,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9317,23 +8981,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -9432,21 +9079,6 @@ "node": ">=4" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -9481,7 +9113,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "peer": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -9865,15 +9498,6 @@ "node": ">=6" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -9915,21 +9539,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hashlru": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", @@ -10254,20 +9863,6 @@ "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.8.tgz", "integrity": "sha512-7na81Uxkl0vqk0CBPO5PvyTkdaJBaezwUJGsMOz7riPOq0rJt+7W31iaopaMICWea/iykUsvNlPx/Tc+MxC3/w==" }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -10361,39 +9956,11 @@ "uint8arrays": "^5.0.1" } }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -10406,34 +9973,6 @@ "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -10445,36 +9984,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -10548,18 +10057,6 @@ "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==" }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-network-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", @@ -10579,21 +10076,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -10632,37 +10114,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", @@ -10674,36 +10125,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-text-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", @@ -10716,21 +10137,6 @@ "node": ">=8" } }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -10743,18 +10149,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -11410,7 +10804,8 @@ "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "peer": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11805,30 +11200,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -12278,12 +11649,6 @@ "semver": "bin/semver" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -12325,15 +11690,6 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "peer": true }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/meow": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", @@ -13131,12 +12487,6 @@ "node": ">= 0.4.0" } }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "node_modules/nocache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", @@ -13290,239 +12640,32 @@ "nopt": "bin/nopt.js" }, "engines": { - "node": "*" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/npm-run-all/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npm-run-all/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/npm-run-all/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/npm-run-all/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" + "node": "*" } }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "dependencies": { - "shebang-regex": "^1.0.0" + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "engines": { "node": ">=0.10.0" } }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -13579,33 +12722,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/observable-webworkers": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/observable-webworkers/-/observable-webworkers-2.0.1.tgz", @@ -13908,6 +13024,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -14228,15 +13345,6 @@ "node": ">=4" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/prebuild-install": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", @@ -14971,24 +14079,6 @@ "@babel/runtime": "^7.8.4" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -15259,52 +14349,11 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safe-stable-stringify": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", @@ -15476,21 +14525,6 @@ "node": ">= 0.4" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -15531,6 +14565,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -15927,73 +14962,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", - "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -16028,15 +14996,6 @@ "node": ">=6" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -16431,49 +15390,6 @@ "utf8-byte-length": "^1.0.1" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -16553,79 +15469,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -16669,21 +15512,6 @@ "multiformats": "^13.0.0" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -16821,12 +15649,6 @@ "node": ">= 0.4.0" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -16920,47 +15742,12 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "peer": true }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -17132,15 +15919,6 @@ "node": ">=12" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 9b464f5..421a966 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,7 @@ "pino-pretty": "^11.0.0", "prettier": "^3.2.5", "rimraf": "^5.0.5", - "ts-node": "^10.9.2", - "typescript": "^5.4.3", - "npm-run-all": "^4.1.5" + "typescript": "^5.4.3" }, "dependencies": { "@chainsafe/libp2p-gossipsub": "^13.0.0", diff --git a/src/config.ts b/src/config.ts index 5ae2a53..e368196 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,14 +2,12 @@ import * as fs from 'fs' import { fileURLToPath } from 'url' import { dirname, join } from 'path' import JSON5 from 'json5' -import { pino } from './utils/logger.js' const currDir = dirname(fileURLToPath(import.meta.url)) const rootDir = join(currDir, '..') const configName = process.argv.slice(2)[0] || 'default' const configFileName = `config.${configName}.json5` -pino.logger.debug('Using config file:', configFileName) const configPath = rootDir + `/${configFileName}` diff --git a/src/store.ts b/src/store.ts index 524d1c6..5aad84f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -13,7 +13,7 @@ export const datastore = new FsDatastore(join(userHomeDir, config.storeFolder, ' const filestorePath = join(userHomeDir, config.storeFolder, 'filestore') if (!existsSync(filestorePath)) { - mkdirSync(filestorePath) + mkdirSync(filestorePath, { recursive: true }) } export const filestore = multer.diskStorage({ destination: function (req, file, cb) { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 0d9063c..63804af 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,6 +1,9 @@ import { pinoHttp } from 'pino-http' +import config from '../config.js' export const pino = pinoHttp({ + level: config.logLevel || 'info', + useLevel: 'debug', transport: { target: 'pino-pretty', options: { From 5f4dfdc077782a4c910dc3765d77e9afe6c17685 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Thu, 2 May 2024 00:38:55 +0400 Subject: [PATCH 021/167] fix: using config log, nodemon autoreload --- nodemon.json | 6 +++--- package.json | 2 +- src/config.ts | 2 +- src/index.ts | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/nodemon.json b/nodemon.json index 8f48bf4..f64cc5e 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,6 +1,6 @@ { - "watch": ["dist"], - "ext": "js, json", + "watch": ["src"], + "ext": "js, ts, json", "ignore": ["dist/**/*.spec.js"], - "exec": "node dist/index.js" + "exec": "npm run build; node dist/index.js" } diff --git a/package.json b/package.json index 421a966..411b38f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "npx rimraf dist; tsc", "start": "node dist/index.js", - "dev": "npm run build; nodemon", + "dev": "nodemon", "prepare": "husky", "bump-patch": "npm version patch", "bump-minor": "npm version minor", diff --git a/src/config.ts b/src/config.ts index e368196..f519b72 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,7 +7,7 @@ const currDir = dirname(fileURLToPath(import.meta.url)) const rootDir = join(currDir, '..') const configName = process.argv.slice(2)[0] || 'default' -const configFileName = `config.${configName}.json5` +export const configFileName = `config.${configName}.json5` const configPath = rootDir + `/${configFileName}` diff --git a/src/index.ts b/src/index.ts index 759053b..76e9086 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,9 @@ import type { PingService } from '@libp2p/ping' import { KadDHT } from '@libp2p/kad-dht' import { PeerId } from '@libp2p/interface' import { filestore } from './store.js' +import { configFileName } from './config.js' + +pino.logger.info(`Using config file: ${configFileName}`) const verifiedFetch = await createVerifiedFetch(helia) const upload = multer({ storage: filestore }) From b1e4c30028c87c56d14db017365d81b6c7bba860 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Thu, 2 May 2024 21:21:57 +0400 Subject: [PATCH 022/167] fix: config and autopeering --- config.default.json5 | 19 ++++++++++++++++++- src/cron.ts | 11 +++++++++-- src/helia.ts | 12 +++--------- src/index.ts | 4 ++-- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 797100f..ad74109 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -14,5 +14,22 @@ } ], storeFolder: '.adm-ipfs', - logLevel: 'debug' + logLevel: 'debug', + peerDiscovery: { + bootstrap: [ + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', + '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', + '/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ' + ], + listen: [ + '/ip4/0.0.0.0/tcp/4001', + '/ip6/::/tcp/4002', + '/webrtc' + ] + }, + serverPort: 4000, + autoPeeringPeriod: '*/10 * * * * *' } diff --git a/src/cron.ts b/src/cron.ts index 358e299..ac5c5db 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -1,13 +1,20 @@ import { CronJob } from 'cron' import { helia } from './helia.js' import { getNodesList } from './utils/utils.js' +import config from './config.js' import { pino } from './utils/logger.js' /** * Auto-peering between ADM IPFS nodes. */ -export const autoPeering = new CronJob('*/10 * * * * *', () => { - autoPeeringHandler().catch(pino.logger.error) +let started = false +export const autoPeering = new CronJob(config.autoPeeringPeriod, () => { + if (!started) { + started = true + autoPeeringHandler() + .catch(pino.logger.error) + .finally(() => (started = false)) + } }) export async function autoPeeringHandler() { diff --git a/src/helia.ts b/src/helia.ts index a62ba56..d2c78db 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -2,6 +2,7 @@ import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' +import config from './config.js' export const helia = await createHelia({ datastore, @@ -9,18 +10,11 @@ export const helia = await createHelia({ libp2p: { peerDiscovery: [ bootstrap({ - list: [ - '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', - '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', - '/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ' - ] + list: config.peerDiscovery.bootstrap }) ], addresses: { - listen: ['/ip4/0.0.0.0/tcp/4001', '/ip6/::/tcp/4002', '/webrtc'] + listen: config.peerDiscovery.listen }, connectionManager: { /** diff --git a/src/index.ts b/src/index.ts index 76e9086..3cd4972 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ import type { PingService } from '@libp2p/ping' import { KadDHT } from '@libp2p/kad-dht' import { PeerId } from '@libp2p/interface' import { filestore } from './store.js' -import { configFileName } from './config.js' +import config, { configFileName } from './config.js' pino.logger.info(`Using config file: ${configFileName}`) @@ -67,7 +67,7 @@ pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) autoPeering.start() -const PORT = 4000 +const PORT = config.serverPort const app = express() app.use(pino) From 211d4f870f080d14265e0c585a32f5dda91c4af3 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Fri, 3 May 2024 17:54:11 +0400 Subject: [PATCH 023/167] feat: healtcheck + cors --- config.default.json5 | 6 ++++- package-lock.json | 23 ++++++++++++++++ package.json | 2 ++ src/{cron.ts => auto-peering.cron.ts} | 4 +-- src/config.ts | 6 ++--- src/disk-usage.cron.ts | 38 +++++++++++++++++++++++++++ src/helia.ts | 2 +- src/index.ts | 37 ++++++++++++++++++++++++-- src/store.ts | 9 ++++--- src/utils/logger.ts | 2 +- src/utils/utils.ts | 34 +++++++++++++++++++++++- 11 files changed, 149 insertions(+), 14 deletions(-) rename src/{cron.ts => auto-peering.cron.ts} (90%) create mode 100644 src/disk-usage.cron.ts diff --git a/config.default.json5 b/config.default.json5 index ad74109..3b74a3c 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -31,5 +31,9 @@ ] }, serverPort: 4000, - autoPeeringPeriod: '*/10 * * * * *' + autoPeeringPeriod: '*/10 * * * * *', + diskUsageScanPeriod: '0 */1 * * * *', + cors: { + originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh'] + } } diff --git a/package-lock.json b/package-lock.json index cd0c0f9..b5918ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "blockstore-core": "^4.4.0", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", + "cors": "^2.8.5", "cron": "^3.1.6", "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", @@ -47,6 +48,7 @@ "@commitlint/config-conventional": "^19.1.0", "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", + "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", @@ -5707,6 +5709,15 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/dns-packet": { "version": "5.6.5", "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", @@ -7441,6 +7452,18 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", diff --git a/package.json b/package.json index 411b38f..9085120 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@commitlint/config-conventional": "^19.1.0", "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", + "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", @@ -56,6 +57,7 @@ "blockstore-core": "^4.4.0", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", + "cors": "^2.8.5", "cron": "^3.1.6", "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", diff --git a/src/cron.ts b/src/auto-peering.cron.ts similarity index 90% rename from src/cron.ts rename to src/auto-peering.cron.ts index ac5c5db..0ad9aa6 100644 --- a/src/cron.ts +++ b/src/auto-peering.cron.ts @@ -1,7 +1,7 @@ import { CronJob } from 'cron' import { helia } from './helia.js' import { getNodesList } from './utils/utils.js' -import config from './config.js' +import { config } from './config.js' import { pino } from './utils/logger.js' /** @@ -12,7 +12,7 @@ export const autoPeering = new CronJob(config.autoPeeringPeriod, () => { if (!started) { started = true autoPeeringHandler() - .catch(pino.logger.error) + .catch((err: Error) => pino.logger.error(`${err.message}\n${err.stack}`)) .finally(() => (started = false)) } }) diff --git a/src/config.ts b/src/config.ts index f519b72..a73bfd2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,8 +9,8 @@ const rootDir = join(currDir, '..') const configName = process.argv.slice(2)[0] || 'default' export const configFileName = `config.${configName}.json5` -const configPath = rootDir + `/${configFileName}` +const configPath = join(rootDir, configFileName) -const config = JSON5.parse(fs.readFileSync(configPath, 'utf8')) +export const config = JSON5.parse(fs.readFileSync(configPath, 'utf8')) -export default config +export const packageJson = JSON5.parse(fs.readFileSync(join(rootDir, 'package.json'), 'utf8')) diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts new file mode 100644 index 0000000..033b3bc --- /dev/null +++ b/src/disk-usage.cron.ts @@ -0,0 +1,38 @@ +import { blockstorePath, datastorePath } from './store.js' +import { dirSize, availableStorageSize } from './utils/utils.js' +import { CronJob } from 'cron' +import { config } from './config.js' +import { pino } from './utils/logger.js' + +let blockstoreSizeMb = 0 +let datastoreSizeMb = 0 +let availableSizeInMb = 0 + +let started = false +export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { + if (!started) { + started = true + scan() + .catch((err: Error) => { + pino.logger.error(`${err.message}\n${err.stack}`) + }) + .finally(() => (started = false)) + } +}) + +async function scan() { + pino.logger.info('[Cron] Running "diskUsage" cronjob.') + blockstoreSizeMb = (await dirSize(blockstorePath)) / 1048576 + datastoreSizeMb = (await dirSize(datastorePath)) / 1048576 + availableSizeInMb = Number((await availableStorageSize()) / 1048576n) +} + +scan().catch(pino.logger.error) + +export function getDiskUsageStats() { + return { + blockstoreSizeMb, + datastoreSizeMb, + availableSizeInMb + } +} diff --git a/src/helia.ts b/src/helia.ts index d2c78db..79da64d 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -2,7 +2,7 @@ import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' -import config from './config.js' +import { config } from './config.js' export const helia = await createHelia({ datastore, diff --git a/src/index.ts b/src/index.ts index 3cd4972..fa261d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { CID } from 'multiformats/cid' import express from 'express' import multer from 'multer' import { Multiaddr, multiaddr } from '@multiformats/multiaddr' -import { autoPeeringHandler, autoPeering } from './cron.js' +import { autoPeeringHandler, autoPeering } from './auto-peering.cron.js' import { helia } from './helia.js' import { Pin } from 'helia' import { flatFiles } from './utils/utils.js' @@ -17,7 +17,9 @@ import type { PingService } from '@libp2p/ping' import { KadDHT } from '@libp2p/kad-dht' import { PeerId } from '@libp2p/interface' import { filestore } from './store.js' -import config, { configFileName } from './config.js' +import { config, configFileName, packageJson } from './config.js' +import { getDiskUsageStats, diskUsageCron } from './disk-usage.cron.js' +import cors from 'cors' pino.logger.info(`Using config file: ${configFileName}`) @@ -66,12 +68,20 @@ const ifs = unixfs(helia) pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) autoPeering.start() +diskUsageCron.start() const PORT = config.serverPort const app = express() app.use(pino) +const corsOptions = { + origin: config.cors.originRegexps.map((item: string) => new RegExp(`^https?:\\/\\/${item}`)), + methods: ['GET', 'POST'] +} +app.use(cors(corsOptions)) +app.options('*', cors()) + app.get('/', (req, res) => { res.send('IPFS node') }) @@ -399,6 +409,29 @@ app.post('/test', async (req, res) => { }) }) +app.get('/node/health', async (req, res) => { + res.send({ + timestamp: Date.now(), + heliaStatus: helia.libp2p.status + }) +}) + +app.get('/node/info', async (req, res) => { + const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() + const heliaVersion = (packageJson.dependencies['helia'] || 'unknown').replaceAll('^', '') + res.send({ + version: packageJson.version, + timestamp: Date.now(), + heliaVersion, + heliaStatus: helia.libp2p.status, + peerId: helia.libp2p.peerId, + multiAddresses: helia.libp2p.getMultiaddrs(), + blockstoreSizeMb, + datastoreSizeMb, + availableSizeInMb + }) +}) + app.listen(PORT, () => { pino.logger.info(`Server is running on http://localhost:${PORT}`) }) diff --git a/src/store.ts b/src/store.ts index 5aad84f..179b2af 100644 --- a/src/store.ts +++ b/src/store.ts @@ -3,13 +3,16 @@ import { FsDatastore } from 'datastore-fs' import { homedir } from 'os' import { existsSync, mkdirSync } from 'fs' import { join } from 'path' -import config from './config.js' +import { config } from './config.js' import multer from 'multer' const userHomeDir = homedir() -export const blockstore = new FsBlockstore(join(userHomeDir, config.storeFolder, 'blockstore')) -export const datastore = new FsDatastore(join(userHomeDir, config.storeFolder, 'datastore')) +export const blockstorePath = join(userHomeDir, config.storeFolder, 'blockstore') +export const datastorePath = join(userHomeDir, config.storeFolder, 'datastore') + +export const blockstore = new FsBlockstore(blockstorePath) +export const datastore = new FsDatastore(datastorePath) const filestorePath = join(userHomeDir, config.storeFolder, 'filestore') if (!existsSync(filestorePath)) { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 63804af..43fb69d 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,5 +1,5 @@ import { pinoHttp } from 'pino-http' -import config from '../config.js' +import { config } from '../config.js' export const pino = pinoHttp({ level: config.logLevel || 'info', diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 90d878a..95bb8fc 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,7 +1,10 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' -import config from '../config.js' +import { config } from '../config.js' import { ConfigNode, NodeWithPeerId } from './types.js' +import { readdir, stat, statfs } from 'node:fs/promises' +import { join } from 'path' +import { pino } from './logger.js' /** * Get peerId from multiaddr string @@ -64,3 +67,32 @@ export function flatFiles( return resultFiles } } + +export async function dirSize(dir: string): Promise { + const files = await readdir(dir, { withFileTypes: true }) + + const paths = files.map(async (file) => { + const path = join(dir, file.name) + + if (file.isDirectory()) return await dirSize(path) + + if (file.isFile()) { + try { + const { size } = await stat(path) + + return size + } catch (e) { + pino.logger.debug(`${e.message}\n${e.stack}`) + } + } + + return 0 + }) + + return (await Promise.all(paths)).flat(Infinity).reduce((i, size) => i + size, 0) +} + +export async function availableStorageSize() { + const statistics = await statfs('/', { bigint: true }) + return statistics.bsize * statistics.bavail +} From b092b465e0db3def8c0d19bb915bd02903586048 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Fri, 3 May 2024 18:46:25 +0400 Subject: [PATCH 024/167] fix: explicit Error type --- src/auto-peering.cron.ts | 2 +- src/disk-usage.cron.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/auto-peering.cron.ts b/src/auto-peering.cron.ts index 0ad9aa6..86c3b45 100644 --- a/src/auto-peering.cron.ts +++ b/src/auto-peering.cron.ts @@ -12,7 +12,7 @@ export const autoPeering = new CronJob(config.autoPeeringPeriod, () => { if (!started) { started = true autoPeeringHandler() - .catch((err: Error) => pino.logger.error(`${err.message}\n${err.stack}`)) + .catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) .finally(() => (started = false)) } }) diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index 033b3bc..4498275 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -13,9 +13,7 @@ export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { if (!started) { started = true scan() - .catch((err: Error) => { - pino.logger.error(`${err.message}\n${err.stack}`) - }) + .catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) .finally(() => (started = false)) } }) From edbc9f99631f8e06a192a6d5dabe9fa3a8dab61d Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 4 May 2024 01:34:28 +0400 Subject: [PATCH 025/167] fix: remove cors for options --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index fa261d6..de49e5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,7 +80,6 @@ const corsOptions = { methods: ['GET', 'POST'] } app.use(cors(corsOptions)) -app.options('*', cors()) app.get('/', (req, res) => { res.send('IPFS node') From 9138cbda4042257307141514db5d5b9a4e4ce978 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 4 May 2024 16:00:41 +0400 Subject: [PATCH 026/167] feat: streaming files --- config.default.json5 | 3 +- package-lock.json | 7531 +++++++++++++++++++++++++----------------- package.json | 3 +- src/index.ts | 110 +- src/multer.d.ts | 20 + src/store.ts | 16 - src/utils/utils.ts | 7 +- 7 files changed, 4616 insertions(+), 3074 deletions(-) create mode 100644 src/multer.d.ts diff --git a/config.default.json5 b/config.default.json5 index ad74109..bb641ce 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -31,5 +31,6 @@ ] }, serverPort: 4000, - autoPeeringPeriod: '*/10 * * * * *' + autoPeeringPeriod: '*/10 * * * * *', + uploadLimitSizeBytes: 268435456 } diff --git a/package-lock.json b/package-lock.json index cd0c0f9..9690544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "it-last": "^3.0.4", "json5": "^2.2.3", "libp2p": "^1.3.1", - "multer": "^1.4.5-lts.1", + "multer": "^2.0.0-rc.4", "multiformats": "^13.1.0", "pino": "^8.20.0", "pino-http": "^9.0.0" @@ -48,7 +48,6 @@ "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", "@types/express": "^4.17.21", - "@types/multer": "^1.4.11", "@types/node": "^20.11.30", "cross-env": "^7.0.3", "eslint": "^8.57.0", @@ -67,15 +66,6 @@ "node": ">=20.0.0" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@achingbrain/nat-port-mapper": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/@achingbrain/nat-port-mapper/-/nat-port-mapper-1.0.13.tgz", @@ -115,16 +105,6 @@ "node": ">=6.0.0" } }, - "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@assemblyscript/loader": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", @@ -143,30 +123,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", - "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", - "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.1", + "@babel/generator": "^7.24.5", "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.1", - "@babel/parser": "^7.24.1", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -191,12 +171,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", - "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", "peer": true, "dependencies": { - "@babel/types": "^7.24.0", + "@babel/types": "^7.24.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -205,16 +185,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", @@ -255,15 +225,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -273,26 +234,20 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "peer": true - }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", - "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", + "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.24.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-split-export-declaration": "^7.24.5", "semver": "^6.3.1" }, "engines": { @@ -338,9 +293,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", - "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -388,12 +343,12 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", + "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", "peer": true, "dependencies": { - "@babel/types": "^7.23.0" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -412,16 +367,16 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -443,9 +398,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", "peer": true, "engines": { "node": ">=6.9.0" @@ -486,12 +441,12 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -510,12 +465,12 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -531,9 +486,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", "engines": { "node": ">=6.9.0" } @@ -548,39 +503,39 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", + "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", "peer": true, "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/helper-function-name": "^7.23.0", + "@babel/template": "^7.24.0", + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", "peer": true, "dependencies": { "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.5", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -635,9 +590,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", "peer": true, "bin": { "parser": "bin/babel-parser.js" @@ -646,6 +601,22 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", + "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", @@ -746,6 +717,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", @@ -1208,12 +1196,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", - "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", + "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -1239,12 +1227,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz", - "integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-create-class-features-plugin": "^7.24.4", "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -1256,18 +1244,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", - "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", + "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.5", "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-split-export-declaration": "^7.24.5", "globals": "^11.1.0" }, "engines": { @@ -1277,6 +1265,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", @@ -1294,12 +1291,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", - "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", + "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -1629,15 +1626,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", - "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", + "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.1" + "@babel/plugin-transform-parameters": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -1679,12 +1676,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", - "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", + "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -1696,12 +1693,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", - "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", + "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -1727,14 +1724,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", - "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", + "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1794,12 +1791,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", - "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz", + "integrity": "sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -1945,12 +1942,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", - "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", + "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -1960,14 +1957,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz", - "integrity": "sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz", + "integrity": "sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==", "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", "@babel/plugin-syntax-typescript": "^7.24.1" }, "engines": { @@ -2041,15 +2038,16 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz", - "integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", + "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", "peer": true, "dependencies": { - "@babel/compat-data": "^7.24.1", + "@babel/compat-data": "^7.24.4", "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.5", "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", @@ -2076,12 +2074,12 @@ "@babel/plugin-transform-async-generator-functions": "^7.24.3", "@babel/plugin-transform-async-to-generator": "^7.24.1", "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.5", "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.1", - "@babel/plugin-transform-classes": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.5", "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.5", "@babel/plugin-transform-dotall-regex": "^7.24.1", "@babel/plugin-transform-duplicate-keys": "^7.24.1", "@babel/plugin-transform-dynamic-import": "^7.24.1", @@ -2101,13 +2099,13 @@ "@babel/plugin-transform-new-target": "^7.24.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", "@babel/plugin-transform-object-super": "^7.24.1", "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.1", - "@babel/plugin-transform-parameters": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", + "@babel/plugin-transform-parameters": "^7.24.5", "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.5", "@babel/plugin-transform-property-literals": "^7.24.1", "@babel/plugin-transform-regenerator": "^7.24.1", "@babel/plugin-transform-reserved-words": "^7.24.1", @@ -2115,7 +2113,7 @@ "@babel/plugin-transform-spread": "^7.24.1", "@babel/plugin-transform-sticky-regex": "^7.24.1", "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.5", "@babel/plugin-transform-unicode-escapes": "^7.24.1", "@babel/plugin-transform-unicode-property-regex": "^7.24.1", "@babel/plugin-transform-unicode-regex": "^7.24.1", @@ -2219,9 +2217,9 @@ "peer": true }, "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", "peer": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2251,19 +2249,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", "peer": true, "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2271,14 +2269,23 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "peer": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2375,12 +2382,12 @@ } }, "node_modules/@commitlint/cli": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.2.2.tgz", - "integrity": "sha512-P8cbOHfg2PQRzfICLSrzUVOCVMqjEZ8Hlth6mtJ4yOEjT47Q5PbIGymgX3rLVylNw+3IAT2Djn9IJ2wHbXFzBg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.3.0.tgz", + "integrity": "sha512-LgYWOwuDR7BSTQ9OLZ12m7F/qhNY+NpAyPBgo4YNMkACE7lGuUnuQq1yi9hz1KA4+3VqpOYl8H1rY/LYK43v7g==", "dev": true, "dependencies": { - "@commitlint/format": "^19.0.3", + "@commitlint/format": "^19.3.0", "@commitlint/lint": "^19.2.2", "@commitlint/load": "^19.2.0", "@commitlint/read": "^19.2.1", @@ -2395,62 +2402,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/cli/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@commitlint/cli/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/cli/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/@commitlint/cli/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@commitlint/config-conventional": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.2.2.tgz", @@ -2477,28 +2428,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@commitlint/ensure": { "version": "19.0.3", "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.0.3.tgz", @@ -2526,9 +2455,9 @@ } }, "node_modules/@commitlint/format": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.0.3.tgz", - "integrity": "sha512-QjjyGyoiVWzx1f5xOteKHNLFyhyweVifMgopozSgx1fGNrGV8+wp7k6n1t6StHdJ6maQJ+UUtO2TcEiBFRyR6Q==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.3.0.tgz", + "integrity": "sha512-luguk5/aF68HiF4H23ACAfk8qS8AHxl4LLN5oxPc24H+2+JRPsNr1OS3Gaea0CrH7PKhArBMKBz5RX9sA5NtTg==", "dev": true, "dependencies": { "@commitlint/types": "^19.0.3", @@ -2538,18 +2467,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/format/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/is-ignored": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.2.2.tgz", @@ -2599,140 +2516,24 @@ "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@commitlint/load/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "node_modules/@commitlint/message": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.0.0.tgz", + "integrity": "sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==", "dev": true, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/@commitlint/parse": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.0.3.tgz", + "integrity": "sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==", "dev": true, "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@commitlint/load/node_modules/cosmiconfig-typescript-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz", - "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==", - "dev": true, - "dependencies": { - "jiti": "^1.19.1" - }, - "engines": { - "node": ">=v16" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=8.2", - "typescript": ">=4" - } - }, - "node_modules/@commitlint/load/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/load/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@commitlint/load/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/load/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@commitlint/message": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.0.0.tgz", - "integrity": "sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==", - "dev": true, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/parse": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.0.3.tgz", - "integrity": "sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==", - "dev": true, - "dependencies": { - "@commitlint/types": "^19.0.3", - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-parser": "^5.0.0" + "@commitlint/types": "^19.0.3", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" }, "engines": { "node": ">=v18" @@ -2754,62 +2555,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/read/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@commitlint/read/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/read/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/@commitlint/read/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@commitlint/resolve-extends": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.1.0.tgz", @@ -2827,15 +2572,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@commitlint/rules": { "version": "19.0.3", "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.0.3.tgz", @@ -2852,62 +2588,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/rules/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@commitlint/rules/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/rules/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/@commitlint/rules/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@commitlint/to-lines": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.0.0.tgz", @@ -2929,142 +2609,47 @@ "node": ">=v18" } }, - "node_modules/@commitlint/top-level/node_modules/find-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", - "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "node_modules/@commitlint/types": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.0.3.tgz", + "integrity": "sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==", "dev": true, "dependencies": { - "locate-path": "^7.2.0", - "path-exists": "^5.0.0", - "unicorn-magic": "^0.1.0" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=v18" } }, - "node_modules/@commitlint/top-level/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { - "p-locate": "^6.0.0" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@commitlint/top-level/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@commitlint/top-level/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/@commitlint/top-level/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/types": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.0.3.tgz", - "integrity": "sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==", - "dev": true, - "dependencies": { - "@types/conventional-commits-parser": "^5.0.0", - "chalk": "^5.3.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/types/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -3084,87 +2669,27 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/@eslint/js": { "version": "8.57.0", @@ -3175,6 +2700,17 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3191,24 +2727,21 @@ } }, "node_modules/@helia/bitswap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.0.0.tgz", - "integrity": "sha512-ZcQNBp2TyU6OVvt8J3GvdQ3f5er8GrjEQrIKLl5jHSoXtbyNaCv9gBzLn85aMMXWBIUQcaom+5s1LIaJ5bqbjw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.0.tgz", + "integrity": "sha512-/RF3vxEHM6KWliLYYvukf+z388SWkJU1NJkdXdbzU7W0YjyCmanYAusOXVf5sfHIiOtK+YFO6oJzy+kcp+eySQ==", "dependencies": { - "@helia/interface": "^4.2.0", - "@helia/utils": "^0.2.0", + "@helia/interface": "^4.3.0", + "@helia/utils": "^0.3.0", "@libp2p/interface": "^1.1.2", "@libp2p/logger": "^4.0.5", "@libp2p/peer-collections": "^5.1.6", "@libp2p/utils": "^5.2.3", "@multiformats/multiaddr": "^12.1.14", "any-signal": "^4.1.1", - "debug": "^4.3.4", "interface-blockstore": "^5.2.9", "interface-store": "^5.1.7", - "it-all": "^3.0.4", "it-drain": "^3.0.5", - "it-filter": "^3.0.4", "it-length-prefixed": "^9.0.0", "it-length-prefixed-stream": "^1.1.6", "it-map": "^3.0.5", @@ -3225,11 +2758,11 @@ } }, "node_modules/@helia/bitswap/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-lCOd5DsimmUrp9+TyxWDkvhDFvI+Hgn7PevQHGya3AwmJXxsuCY6FtRgd7YkdaoqAm4+q7L710UnUtp5AmHlVA==", "dependencies": { - "@helia/interface": "^4.2.0", + "@helia/interface": "^4.3.0", "@ipld/dag-cbor": "^9.2.0", "@ipld/dag-json": "^10.2.0", "@ipld/dag-pb": "^4.1.0", @@ -3259,11 +2792,11 @@ } }, "node_modules/@helia/block-brokers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.1.0.tgz", - "integrity": "sha512-Y6iVROn1vH6dlQnEUerc7ULASAq2fY1xlrpBg93HO3JyCc0C1usCzsrtEcOMGd852dDM1SY7QN1wKPCVsq2w/g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.1.3.tgz", + "integrity": "sha512-RW76CztfcDRHxbukArI2iMuJBgkLlQ2GsW5PuU5yATgHcERRJaDr0dSwmGtySMfMn7WweF+3RbiRw196eYxM4w==", "dependencies": { - "@helia/bitswap": "^1.0.0", + "@helia/bitswap": "^1.0.1", "@helia/interface": "^4.2.0", "@helia/utils": "^0.2.0", "@libp2p/interface": "^1.1.4", @@ -3277,46 +2810,12 @@ "progress-events": "^1.0.0" } }, - "node_modules/@helia/block-brokers/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", - "dependencies": { - "@helia/interface": "^4.2.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.1", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "murmurhash3js-revisited": "^3.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" - } - }, "node_modules/@helia/car": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@helia/car/-/car-3.1.3.tgz", - "integrity": "sha512-y+i/eyaUwi0XzfFOPw4VlGUH/DfonD38CoHfjqxWKW8dKxReKdVMkca6hjoDmt31buVSIMeDoAw17iwh1IjFvg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@helia/car/-/car-3.1.5.tgz", + "integrity": "sha512-3vlkSsIt/QD2CSnuGOjjx2tCEZ9Dxss/PohqXTiXdHeFeTAXoLEgX5f25jyYUHErLt7tdgMrO6ayACVPFpjHVQ==", "dependencies": { - "@helia/interface": "^4.2.0", + "@helia/interface": "^4.3.0", "@ipld/car": "^5.3.0", "@libp2p/interfaces": "^3.3.2", "interface-blockstore": "^5.2.10", @@ -3329,9 +2828,9 @@ } }, "node_modules/@helia/delegated-routing-v1-http-api-client": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.0.tgz", - "integrity": "sha512-NQRG5djXKI0GcFx35JMKSQ92RscGOhER6ti66ExjreAffaO2/9vk2ZbAbfCoZnjyTnx66HWAgAyB4GDDTAeFLA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.1.tgz", + "integrity": "sha512-Gkaw3B8IjgCCXtasa17j8wQaFdzph2s0RiLyrJTS2htmwVFWZEjLNLUDJeWsMHBoLBza8SrVGvQ9fiN3XD3rfg==", "dependencies": { "@libp2p/interface": "^1.1.1", "@libp2p/logger": "^4.0.4", @@ -3350,24 +2849,43 @@ } }, "node_modules/@helia/http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@helia/http/-/http-1.0.4.tgz", - "integrity": "sha512-lXKX14xbSi2pDA7UOu3U4S1rNvPTkpLJtQpZS/amvo0ooAtbni+fA2uHDx/lTO7lNCfJMD9YLP1hikHz6D+nAQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@helia/http/-/http-1.0.7.tgz", + "integrity": "sha512-79mui6ZlcgzDiOl+Nz+cje8NUTOcJE1lMmh4D4Ryxd80gDGlHk2ZY8d4w468LvyJ9mm2ZJJuboSwLvPDHm0UJw==", "dependencies": { - "@helia/block-brokers": "^2.1.0", - "@helia/interface": "^4.2.0", - "@helia/routers": "^1.0.3", - "@helia/utils": "^0.2.0", + "@helia/block-brokers": "^3.0.0", + "@helia/interface": "^4.3.0", + "@helia/routers": "^1.1.0", + "@helia/utils": "^0.3.0", "blockstore-core": "^4.4.0", "datastore-core": "^9.2.9" } }, + "node_modules/@helia/http/node_modules/@helia/block-brokers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.0.tgz", + "integrity": "sha512-f93Lklv+ZyySxIAiZDG6p2kRF+OV3hdhVUAyWNK6TWpyxuFNj45cYQci9nz/ZTmEHqg2ylswwiq4lEGml3WS7Q==", + "dependencies": { + "@helia/bitswap": "^1.1.0", + "@helia/interface": "^4.3.0", + "@helia/utils": "^0.3.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/utils": "^5.2.6", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "@multiformats/multiaddr-to-uri": "^10.0.1", + "interface-blockstore": "^5.2.10", + "interface-store": "^5.1.8", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0" + } + }, "node_modules/@helia/http/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-lCOd5DsimmUrp9+TyxWDkvhDFvI+Hgn7PevQHGya3AwmJXxsuCY6FtRgd7YkdaoqAm4+q7L710UnUtp5AmHlVA==", "dependencies": { - "@helia/interface": "^4.2.0", + "@helia/interface": "^4.3.0", "@ipld/dag-cbor": "^9.2.0", "@ipld/dag-json": "^10.2.0", "@ipld/dag-pb": "^4.1.0", @@ -3397,9 +2915,9 @@ } }, "node_modules/@helia/interface": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.2.0.tgz", - "integrity": "sha512-8F1rZAskqxcIpXEcJOa050fsdYQLDbf6WRgUyPve+gPIZVTvwgfuGuOlZxxrXAFtH7CujRiP3yMAN3V6iTbuRA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.3.0.tgz", + "integrity": "sha512-gaWQSVGIcOkK0Wx12qUiUMFLPI8AjQCYaf4dpZdciSYPNfKOtPpW54QGzgrQYBcvBgPzg16hnnvDGwLeL2WlAQ==", "dependencies": { "@libp2p/interface": "^1.1.4", "@multiformats/dns": "^1.0.1", @@ -3411,11 +2929,11 @@ } }, "node_modules/@helia/ipns": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@helia/ipns/-/ipns-7.2.1.tgz", - "integrity": "sha512-HHfZr24H3iGFNHVvbOL+gwVXkmvo7umJ7US4fAGNbgXytXSEY/GmIqF+fDA0xBtPvFSrccv18XoKH7Dz+yNrfg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@helia/ipns/-/ipns-7.2.2.tgz", + "integrity": "sha512-9FX0/93nT5ueaFMFzhbS+YWx/YHoqVnr11ZIfDMOIleU7WC0U84W/J7n1k21yNDajFIHqXQJNFm68geJaP42Ig==", "dependencies": { - "@helia/interface": "^4.2.0", + "@helia/interface": "^4.3.0", "@libp2p/interface": "^1.1.4", "@libp2p/kad-dht": "^12.0.8", "@libp2p/logger": "^4.0.7", @@ -3429,11 +2947,11 @@ } }, "node_modules/@helia/mfs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.2.tgz", - "integrity": "sha512-vFhU2AMBGcw54FHoJPKG74lBkFGheJFJA69Tl0n531QKdaZwEgnjZB97zhx+Hxyx2S3a92gEdQiCTNRYAkOJ6g==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.6.tgz", + "integrity": "sha512-ew30vM+oLeoYx6zdzdLZNwJ6vzCHCE1twzNMeccUPQdFdGY2x/P5+izBljPId6W4zuq8XXMD2rYOmtPUeSC5sw==", "dependencies": { - "@helia/unixfs": "^3.0.2", + "@helia/unixfs": "^3.0.6", "@libp2p/interfaces": "^3.3.2", "@libp2p/logger": "^4.0.7", "interface-blockstore": "^5.2.10", @@ -3445,13 +2963,14 @@ } }, "node_modules/@helia/routers": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.3.tgz", - "integrity": "sha512-bX6I5ir2YCK+LW/G0YrHhqJW0SjG4DDnkxLOk2dzmZNy2OCB+Ogt0+W2keyz5nHGfTuwcBBn4qH5ouN0vTZ74w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.1.0.tgz", + "integrity": "sha512-WwHcvN8No6+3alTrxNXtTJdqfyX0Ymm41u5yL+1YFdsvRKKy5RLxNlgla34kFJaHuQGPFdROYP/9X4UaHpvcIQ==", "dependencies": { "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.2.0", + "@helia/interface": "^4.3.0", "@libp2p/interface": "^1.1.4", + "@multiformats/uri-to-multiaddr": "^8.0.0", "ipns": "^9.0.0", "it-first": "^3.0.4", "it-map": "^3.0.5", @@ -3460,11 +2979,11 @@ } }, "node_modules/@helia/unixfs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.2.tgz", - "integrity": "sha512-zGOCFPJn8aAxNCImo67PReV2WJIdmjh5pPBDp2MTyeMoa1G5VuhfW6GnSYzQQNagbY/LYdSxDIE+a33rgjGtdw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.6.tgz", + "integrity": "sha512-x3hoHUjwNk2M9Hg0Z9sQE8O8S2rcj13O9dYexGHckxdpeEPhNOwRWT1cMpY8NTK0vpuIs0TvlQm9gYfHm94Clw==", "dependencies": { - "@helia/interface": "^4.1.0", + "@helia/interface": "^4.3.0", "@ipld/dag-pb": "^4.1.0", "@libp2p/interface": "^1.1.4", "@libp2p/logger": "^4.0.7", @@ -3475,7 +2994,7 @@ "ipfs-unixfs-exporter": "^13.5.0", "ipfs-unixfs-importer": "^15.2.4", "it-all": "^3.0.4", - "it-glob": "^2.0.6", + "it-glob": "^3.0.0", "it-last": "^3.0.4", "it-pipe": "^3.0.1", "merge-options": "^3.0.4", @@ -3486,21 +3005,22 @@ } }, "node_modules/@helia/utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.1.0.tgz", - "integrity": "sha512-uRUDzGpeWbPANFlq3J3x14tE89enVEBoaRXM1CKpE25vXUOij9qoYjE8GlXDNCQgkazAzrVswdjcRn6Eza+bIg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", + "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", "dependencies": { - "@helia/interface": "^4.1.0", + "@helia/interface": "^4.2.0", "@ipld/dag-cbor": "^9.2.0", "@ipld/dag-json": "^10.2.0", "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", "@libp2p/interface": "^1.1.4", "@libp2p/logger": "^4.0.7", - "@libp2p/peer-collections": "^5.1.7", "@libp2p/utils": "^5.2.6", "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", "any-signal": "^4.1.1", - "blockstore-core": "^4.4.0", + "blockstore-core": "^4.4.1", "cborg": "^4.0.9", "interface-blockstore": "^5.2.10", "interface-datastore": "^8.2.11", @@ -3511,14 +3031,17 @@ "it-merge": "^3.0.3", "mortice": "^3.0.4", "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.2" } }, "node_modules/@helia/verified-fetch": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.12.tgz", - "integrity": "sha512-eLDGvrDLDs09vZk5PxYmPTB7WlJ88ff4GwF7gRbH5zaqZVVCUP3S6lkEvmNM+9MYPfElfR/wUOmN2GJIVqDQ/A==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.14.tgz", + "integrity": "sha512-UK2mMMIZpgDIH5MfsBdlRtrBfx9wjGZL1xqIWmA91Fuk+DovQN12jui7zpfaARe4S1B4QyYHNhggUADGRx0UHA==", "dependencies": { "@helia/block-brokers": "^2.1.0", "@helia/car": "^3.1.3", @@ -3811,6 +3334,43 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -3825,16 +3385,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -3863,7 +3413,13 @@ "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", @@ -3873,28 +3429,22 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "peer": true - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@libp2p/autonat": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.14.tgz", - "integrity": "sha512-yd6Qlltzt7/kkec9wOtgUJzzDXXreVXyKVR1E5zms58sQmVavc3PWEJE1cFxK0GYMF2QjjzJY13/z9WLeQBzIA==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.19.tgz", + "integrity": "sha512-TyptjlqUvxeRIE8wrr54j3vAteSuJof1U1ryHDLjoM3fXk64a8Ie9Yc1PkcOEMXBJ7oiAEoxe0LmQ+VW89q+hQ==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-id-factory": "^4.1.1", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "it-first": "^3.0.4", "it-length-prefixed": "^9.0.4", "it-map": "^3.0.5", @@ -3905,46 +3455,46 @@ } }, "node_modules/@libp2p/bootstrap": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.17.tgz", - "integrity": "sha512-NO+YTigMn62YMbPAz4jK8sOC6tY2K5Q7wnRI2lmHzP9albIX+Nf4/Tyj7tu8Owi5CP+v07eKXWejmrXm+MkJPg==", + "version": "10.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.22.tgz", + "integrity": "sha512-qAEBvmsksPnuVYMF2Khfvw3Nvatv+VHvrW36A1LQM20nIZ4D25c0t0D9xUyMZd87R4/T+fqCPsW8JKG+6XQH7w==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14" + "@multiformats/multiaddr": "^12.2.1" } }, "node_modules/@libp2p/circuit-relay-v2": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.17.tgz", - "integrity": "sha512-FSTqidxco90MjMdKTGhFZoGxf/EaZMuwRWyEq5oGw1aG8lBKrH2fwP1fTRfwAQVhQ8Pxd9B9/SfBU6QSHA5YKQ==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@libp2p/utils": "^5.2.7", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.22.tgz", + "integrity": "sha512-N913QHAsjIkOtS3cbr1oANG8UbUMYNAxOkgNkTzBNTqTiNbJQxOellK00DpX+quEOP0T+YUc7EnCSrU5wtvL+g==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr": "^12.2.1", "any-signal": "^4.1.1", "it-protobuf-stream": "^1.1.2", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", - "p-defer": "^4.0.0", + "p-defer": "^4.0.1", "p-retry": "^6.2.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/crypto": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.6.tgz", - "integrity": "sha512-AJ4i3DHOTlY961O26M3k1IjmU4rUd5WgeK4t9IRzFfLIbD6uwA+cevJMG2qr0UHJfbYdGKKQ2Po1wqZONoIA9Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.1.tgz", + "integrity": "sha512-ju7P18Swd0CAa4ud31PBQdxPN++6dFl49RrH4F4yh5HYA3hXL18nbioH18iJ4WAahzjNQsdRMYzTQLWAUZPAkg==", "dependencies": { - "@libp2p/interface": "^1.2.0", + "@libp2p/interface": "^1.3.1", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "asn1js": "^3.0.5", @@ -3955,15 +3505,15 @@ } }, "node_modules/@libp2p/dcutr": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.0.14.tgz", - "integrity": "sha512-0x5Sn21KVe1Fz0YeIudPIg2GB+XDVwr5IHblXVVKq5PNHjUvqarO4XM5GAaLyK0tZqwFkkgDNE4BjmHWZkejng==", + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.0.19.tgz", + "integrity": "sha512-EIgeQ5ZFpL6OYqd4PtJyV6Tc8VWu/s2CYeDub1pPZz06UiHfKYO3pDiCEeb9tr740O//VRK127yXaXHdtlV+kA==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "delay": "^6.0.0", "it-protobuf-stream": "^1.1.2", "protons-runtime": "^5.4.0", @@ -3971,27 +3521,27 @@ } }, "node_modules/@libp2p/identify": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-1.0.16.tgz", - "integrity": "sha512-uDXae1DGok+rFF5T8yixf3lkadr5U3O+x72BjgZaIeduWLCbPDczlKMNJuFJF3+ys45qcO8WuySuZqyK2aq8kw==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-1.0.21.tgz", + "integrity": "sha512-wUpgXK1pCrd1wkG1vgkRe+TRHMv+SvR206o5x0srcifjGByXHwKzmTgwYK8dVVVA7SITsuobxVRuELl6OOAaCg==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "it-protobuf-stream": "^1.1.2", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2", + "uint8arrays": "^5.0.3", "wherearewe": "^2.0.1" } }, "node_modules/@libp2p/interface": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.2.0.tgz", - "integrity": "sha512-ImnGNl3El/AukgaojACT8i9SNW1FOsrThcQU/qA3w5tEBR5p84Uwgzl/nxa4X5vGinItUJ9jLEJmtkQJENoiGQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.3.1.tgz", + "integrity": "sha512-KJoYP6biAgIHUU3pxaixaaYCvIHZshzXetxfoNigadAZ3hCGuwpdFhk7IABEaI3RgadOOYUwW3MXPbL+cxnXVQ==", "dependencies": { "@multiformats/multiaddr": "^12.2.1", "it-pushable": "^3.2.3", @@ -4002,12 +3552,12 @@ } }, "node_modules/@libp2p/interface-internal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.1.0.tgz", - "integrity": "sha512-B6Cu3Mhp5kY2Z1cU0soCR4ZtjZtE4FuWE0qdJNauOpcQe9HOjPF8SanFmeEIZ0FKSOo0onQdQi2YdNUTtOVyvQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.2.0.tgz", + "integrity": "sha512-7jrVw3I9B7l6TiKIq199sn0y1BlPVfFGrYE8h02RLLEEuq64q4Gx+/UGmCo7aD3E4+wCcrCLP37O4LZxjAhWUQ==", "dependencies": { - "@libp2p/interface": "^1.2.0", - "@libp2p/peer-collections": "^5.1.10", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-collections": "^5.2.0", "@multiformats/multiaddr": "^12.2.1", "uint8arraylist": "^2.4.8" } @@ -4022,17 +3572,17 @@ } }, "node_modules/@libp2p/kad-dht": { - "version": "12.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.13.tgz", - "integrity": "sha512-as+pIHcrjVTXkf6kyULtnJg/RRvrbVjyycPD8HtqD5SKB0PDiYvJO0LY0oVAHFFi5oGMCqSG5777VQfC/tmRhQ==", - "dependencies": { - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.2.0", - "@libp2p/interface-internal": "^1.1.0", - "@libp2p/peer-collections": "^5.1.10", - "@libp2p/peer-id": "^4.0.10", - "@libp2p/record": "^4.0.0", - "@libp2p/utils": "^5.3.1", + "version": "12.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.15.tgz", + "integrity": "sha512-BY4/0hok8PhvaOL31AvX0CkQC3j0yUgahzMNr2gqLI9wbCiuiOqzo4FSuJ0gIkv8I9XcGS6L+CfZ44SpsUZPEA==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/record": "^4.0.1", + "@libp2p/utils": "^5.4.0", "@multiformats/multiaddr": "^12.2.1", "any-signal": "^4.1.1", "hashlru": "^2.3.0", @@ -4060,26 +3610,26 @@ } }, "node_modules/@libp2p/keychain": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.0.10.tgz", - "integrity": "sha512-yI7Z3H4hNky7uRyJUHE/4lcc42jboFlGnveW+nrUEm+3/IrCt7T+Ci74Ighhh41V90iKJyFAfQeR+HAnwpT9GA==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.0.14.tgz", + "integrity": "sha512-uTQmFHqo5IGMDVQJz6Hb9ubg0pV7LwL/S6tGK1CCz46lanAb5Harb4EEMGcv5p4URrEpQ4TheatGnHyDoX5Mug==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", "interface-datastore": "^8.2.11", "merge-options": "^3.0.4", "multiformats": "^13.1.0", "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/logger": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.10.tgz", - "integrity": "sha512-JiRfJHO/D9Jlh2rJ6STnONoeQevBAdAZaGUxrtvBf4RFfucldSFEMOtdkFO8xFGuiA90Q2kj4BE2douG6fB3Lw==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.12.tgz", + "integrity": "sha512-eSiHY06Zijq6b1rMBob/ZuGBEavFm8IPNPEzeOU3JrBU7v/OV8Da0FesbemtkudZVPRi8mqRoOiIwmWn0mObng==", "dependencies": { - "@libp2p/interface": "^1.2.0", + "@libp2p/interface": "^1.3.1", "@multiformats/multiaddr": "^12.2.1", "debug": "^4.3.4", "interface-datastore": "^8.2.11", @@ -4087,156 +3637,157 @@ } }, "node_modules/@libp2p/mdns": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.0.17.tgz", - "integrity": "sha512-hflI9xnA1vWtwiZnl9Y4HXGp0QoDIt38kFdYqlgvcRIAJuH4Gw0dD/pFvzYUT3/BPAM1sZb9xU8sK1dwmPwWzQ==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "version": "10.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.0.22.tgz", + "integrity": "sha512-4ZWQeYhZaa4G4l+f4/TXMQVgGAWoXzklpzkfbFzK17bVwH3ocCKvcLrG+gYMG8hiVIjLvmx5yT3ahUTN1aFmtQ==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "@types/multicast-dns": "^7.2.4", "dns-packet": "^5.6.1", "multicast-dns": "^7.2.5" } }, "node_modules/@libp2p/mplex": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.0.17.tgz", - "integrity": "sha512-/+AW5J5Zh/M2vEE7nDU00zaLyCtbmBstcUnps+MBKEKoZ4nQaGtAox2dq9dvCOSWrFl3zhHDrLQy1Kt2xIfxMA==", + "version": "10.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.0.22.tgz", + "integrity": "sha512-1lpREL+EHeE+/WGKZwKHIkGIuWPmgFrwWtlhvYu0xrceVCgjlDlCE3mJuN9hzN0IPr0A3278yahiVCCX9zO33A==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", + "@libp2p/interface": "^1.3.1", + "@libp2p/utils": "^5.4.0", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/multistream-select": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.5.tgz", - "integrity": "sha512-fQSF3bjlaSqneD50/cj9Kbl2YjFVy45kHxtjnMGb04cnyL8CefJ89dY2aF3bWCbbEqUxzc4yCCNi48y+Pfo34w==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.9.tgz", + "integrity": "sha512-Cs7KgIZXmHnTbtbK0nHVezgghCuo/8IjdioeViXyljTe2/ye4lds86lSkuZTEEbggM/4djT0fBdvqAJ2F5rhVQ==", "dependencies": { - "@libp2p/interface": "^1.1.5", + "@libp2p/interface": "^1.3.1", "it-length-prefixed": "^9.0.4", "it-length-prefixed-stream": "^1.1.6", "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0", + "p-defer": "^4.0.1", "race-signal": "^1.0.2", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-collections": { - "version": "5.1.10", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.10.tgz", - "integrity": "sha512-Edr4FBzCgE7FRgc0wfYfcmihQ4GDHwkQP7xMG4oOVoIxHEzuk9Nb2opK9cLbK+nU4oAROgFLzJEJuiG7BGV2hg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.0.tgz", + "integrity": "sha512-I8fXe+3ToYJaXXodddEtj1Ix2gyfz46NzL5i47e776wwCZhtQqjA1kbhTkO8bQogHYSub0SEWxsjm1HL9dtSYw==", "dependencies": { - "@libp2p/interface": "^1.2.0", - "@libp2p/peer-id": "^4.0.10" + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0" } }, "node_modules/@libp2p/peer-id": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.10.tgz", - "integrity": "sha512-cR5dQ5fPcxP4LLSXDgo+TSOhtElZSwRXVSSgT/GM/Vvbua5M91NzsksYfd/lg8XwTCSvTER0qmE6ZIR05vjQrA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.1.1.tgz", + "integrity": "sha512-Fu25drhhdc5JDWb3x33u+5p9I3F2tmYrlx2aiwD5Kv70RLKC9k7B1m4iywBkFE82QKUPtT21IrVYlwsm46znqQ==", "dependencies": { - "@libp2p/interface": "^1.2.0", + "@libp2p/interface": "^1.3.1", "multiformats": "^13.1.0", "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-id-factory": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.0.8.tgz", - "integrity": "sha512-Kk9TqhSY0dPr2RmLUSJj+V5j4vgxrB25G5bf+nNfY79s/YNh0uBIq2ZmeBezXnG4q5egH1e9vbrgHdl7PUllUA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.1.1.tgz", + "integrity": "sha512-8/tRsKQLtgXmM8KklE1XB5VFfHytnbhibH/tRMN9CFZWkpkK1KmFgMxN8t9jZ2+zSK1ozZQQi4jyvkYEyLi5zQ==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-record": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.11.tgz", - "integrity": "sha512-jAi/TX1NSdNPsO0TmJd5fEKQfjdkxxBPmlwofBx2dPqtud/Aac4wa6dvIFQKqGkh8ZeC8FAQbv8jE/a8jgg3MQ==", - "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.16.tgz", + "integrity": "sha512-U6oDKvz40CDSgQkbd7FIORJFB8XCPJ+At4i4JFNtyKX/c34nPINTPCH0lL6exsTnus8L2XHhRC9Hh9zfMyXdNg==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "protons-runtime": "^5.4.0", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-store": { - "version": "10.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.12.tgz", - "integrity": "sha512-lX8wdgX0NNxIcnutJ3hxfhyt+QXl0fVxYY3O1tKtEPoyilbxBA66qVVdU3nVmETkku7TcUuSOEfwevwmQC7qhw==", + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.17.tgz", + "integrity": "sha512-LHo63U8dbeqrdohc19h4M9qkI7iDTpIHG+kLv2A2lNGwrR22op0mk0ARRPUTi8ZBWvLg1Q3cpXih5JBAdU2n7w==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@multiformats/multiaddr": "^12.2.1", "interface-datastore": "^8.2.11", "it-all": "^3.0.4", "mortice": "^3.0.4", "multiformats": "^13.1.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/ping": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.0.13.tgz", - "integrity": "sha512-x3q0IOYKmLzwKbsXKL5fH9EAf/M4/LoGctCbffpr+dxn3cYZOJIMO2ZgJkEBW7DslNTskufgeTsh+wgG0EsWrw==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.0.17.tgz", + "integrity": "sha512-tfbQQOCM+yNSFfdxzOnUBMjXpww47ntT47mgvsfUp/DWHo/3qRaVSmDWHNP1mMSWa30TFcoMUUL2ndZjSf/rMg==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@multiformats/multiaddr": "^12.2.1", "it-first": "^3.0.4", "it-pipe": "^3.0.1", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/pubsub": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-9.0.12.tgz", - "integrity": "sha512-PTrdRXoluKFhnhQVMwk5iDuwrEvOTqM/fjcOE5YZ5mJCCu5NuHGmFPy5hkVeu19IC8vM9LCTCdNLuA71/GUEZg==", - "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", + "version": "9.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-9.0.17.tgz", + "integrity": "sha512-7QOoaQ+DqPcQARUBjKAY2pE81nQ10kqfTi+QWZxhgxGFFjDAWpEnu4mELRXGAyPfo/TZ+P+6kHulMJ/JKjoUJA==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", "it-length-prefixed": "^9.0.4", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.1.0", "p-queue": "^8.0.1", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/record": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.0.tgz", - "integrity": "sha512-qEbLgKj1DY6K0B9LFXXm0hdCmMJdkfrxFMa5/pOe9PufaRPCAOABYmbPYQQmDNI8UrwWJbf47tj+pmDS57SLTw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.1.tgz", + "integrity": "sha512-hflqTiuuMI7GzmTyvycv7y9wvR/xePD/06gcY2ePnWkA1jWlIuebegEbs6+E6MSHQRnmnfecpeZDVRFOQVUSEw==", "dependencies": { "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", @@ -4244,66 +3795,71 @@ } }, "node_modules/@libp2p/tcp": { - "version": "9.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.17.tgz", - "integrity": "sha512-joGy4Zs/2jQ1pe8fD7yr9lLqJ2ZXxZN6dZhhzknf3Opq1YRQ3yyz2eu5G1LNdN0jqyO259UO+TnLtEkruel/QQ==", + "version": "9.0.24", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.24.tgz", + "integrity": "sha512-cv5Kp0z/MOKJNS1OAgYGQNHfXuH1/mdPkUl/3ifjlj1+LMOnWalXm/WM13l2gDCQcwSsEYMRt4wKGcja9J+BTQ==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", + "@libp2p/interface": "^1.3.1", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr": "^12.2.1", "@types/sinon": "^17.0.3", - "stream-to-it": "^0.2.4" + "stream-to-it": "^1.0.0" } }, "node_modules/@libp2p/upnp-nat": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.15.tgz", - "integrity": "sha512-F9NStswMhpKNfzFf91mHxWCw+yKTZfbeadgb5wW6sFh6gQNpMWHdvUDoC8hg2qL8snHcMn5OqGNgE+KvNvSKAw==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.20.tgz", + "integrity": "sha512-Mzcqsu0Ylg2nHk3aiRafFNE1U09vIBvzszD32+RavK18OdC8Heh7OweCtWQNUpyHehrHZAw7Rn3XT+f1aVVaFQ==", "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.13", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "wherearewe": "^2.0.1" } }, "node_modules/@libp2p/utils": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.3.1.tgz", - "integrity": "sha512-FdGzRU50PJLYSEOmVXqqtq27yjUVXkU4QNRZzMVuXF9L/sKgSC2oXwj0Satc9fHx5tG3MCX1ZOSAmYEIl2fu+w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.0.tgz", + "integrity": "sha512-tEBhg5g06vQbR+x51U2N5xAWFNrQpVIVnJZX8urrKcxTRQnJk57Uka2eTEtBj8AxgQ0DDC7a5r2km2X50ja/EA==", "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/interface": "^1.2.0", - "@libp2p/logger": "^4.0.10", + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/logger": "^4.0.12", "@multiformats/multiaddr": "^12.2.1", "@multiformats/multiaddr-matcher": "^1.2.0", + "@sindresorhus/fnv1a": "^3.1.0", + "@types/murmurhash3js-revisited": "^3.0.3", "delay": "^6.0.0", "get-iterator": "^2.0.1", "is-loopback-addr": "^2.0.2", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", + "murmurhash3js-revisited": "^3.0.0", "netmask": "^2.0.2", "p-defer": "^4.0.1", "race-event": "^1.2.0", "race-signal": "^1.0.2", - "uint8arraylist": "^2.4.8" + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/webrtc": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.23.tgz", - "integrity": "sha512-/HI60R9KLivtFCB2vIKDcwyppnKpB2T7BYBej5CCO6KQQM2zEA9/jd/dZ94hs7DUQZCOynJHcUjRKG/rIDyncQ==", + "version": "4.0.31", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.31.tgz", + "integrity": "sha512-+MIQ+qv2j0H2iOOnnyniiYpDbEBq1s7gna+ndElV5A7cimv+4OXdCu8CcmtGdfPvrUbZxFYKmSvbcBF87YLZcw==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "detect-browser": "^5.3.0", "it-length-prefixed": "^9.0.4", "it-protobuf-stream": "^1.1.2", @@ -4311,48 +3867,50 @@ "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", "multihashes": "^4.0.3", - "node-datachannel": "^0.5.5", - "p-defer": "^4.0.0", - "p-event": "^6.0.0", + "node-datachannel": "^0.8.0", + "p-defer": "^4.0.1", + "p-event": "^6.0.1", "p-timeout": "^6.1.2", "protons-runtime": "^5.4.0", "race-signal": "^1.0.2", - "react-native-webrtc": "^118.0.1", + "react-native-webrtc": "^118.0.3", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/websockets": { - "version": "8.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.0.17.tgz", - "integrity": "sha512-b8CebwVYSL+tvoh3yvDzwbY7KgLwdkPb22aPjc7ogB/+XHu/R4cTXSPRHAuzNXNdM4XN5NFtwjyHVBUXSqYRBQ==", + "version": "8.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.0.22.tgz", + "integrity": "sha512-p7vWeQ4Ibg12eeirNatfnTkJWgN+fmoTfp1zqrDLn1j4GE/Ga9z07IPmGjuPBYWr1yiLFhXOzDhWrmaCpOm6zA==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", + "@libp2p/interface": "^1.3.1", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr": "^12.2.1", "@multiformats/multiaddr-to-uri": "^10.0.1", "@types/ws": "^8.5.10", "it-ws": "^6.1.1", - "p-defer": "^4.0.0", + "p-defer": "^4.0.1", "wherearewe": "^2.0.1", "ws": "^8.16.0" } }, "node_modules/@libp2p/webtransport": { - "version": "4.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.0.22.tgz", - "integrity": "sha512-oHI9xJBmknO51P9XqQDa3mN/ed0s043eYvnpp1useealowXedA/4Eth84s9cvVC54o6mCNyPzqIgg2UMBWb7Bw==", + "version": "4.0.30", + "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.0.30.tgz", + "integrity": "sha512-d2yhK1Lis/0FcgoPU98MTLK0wpytXKOAP9XFfBuAbQMQ3qknPMk2HvXvA6R/U3DbNzPaJ8iHOfK9tUwzOyFCiA==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", + "race-signal": "^1.0.2", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@multiformats/base-x": { @@ -4397,9 +3955,9 @@ } }, "node_modules/@multiformats/multiaddr-matcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.0.tgz", - "integrity": "sha512-LH6yR7h3HSNKcxuvvi2UpLuowuVkYC6H9Y3jqmKuTai8XtKnXtW6NcDZFD/ooTBY+H4yX/scoJpjOalHrk5qdQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.1.tgz", + "integrity": "sha512-rcf8RSsvOkJcMoNaGgEPXgoCyvorGBOyNfj1TYX2IHcI8FhqDcuzuYwzuHz6wlsTwi4ADDNU2azGcOXftCnfYA==", "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@multiformats/multiaddr": "^12.0.0", @@ -4414,150 +3972,482 @@ "@multiformats/multiaddr": "^12.0.0" } }, - "node_modules/@multiformats/murmur3": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.8.tgz", - "integrity": "sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA==", + "node_modules/@multiformats/murmur3": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.8.tgz", + "integrity": "sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA==", + "dependencies": { + "multiformats": "^13.0.0", + "murmurhash3js-revisited": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/uri-to-multiaddr": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@multiformats/uri-to-multiaddr/-/uri-to-multiaddr-8.0.0.tgz", + "integrity": "sha512-86O+gY6JTnCv0O/IxTKV+1+GACoEBTr5Cfyh+FdzStWneviz2AZwLK8Hsys5dbfMgT//Vs7FolMiEHURlCel8w==", + "dependencies": { + "@multiformats/multiaddr": "^12.1.14", + "is-ip": "^5.0.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", + "integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@react-native-community/cli": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.6.tgz", + "integrity": "sha512-IqclB7VQ84ye8Fcs89HOpOscY4284VZg2pojHNl8H0Lzd4DadXJWQoxC7zWm8v2f8eyeX2kdhxp2ETD5tceIgA==", + "peer": true, + "dependencies": { + "@react-native-community/cli-clean": "13.6.6", + "@react-native-community/cli-config": "13.6.6", + "@react-native-community/cli-debugger-ui": "13.6.6", + "@react-native-community/cli-doctor": "13.6.6", + "@react-native-community/cli-hermes": "13.6.6", + "@react-native-community/cli-server-api": "13.6.6", + "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-types": "13.6.6", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-13.6.6.tgz", + "integrity": "sha512-cBwJTwl0NyeA4nyMxbhkWZhxtILYkbU3TW3k8AXLg+iGphe0zikYMGB3T+haTvTc6alTyEFwPbimk9bGIqkjAQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "13.6.6", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, + "node_modules/@react-native-community/cli-clean/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-13.6.6.tgz", + "integrity": "sha512-mbG425zCKr8JZhv/j11382arezwS/70juWMsn8j2lmrGTrP1cUdW0MF15CCIFtJsqyK3Qs+FTmqttRpq81QfSg==", + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "13.6.6", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "peer": true, "dependencies": { - "multiformats": "^13.0.0", - "murmurhash3js-revisited": "^3.0.0" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=4" } }, - "node_modules/@noble/ciphers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", - "integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==", - "funding": { - "url": "https://paulmillr.com/funding/" + "node_modules/@react-native-community/cli-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" } }, - "node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "node_modules/@react-native-community/cli-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "peer": true, "dependencies": { - "@noble/hashes": "1.4.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=4" } }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "engines": { - "node": ">= 16" + "node_modules/@react-native-community/cli-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@react-native-community/cli-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "peer": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">= 8" + "node": ">=4" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "node_modules/@react-native-community/cli-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "peer": true, "engines": { - "node": ">= 8" + "node": ">=4" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@react-native-community/cli-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" + "node": ">=8" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.6.tgz", + "integrity": "sha512-Vv9u6eS4vKSDAvdhA0OiQHoA7y39fiPIgJ6biT32tN4avHDtxlc6TWZGiqv7g98SBvDWvoVAmdPLcRf3kU+c8g==", + "peer": true, + "dependencies": { + "serve-static": "^1.13.1" } }, - "node_modules/@react-native-community/cli": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz", - "integrity": "sha512-647OSi6xBb8FbwFqX9zsJxOzu685AWtrOUWHfOkbKD+5LOpGORw+GQo0F9rWZnB68rLQyfKUZWJeaD00pGv5fw==", - "peer": true, - "dependencies": { - "@react-native-community/cli-clean": "12.3.6", - "@react-native-community/cli-config": "12.3.6", - "@react-native-community/cli-debugger-ui": "12.3.6", - "@react-native-community/cli-doctor": "12.3.6", - "@react-native-community/cli-hermes": "12.3.6", - "@react-native-community/cli-plugin-metro": "12.3.6", - "@react-native-community/cli-server-api": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "@react-native-community/cli-types": "12.3.6", + "node_modules/@react-native-community/cli-doctor": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-13.6.6.tgz", + "integrity": "sha512-TWZb5g6EmQe2Ua2TEWNmyaEayvlWH4GmdD9ZC+p8EpKFpB1NpDGMK6sXbpb42TDvwZg5s4TDRplK0PBEA/SVDg==", + "peer": true, + "dependencies": { + "@react-native-community/cli-config": "13.6.6", + "@react-native-community/cli-platform-android": "13.6.6", + "@react-native-community/cli-platform-apple": "13.6.6", + "@react-native-community/cli-platform-ios": "13.6.6", + "@react-native-community/cli-tools": "13.6.6", "chalk": "^4.1.2", - "commander": "^9.4.1", + "command-exists": "^1.2.8", "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "react-native": "build/bin.js" - }, + "hermes-profile-transformer": "^0.0.6", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "peer": true, "engines": { - "node": ">=18" + "node": ">=6" } }, - "node_modules/@react-native-community/cli-clean": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz", - "integrity": "sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==", + "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "execa": "^5.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { + "node_modules/@react-native-community/cli-doctor/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -4580,7 +4470,28 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { + "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", @@ -4589,7 +4500,7 @@ "node": ">=10.17.0" } }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { + "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", @@ -4601,7 +4512,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { + "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -4610,7 +4521,7 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { + "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -4622,7 +4533,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-clean/node_modules/onetime": { + "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -4637,63 +4548,125 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-clean/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/@react-native-community/cli-doctor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-hermes": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-13.6.6.tgz", + "integrity": "sha512-La5Ie+NGaRl3klei6WxKoOxmCUSGGxpOk6vU5pEGf0/O7ky+Ay0io+zXYUZqlNMi/cGpO7ZUijakBYOB/uyuFg==", + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-android": "13.6.6", + "@react-native-community/cli-tools": "13.6.6", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6" + } + }, + "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-hermes/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "peer": true, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/@react-native-community/cli-config": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.6.tgz", - "integrity": "sha512-JGWSYQ9EAK6m2v0abXwFLEfsqJ1zkhzZ4CV261QZF9MoUNB6h57a274h1MLQR9mG6Tsh38wBUuNfEPUvS1vYew==", + "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "glob": "^7.1.3", - "joi": "^17.2.1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.6.tgz", - "integrity": "sha512-SjUKKsx5FmcK9G6Pb6UBFT0s9JexVStK5WInmANw75Hm7YokVvHEgtprQDz2Uvy5znX5g2ujzrkIU//T15KQzA==", + "node_modules/@react-native-community/cli-platform-android": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.6.tgz", + "integrity": "sha512-/tMwkBeNxh84syiSwNlYtmUz/Ppc+HfKtdopL/5RB+fd3SV1/5/NPNjMlyLNgFKnpxvKCInQ7dnl6jGHJjeHjg==", "peer": true, "dependencies": { - "serve-static": "^1.13.1" + "@react-native-community/cli-tools": "13.6.6", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.2.4", + "logkitty": "^0.7.1" } }, - "node_modules/@react-native-community/cli-doctor": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.6.tgz", - "integrity": "sha512-fvBDv2lTthfw4WOQKkdTop2PlE9GtfrlNnpjB818MhcdEnPjfQw5YaTUcnNEGsvGomdCs1MVRMgYXXwPSN6OvQ==", + "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "peer": true, "dependencies": { - "@react-native-community/cli-config": "12.3.6", - "@react-native-community/cli-platform-android": "12.3.6", - "@react-native-community/cli-platform-ios": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { + "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -4716,7 +4689,28 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { + "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", @@ -4725,7 +4719,7 @@ "node": ">=10.17.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { + "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", @@ -4737,7 +4731,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { + "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -4746,7 +4740,7 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { + "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -4758,7 +4752,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { + "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -4773,7 +4767,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-final-newline": { + "node_modules/@react-native-community/cli-platform-android/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", @@ -4782,33 +4782,49 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz", - "integrity": "sha512-sNGwfOCl8OAIjWCkwuLpP8NZbuO0dhDI/2W7NeOGDzIBsf4/c4MptTrULWtGIH9okVPLSPX0NnRyGQ+mSwWyuQ==", + "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "peer": true, "dependencies": { - "@react-native-community/cli-platform-android": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.6.tgz", - "integrity": "sha512-DeDDAB8lHpuGIAPXeeD9Qu2+/wDTFPo99c8uSW49L0hkmZJixzvvvffbGQAYk32H0TmaI7rzvzH+qzu7z3891g==", + "node_modules/@react-native-community/cli-platform-apple": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.6.tgz", + "integrity": "sha512-bOmSSwoqNNT3AmCRZXEMYKz1Jf1l2F86Nhs7qBcXdY/sGiJ+Flng564LOqvdAlVLTbkgz47KjNKCS2pP4Jg0Mg==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", + "@react-native-community/cli-tools": "13.6.6", "chalk": "^4.1.2", "execa": "^5.0.0", - "fast-xml-parser": "^4.2.4", - "glob": "^7.1.3", - "logkitty": "^0.7.1" + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.0.12", + "ora": "^5.4.1" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-platform-apple/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -4831,7 +4847,28 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-apple/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-apple/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", @@ -4840,7 +4877,7 @@ "node": ">=10.17.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", @@ -4852,7 +4889,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -4861,7 +4898,7 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -4873,7 +4910,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -4888,7 +4925,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/strip-final-newline": { + "node_modules/@react-native-community/cli-platform-apple/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, + "node_modules/@react-native-community/cli-platform-apple/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", @@ -4897,21 +4940,89 @@ "node": ">=6" } }, + "node_modules/@react-native-community/cli-platform-apple/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.6.tgz", - "integrity": "sha512-3eZ0jMCkKUO58wzPWlvAPRqezVKm9EPZyaPyHbRPWU8qw7JqkvnRlWIaYDGpjCJgVW4k2hKsEursLtYKb188tg==", + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.6.tgz", + "integrity": "sha512-vjDnRwhlSN5ryqKTas6/DPkxuouuyFBAqAROH4FR1cspTbn6v78JTZKDmtQy9JMMo7N5vZj1kASU5vbFep9IOQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-apple": "13.6.6" + } + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-13.6.6.tgz", + "integrity": "sha512-ZtCXxoFlM7oDv3iZ3wsrT3SamhtUJuIkX2WePLPlN5bcbq7zimbPm2lHyicNJtpcGQ5ymsgpUWPCNZsWQhXBqQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-debugger-ui": "13.6.6", + "@react-native-community/cli-tools": "13.6.6", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^6.2.2" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-13.6.6.tgz", + "integrity": "sha512-ptOnn4AJczY5njvbdK91k4hcYazDnGtEPrqIwEI+k/CTBHNdb27Rsm2OZ7ye6f7otLBqF8gj/hK6QzJs8CEMgw==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.6", + "appdirsjs": "^1.2.4", "chalk": "^4.1.2", "execa": "^5.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", - "ora": "^5.4.1" + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { + "node_modules/@react-native-community/cli-tools/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -4934,7 +5045,44 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/human-signals": { + "node_modules/@react-native-community/cli-tools/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", @@ -4943,19 +5091,34 @@ "node": ">=10.17.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { + "node_modules/@react-native-community/cli-tools/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "peer": true, "engines": { - "node": ">=8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/mimic-fn": { + "node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -4964,7 +5127,7 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { + "node_modules/@react-native-community/cli-tools/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -4976,7 +5139,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/onetime": { + "node_modules/@react-native-community/cli-tools/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -4991,101 +5154,77 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.6.tgz", - "integrity": "sha512-3jxSBQt4fkS+KtHCPSyB5auIT+KKIrPCv9Dk14FbvOaEh9erUWEm/5PZWmtboW1z7CYeNbFMeXm9fM2xwtVOpg==", - "peer": true - }, - "node_modules/@react-native-community/cli-server-api": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.6.tgz", - "integrity": "sha512-80NIMzo8b2W+PL0Jd7NjiJW9mgaT8Y8wsIT/lh6mAvYH7mK0ecDJUYUTAAv79Tbo1iCGPAr3T295DlVtS8s4yQ==", + "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "peer": true, "dependencies": { - "@react-native-community/cli-debugger-ui": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "node_modules/@react-native-community/cli-tools/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "peer": true, "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.6.tgz", - "integrity": "sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==", + "node_modules/@react-native-community/cli-tools/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, + "node_modules/@react-native-community/cli-tools/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "peer": true, - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/@react-native-community/cli-tools/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "peer": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@react-native-community/cli-tools/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, "engines": { "node": ">=10" }, @@ -5093,43 +5232,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/@react-native-community/cli-types": { + "version": "13.6.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-13.6.6.tgz", + "integrity": "sha512-733iaYzlmvNK7XYbnWlMjdE+2k0hlTBJW071af/xb6Bs+hbJqBP9c03FZuYH2hFFwDDntwj05bkri/P7VgSxug==", "peer": true, "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "joi": "^17.2.1" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@react-native-community/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "peer": true, "dependencies": { - "p-limit": "^3.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native-community/cli-types": { - "version": "12.3.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.6.tgz", - "integrity": "sha512-xPqTgcUtZowQ8WKOkI9TLGBwH2bGggOC4d2FFaIRST3gTcjrEeGRNeR5aXCzJFIgItIft8sd7p2oKEdy90+01Q==", + "node_modules/@react-native-community/cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "peer": true, - "dependencies": { - "joi": "^17.2.1" + "engines": { + "node": "^12.20.0 || >=14" } }, "node_modules/@react-native-community/cli/node_modules/execa": { @@ -5155,6 +5289,40 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/@react-native-community/cli/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native-community/cli/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5176,6 +5344,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@react-native-community/cli/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native-community/cli/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5212,6 +5392,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@react-native-community/cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, "node_modules/@react-native-community/cli/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -5221,37 +5443,50 @@ "node": ">=6" } }, + "node_modules/@react-native-community/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native/assets-registry": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", - "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.83.tgz", + "integrity": "sha512-2vkLMVnp+YTZYTNSDIBZojSsjz8sl5PscP3j4GcV6idD8V978SZfwFlk8K0ti0BzRs11mzL0Pj17km597S/eTQ==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", - "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.83.tgz", + "integrity": "sha512-+S0st3t4Ro00bi9gjT1jnK8qTFOU+CwmziA7U9odKyWrCoRJrgmrvogq/Dr1YXlpFxexiGIupGut1VHxr+fxJA==", "peer": true, "dependencies": { - "@react-native/codegen": "0.73.3" + "@react-native/codegen": "0.74.83" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.73.21", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", - "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.83.tgz", + "integrity": "sha512-KJuu3XyVh3qgyUer+rEqh9a/JoUxsDOzkJNfRpDyXiAyjDRoVch60X/Xa/NcEQ93iCVHAWs0yQ+XGNGIBCYE6g==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.18.0", "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", "@babel/plugin-proposal-numeric-separator": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.20.0", @@ -5287,7 +5522,7 @@ "@babel/plugin-transform-typescript": "^7.5.0", "@babel/plugin-transform-unicode-regex": "^7.0.0", "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.4", + "@react-native/babel-plugin-codegen": "0.74.83", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -5299,14 +5534,14 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", - "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.83.tgz", + "integrity": "sha512-GgvgHS3Aa2J8/mp1uC/zU8HuTh8ZT5jz7a4mVMWPw7+rGyv70Ba8uOVBq6UH2Q08o617IATYc+0HfyzAfm4n0w==", "peer": true, "dependencies": { "@babel/parser": "^7.20.0", - "flow-parser": "^0.206.0", "glob": "^7.1.1", + "hermes-parser": "0.19.1", "invariant": "^2.2.4", "jscodeshift": "^0.14.0", "mkdirp": "^0.5.1", @@ -5320,27 +5555,44 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.17", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.17.tgz", - "integrity": "sha512-F3PXZkcHg+1ARIr6FRQCQiB7ZAA+MQXGmq051metRscoLvgYJwj7dgC8pvgy0kexzUkHu5BNKrZeySzUft3xuQ==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.83.tgz", + "integrity": "sha512-7GAFjFOg1mFSj8bnFNQS4u8u7+QtrEeflUIDVZGEfBZQ3wMNI5ycBzbBGycsZYiq00Xvoc6eKFC7kvIaqeJpUQ==", "peer": true, "dependencies": { - "@react-native-community/cli-server-api": "12.3.6", - "@react-native-community/cli-tools": "12.3.6", - "@react-native/dev-middleware": "0.73.8", - "@react-native/metro-babel-transformer": "0.73.15", + "@react-native-community/cli-server-api": "13.6.6", + "@react-native-community/cli-tools": "13.6.6", + "@react-native/dev-middleware": "0.74.83", + "@react-native/metro-babel-transformer": "0.74.83", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", "metro-config": "^0.80.3", "metro-core": "^0.80.3", "node-fetch": "^2.2.0", + "querystring": "^0.2.1", "readline": "^1.3.0" }, "engines": { "node": ">=18" } }, + "node_modules/@react-native/community-cli-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@react-native/community-cli-plugin/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -5361,7 +5613,28 @@ "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" } }, "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { @@ -5421,6 +5694,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@react-native/community-cli-plugin/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, "node_modules/@react-native/community-cli-plugin/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -5430,29 +5709,43 @@ "node": ">=6" } }, + "node_modules/@react-native/community-cli-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native/debugger-frontend": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", - "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.83.tgz", + "integrity": "sha512-RGQlVUegBRxAUF9c1ss1ssaHZh6CO+7awgtI9sDeU0PzDZY/40ImoPD5m0o0SI6nXoVzbPtcMGzU+VO590pRfA==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.73.8", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", - "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.83.tgz", + "integrity": "sha512-UH8iriqnf7N4Hpi20D7M2FdvSANwTVStwFCSD7VMU9agJX88Yk0D1T6Meh2RMhUu4kY2bv8sTkNRm7LmxvZqgA==", "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.73.3", + "@react-native/debugger-frontend": "0.74.83", + "@rnx-kit/chromium-edge-launcher": "^1.0.0", "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^1.0.0", "connect": "^3.6.5", "debug": "^2.2.0", "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", "open": "^7.0.3", + "selfsigned": "^2.4.1", "serve-static": "^1.13.1", "temp-dir": "^2.0.0", "ws": "^6.2.2" @@ -5502,32 +5795,32 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", - "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.83.tgz", + "integrity": "sha512-Pw2BWVyOHoBuJVKxGVYF6/GSZRf6+v1Ygc+ULGz5t20N8qzRWPa2fRZWqoxsN7TkNLPsECYY8gooOl7okOcPAQ==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", - "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.83.tgz", + "integrity": "sha512-/t74n8r6wFhw4JEoOj3bN71N1NDLqaawB75uKAsSjeCwIR9AfCxlzZG0etsXtOexkY9KMeZIQ7YwRPqUdNXuqw==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.15", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", - "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.83.tgz", + "integrity": "sha512-hGdx5N8diu8y+GW/ED39vTZa9Jx1di2ZZ0aapbhH4egN1agIAusj5jXTccfNBwwWF93aJ5oVbRzfteZgjbutKg==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.21", - "hermes-parser": "0.15.0", + "@react-native/babel-preset": "0.74.83", + "hermes-parser": "0.19.1", "nullthrows": "^1.1.1" }, "engines": { @@ -5538,15 +5831,15 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", - "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.83.tgz", + "integrity": "sha512-jhCY95gRDE44qYawWVvhTjTplW1g+JtKTKM3f8xYT1dJtJ8QWv+gqEtKcfmOHfDkSDaMKG0AGBaDTSK8GXLH8Q==", "peer": true }, "node_modules/@react-native/virtualized-lists": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", - "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "version": "0.74.83", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.83.tgz", + "integrity": "sha512-rmaLeE34rj7py4FxTod7iMTC7BAsm+HrGA8WxYmEJeyTV7WSaxAkosKoYBz8038mOiwnG9VwA/7FrB6bEQvn1A==", "peer": true, "dependencies": { "invariant": "^2.2.4", @@ -5556,9 +5849,261 @@ "node": ">=18" }, "peerDependencies": { + "@types/react": "^18.2.6", + "react": "*", "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rnx-kit/chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==", + "peer": true, + "dependencies": { + "@types/node": "^18.0.0", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=14.15" + } + }, + "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/@types/node": { + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -5586,6 +6131,17 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "peer": true }, + "node_modules/@sindresorhus/fnv1a": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", + "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -5604,33 +6160,113 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@swc/core": { + "node_modules/@swc/core": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.18.tgz", + "integrity": "sha512-VChk3ldLhmVoX3Hd2M3Y4j960T0lo2Zus60iZoWST6P65RVPt8BatFVVPAB9dABy1dB5zn1BCpHlH85yXVysQw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "swcx": "run_swcx.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.18", + "@swc/core-darwin-x64": "1.3.18", + "@swc/core-linux-arm-gnueabihf": "1.3.18", + "@swc/core-linux-arm64-gnu": "1.3.18", + "@swc/core-linux-arm64-musl": "1.3.18", + "@swc/core-linux-x64-gnu": "1.3.18", + "@swc/core-linux-x64-musl": "1.3.18", + "@swc/core-win32-arm64-msvc": "1.3.18", + "@swc/core-win32-ia32-msvc": "1.3.18", + "@swc/core-win32-x64-msvc": "1.3.18" + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.18.tgz", + "integrity": "sha512-4UEQ+LyzDFTszEy4LCU50h4cjVNJcNwD87aVBT/8i6YXj5dyMki/TrkIQ6Bhv7g5beg2GRncB2ndjN66r8I8+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.18.tgz", + "integrity": "sha512-DSCd7eVr+4ukffNnvhrFmUoCF0VLOXPgGmdwm6u0irLWOLtr2VZNZcf7UF+t/Y9jPKmXz3OY6lVgwtjxZhiklQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.18.tgz", + "integrity": "sha512-9dy6qJiWAls9OrBvrWbFDbjEkuOPrEP6OsKyrQWTMqLjCLwgLa3g4yC0YtPdUa/A8uyNVKtRcq+NXoKW+mP/QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.18.tgz", - "integrity": "sha512-VChk3ldLhmVoX3Hd2M3Y4j960T0lo2Zus60iZoWST6P65RVPt8BatFVVPAB9dABy1dB5zn1BCpHlH85yXVysQw==", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.18.tgz", + "integrity": "sha512-8FZjiUSM4JBQTD4sV7Y6BNMdo0oDlqa8xYVaAimuIBL8ixD/Fb+0GIxKdB59yKRVQyuXJRa6Pwzd7zk3wY5T0Q==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, - "bin": { - "swcx": "run_swcx.js" - }, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.18.tgz", + "integrity": "sha512-0zNqfFeAHZp37lu+lTVvZKfDM10EIoYJtv9sWz+0EA5mkzwj4NtC3ialTIjcPAyJ9Oq4zBtToW2hv7qEtyBHZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.18", - "@swc/core-darwin-x64": "1.3.18", - "@swc/core-linux-arm-gnueabihf": "1.3.18", - "@swc/core-linux-arm64-gnu": "1.3.18", - "@swc/core-linux-arm64-musl": "1.3.18", - "@swc/core-linux-x64-gnu": "1.3.18", - "@swc/core-linux-x64-musl": "1.3.18", - "@swc/core-win32-arm64-msvc": "1.3.18", - "@swc/core-win32-ia32-msvc": "1.3.18", - "@swc/core-win32-x64-msvc": "1.3.18" } }, "node_modules/@swc/core-linux-x64-gnu": { @@ -5665,6 +6301,54 @@ "node": ">=10" } }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.18.tgz", + "integrity": "sha512-G1Lu/sP+v34lwsGFreklnCdxygMLmobyLY31cNPd0i47ZwgrGowuTV34Mcqfc4AWRkayqVAIlb/WWIZ1+qemcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.18.tgz", + "integrity": "sha512-Uu+m5BPemw5ZiG6LaF+pP0qFQuIXF55wMZNa0Dbl/16hF7ci6q941MT6CqeK5LQQ52FVVqeYO5lDk5CggaA3Mw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.18.tgz", + "integrity": "sha512-9o8uFNsPmWB5FFQSDCsI/KVBSHuAILEwB/hMvbUxKtZeSWAQTm5BqbNPi6X11KJ3MdyoJn7zPejj3grL3dcd/w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -5681,9 +6365,9 @@ } }, "node_modules/@types/busboy": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.3.tgz", - "integrity": "sha512-YMBLFN/xBD8bnqywIlGyYqsNFXu6bsiY7h3Ae0kO17qEuTjsqeyYMRPSUDacIKIquws2Y6KjmxAyNx8xB3xQbw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", "dev": true, "dependencies": { "@types/node": "*" @@ -5715,6 +6399,11 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -5728,9 +6417,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", "dev": true, "dependencies": { "@types/node": "*", @@ -5776,9 +6465,9 @@ "dev": true }, "node_modules/@types/luxon": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.8.tgz", - "integrity": "sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" }, "node_modules/@types/mime": { "version": "1.3.5", @@ -5792,15 +6481,6 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, - "node_modules/@types/multer": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", - "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/multicast-dns": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", @@ -5816,13 +6496,22 @@ "integrity": "sha512-QvlqvYtGBYIDeO8dFdY4djkRubcrc+yTJtBc7n8VZPlJDUS/00A+PssbvERM8f9bYRmcaSEHPZgZojeQj7kzAA==" }, "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "version": "20.12.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz", + "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==", "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -5830,9 +6519,9 @@ "dev": true }, "node_modules/@types/qs": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", - "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", "dev": true }, "node_modules/@types/range-parser": { @@ -5863,14 +6552,14 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sinon": { @@ -6181,15 +6870,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" }, "funding": { "type": "github", @@ -6240,6 +6929,83 @@ "strip-ansi": "^5.0.0" } }, + "node_modules/ansi-fragments/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-fragments/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/ansi-fragments/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "peer": true + }, + "node_modules/ansi-fragments/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "peer": true + }, + "node_modules/ansi-fragments/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-fragments/node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -6290,18 +7056,18 @@ "peer": true }, "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-2.0.0.tgz", + "integrity": "sha512-yUPXgerKgcuwakzrRyklfhX+Ma2aYYMjb+BO2RPUwq+tk928V/i5DFWcCUS3hQhj468N+Ktmwb0tfbEtmfC6WA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/array-flatten": { "version": "1.1.1", @@ -6396,13 +7162,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", - "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", "peer": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.1", + "@babel/helper-define-polyfill-provider": "^0.6.2", "semver": "^6.3.1" }, "peerDependencies": { @@ -6432,12 +7198,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", - "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6457,6 +7223,17 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base32-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base32-encode/-/base32-encode-2.0.0.tgz", + "integrity": "sha512-mlmkfc2WqdDtMl/id4qm3A7RjW6jxcbAoMjdRmsPiwQP0ufD4oXItYMnPgVHe80lnAIy+1xwzhHE1s4FoIceSw==", + "dependencies": { + "to-data-view": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6498,6 +7275,19 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/blockstore-core": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.1.tgz", @@ -6529,6 +7319,36 @@ "multiformats": "^13.0.1" } }, + "node_modules/blockstore-fs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/blockstore-fs/node_modules/it-glob": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.7.tgz", + "integrity": "sha512-FjL2rAsGu566sMVKexFG5ciDoKoi1lgwXlSE6DW0yVbGRyvrlzi0OQ3fbfrw89dpIAzJFHnLNQwZS4yRkt3d/Q==", + "dependencies": { + "minimatch": "^9.0.4" + } + }, + "node_modules/blockstore-fs/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -6552,14 +7372,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6573,17 +7385,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6605,9 +7406,9 @@ } }, "node_modules/browser-readablestream-to-it": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.5.tgz", - "integrity": "sha512-obLCT9jnxAeZlbaRWluUiZrcSJEoi2JkM0eoiJqlIP7MFwZwZjcB6giZvD343PXfr96ilD91M/wFqFvyAZq+Gg==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.7.tgz", + "integrity": "sha512-g1Aznml3HmqTLSXylZhGwdfnAa67+vlNAYhT9ROJZkAxY7yYmWusND10olvCMPe4sVhZyVwn5tPkRzOg85kBEg==" }, "node_modules/browserslist": { "version": "4.23.0", @@ -6676,7 +7477,8 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "peer": true }, "node_modules/busboy": { "version": "1.6.0", @@ -6690,10 +7492,9 @@ } }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "peer": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } @@ -6728,6 +7529,15 @@ "node": ">=4" } }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/caller-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", @@ -6741,12 +7551,12 @@ } }, "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "peer": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/camelcase": { @@ -6788,9 +7598,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001600", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", - "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", + "version": "1.0.30001615", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001615.tgz", + "integrity": "sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==", "funding": [ { "type": "opencollective", @@ -6816,39 +7626,17 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -6879,6 +7667,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -6902,47 +7702,6 @@ "node": ">=12.13.0" } }, - "node_modules/chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "node_modules/chromium-edge-launcher/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/chromium-edge-launcher/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -7009,52 +7768,12 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/cli-truncate/node_modules/emoji-regex": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", "dev": true }, - "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/cli-truncate/node_modules/string-width": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", @@ -7109,15 +7828,20 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/clone": { @@ -7143,6 +7867,20 @@ "node": ">=6" } }, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "dependencies": { + "is-regexp": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7160,10 +7898,10 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "peer": true + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true }, "node_modules/command-exists": { "version": "1.2.9", @@ -7172,12 +7910,12 @@ "peer": true }, "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "peer": true, + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, "engines": { - "node": "^12.20.0 || >=14" + "node": ">=16" } }, "node_modules/commondir": { @@ -7226,6 +7964,15 @@ "node": ">= 0.8.0" } }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -7241,47 +7988,17 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "peer": true }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "peer": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -7306,12 +8023,51 @@ "ms": "2.0.0" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true - }, + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -7323,25 +8079,6 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -7392,13 +8129,12 @@ "node": ">=16" } }, - "node_modules/conventional-commits-parser/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", "engines": { - "node": ">=16.10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7424,9 +8160,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", + "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", "peer": true, "dependencies": { "browserslist": "^4.23.0" @@ -7439,29 +8175,58 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "peer": true }, "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "peer": true, + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz", + "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==", + "dev": true, + "dependencies": { + "jiti": "^1.19.1" + }, + "engines": { + "node": ">=v16" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=8.2", + "typescript": ">=4" } }, "node_modules/cron": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.6.tgz", - "integrity": "sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", "dependencies": { - "@types/luxon": "~3.3.0", + "@types/luxon": "~3.4.0", "luxon": "~3.4.0" } }, @@ -7541,6 +8306,36 @@ "it-parallel-batch": "^3.0.4" } }, + "node_modules/datastore-fs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/datastore-fs/node_modules/it-glob": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.7.tgz", + "integrity": "sha512-FjL2rAsGu566sMVKexFG5ciDoKoi1lgwXlSE6DW0yVbGRyvrlzi0OQ3fbfrw89dpIAzJFHnLNQwZS4yRkt3d/Q==", + "dependencies": { + "minimatch": "^9.0.4" + } + }, + "node_modules/datastore-fs/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -7551,9 +8346,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", "peer": true }, "node_modules/debug": { @@ -7653,6 +8448,52 @@ "node": ">= 16" } }, + "node_modules/default-gateway/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-gateway/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -7714,20 +8555,6 @@ "node": ">= 0.8" } }, - "node_modules/deprecated-react-native-prop-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", - "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", - "peer": true, - "dependencies": { - "@react-native/normalize-colors": "^0.73.0", - "invariant": "^2.2.4", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -7809,9 +8636,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.721", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", - "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==", + "version": "1.4.755", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.755.tgz", + "integrity": "sha512-9nop+3jZxSHIxe1EzEUcjDXzK+3qOv3fY5w0sE88nIZUntbv1aXWmoxGWlklX5XSO4txCpLssWkUSh8RQPovBg==", "peer": true }, "node_modules/emoji-regex": { @@ -7819,6 +8646,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encode-utf8": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-2.0.0.tgz", + "integrity": "sha512-3EyMFxZj1/7oMotElDQUEQcP7N4TIe1aJ0m1uBDoyQ8I2LBHhBsXx8P3KsPbqNlGzG+NYxFwEauUwMPHZg3YDQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -7845,9 +8680,9 @@ } }, "node_modules/envinfo": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", - "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", "peer": true, "bin": { "envinfo": "dist/cli.js" @@ -8168,67 +9003,69 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "is-glob": "^4.0.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=8" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8274,22 +9111,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "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, "dependencies": { - "ansi-regex": "^5.0.1" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "engines": { "node": ">=10" @@ -8404,22 +9250,23 @@ } }, "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -8482,66 +9329,11 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -8578,7 +9370,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -8590,6 +9381,17 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8647,7 +9449,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -8725,17 +9526,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "peer": true, + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -8746,7 +9546,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8754,8 +9553,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-cache-dir": { "version": "2.1.0", @@ -8772,15 +9570,20 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -8825,14 +9628,22 @@ "peer": true }, "node_modules/flow-parser": { - "version": "0.206.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", - "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "version": "0.235.1", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.235.1.tgz", + "integrity": "sha512-s04193L4JE+ntEcQXbD6jxRRlyj9QXcgEl2W6xSjH4l9x4b0eHoCHfbYHjqf9LdZFUiM5LhgpiqsvLj/AyOyYQ==", "peer": true, "engines": { "node": ">=0.4.0" } }, + "node_modules/fmix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fmix/-/fmix-1.0.0.tgz", + "integrity": "sha512-PIaqOGvVH5P+R92Ywy5PumsNEHvondVQh42SGOmkA9A0ZTFbfguzZpjZ/Gy3WVRUqT9Ia8k5tWlJeiZQzRHA7g==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -8849,18 +9660,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8905,11 +9704,35 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/fs-temp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-temp/-/fs-temp-2.0.1.tgz", + "integrity": "sha512-WYE7cUGOA0xRKsiYxNf/+WDuj0T20OtX85bVhsXpY+wJmjrRIUQftTI6JF9PVpo2RpGkh0l8lZsV8zszisYQ0Q==", + "dependencies": { + "random-path": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -8918,6 +9741,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8971,11 +9805,12 @@ "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" }, "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8998,18 +9833,6 @@ "node": ">=16" } }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -9035,15 +9858,15 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/global-directory": { @@ -9061,22 +9884,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "peer": true, + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby": { @@ -9163,11 +9983,37 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/gts/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "node_modules/gts/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/gts/node_modules/chalk": { + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, "node_modules/gts/node_modules/eslint": { "version": "8.53.0", @@ -9263,33 +10109,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gts/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gts/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/gts/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gts/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/gts/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -9311,17 +10151,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gts/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "node_modules/gts/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/gts/node_modules/locate-path": { "version": "6.0.0", @@ -9338,6 +10172,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gts/node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gts/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -9404,6 +10264,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gts/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/gts/node_modules/prettier": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", @@ -9434,17 +10303,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/gts/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "node_modules/gts/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, "node_modules/gts/node_modules/strip-final-newline": { "version": "2.0.0", @@ -9455,10 +10318,22 @@ "node": ">=6" } }, + "node_modules/gts/node_modules/supports-color": { + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gts/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, "engines": { "node": ">=10" @@ -9467,17 +10342,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gts/node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/gts/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" + } + }, + "node_modules/gts/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/hamt-sharding": { @@ -9506,6 +10389,14 @@ "node": ">=4" } }, + "node_modules/has-own-property": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-property/-/has-own-property-2.0.0.tgz", + "integrity": "sha512-oupojxEPq/nfAshi0hOFDjpmYO4JXtymFyPC9YSBmFqGamZ2zUgrCkYInt5tf7f1j6iumlzJBpfLNRxZIQCM0w==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -9556,22 +10447,22 @@ } }, "node_modules/helia": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/helia/-/helia-4.1.0.tgz", - "integrity": "sha512-ZDRW4fir8GdUKjI+M21BpzGGzbgWeD5G4LGpMEiXBYwwgIdn+ITCeLIIIOA9pPc0BJSIOJGpsMtaCV8FztgQIg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.1.tgz", + "integrity": "sha512-b3ZXDeddNtSiUbz8mJumL0chTRYjGGjmPb5qQhilXOG5PKFwffffYaR0Py4eaZ5eTlBlCfKCqjiBNeS4oerAWA==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/block-brokers": "^2.0.3", + "@helia/block-brokers": "^3.0.0", "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.1.0", - "@helia/routers": "^1.0.2", - "@helia/utils": "^0.1.0", + "@helia/interface": "^4.3.0", + "@helia/routers": "^1.1.0", + "@helia/utils": "^0.3.0", "@libp2p/autonat": "^1.0.13", "@libp2p/bootstrap": "^10.0.16", "@libp2p/circuit-relay-v2": "^1.0.16", "@libp2p/dcutr": "^1.0.13", - "@libp2p/identify": "^1.0.15", + "@libp2p/identify": "^2.0.0", "@libp2p/interface": "^1.1.4", "@libp2p/kad-dht": "^12.0.8", "@libp2p/keychain": "^4.0.9", @@ -9594,6 +10485,79 @@ "multiformats": "^13.1.0" } }, + "node_modules/helia/node_modules/@helia/block-brokers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.0.tgz", + "integrity": "sha512-f93Lklv+ZyySxIAiZDG6p2kRF+OV3hdhVUAyWNK6TWpyxuFNj45cYQci9nz/ZTmEHqg2ylswwiq4lEGml3WS7Q==", + "dependencies": { + "@helia/bitswap": "^1.1.0", + "@helia/interface": "^4.3.0", + "@helia/utils": "^0.3.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/utils": "^5.2.6", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "@multiformats/multiaddr-to-uri": "^10.0.1", + "interface-blockstore": "^5.2.10", + "interface-store": "^5.1.8", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0" + } + }, + "node_modules/helia/node_modules/@helia/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-lCOd5DsimmUrp9+TyxWDkvhDFvI+Hgn7PevQHGya3AwmJXxsuCY6FtRgd7YkdaoqAm4+q7L710UnUtp5AmHlVA==", + "dependencies": { + "@helia/interface": "^4.3.0", + "@ipld/dag-cbor": "^9.2.0", + "@ipld/dag-json": "^10.2.0", + "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", + "@libp2p/interface": "^1.1.4", + "@libp2p/logger": "^4.0.7", + "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", + "blockstore-core": "^4.4.1", + "cborg": "^4.0.9", + "interface-blockstore": "^5.2.10", + "interface-datastore": "^8.2.11", + "interface-store": "^5.1.8", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-foreach": "^2.0.6", + "it-merge": "^3.0.3", + "mortice": "^3.0.4", + "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/helia/node_modules/@libp2p/identify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.0.0.tgz", + "integrity": "sha512-1vdXRHIkENE+iyN4yXo00ia02I63UCm4FVeM5pQZZxbeZz4+1Eo8dJuDYU9h0SFTghhkIYeyTT6CnkqfvMcy8A==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "it-drain": "^3.0.7", + "it-parallel": "^3.0.7", + "it-protobuf-stream": "^1.1.2", + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3", + "wherearewe": "^2.0.1" + } + }, "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", @@ -9601,18 +10565,18 @@ "dev": true }, "node_modules/hermes-estree": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", - "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", + "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==", "peer": true }, "node_modules/hermes-parser": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", - "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", + "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", "peer": true, "dependencies": { - "hermes-estree": "0.15.0" + "hermes-estree": "0.19.1" } }, "node_modules/hermes-profile-transformer": { @@ -9639,6 +10603,24 @@ "node": ">=10" } }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -9654,20 +10636,13 @@ "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, "engines": { - "node": ">=14.18.0" + "node": ">=16.17.0" } }, "node_modules/husky": { @@ -9746,22 +10721,34 @@ } }, "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "peer": true, + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } }, "node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true, "funding": { "type": "github", @@ -9800,9 +10787,13 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/inquirer": { "version": "7.3.3", @@ -9828,13 +10819,38 @@ "node": ">=8.0.0" } }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/inquirer/node_modules/chalk": { + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "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, "dependencies": { - "ansi-regex": "^5.0.1" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" @@ -9872,6 +10888,17 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -9881,13 +10908,13 @@ } }, "node_modules/ipfs-unixfs": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.3.tgz", - "integrity": "sha512-sy6Koojwm/EcM8yvDlycRYA89C8wIcLcGTMMpqnCPUtqTCdl+JxsuPNCBgAu7tmO8Nipm7Tv7f0g/erxTGKKRA==", + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.4.tgz", + "integrity": "sha512-RE4nyx5qgG2w7JOLj0Y0D7SfAR1ZkEdramNaBx0OSD4DlQ2Y2NORgc4FHfej3Pgy31v+QISDVP1pQJhdv3bUUg==", "dependencies": { "err-code": "^3.0.1", - "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.3" + "protons-runtime": "^5.4.0", + "uint8arraylist": "^2.4.8" } }, "node_modules/ipfs-unixfs-exporter": { @@ -9915,26 +10942,26 @@ } }, "node_modules/ipfs-unixfs-importer": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.2.4.tgz", - "integrity": "sha512-3b7d/pLPwGvAEXvpJ0WyYlbn2pb2j7qY6FayuMSzbZNdFxdJ82l6VkJ9vK1d/G/AHx+0ZfB06eSdGKjX0GVCAg==", + "version": "15.2.5", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.2.5.tgz", + "integrity": "sha512-K8tybgmhOthQ/ThPcjohYnTaVhv1hi3YKo+gPREBXcMUUG7Yd2HYfz1jExuOfhaMzICl0hDKwUMJsaU6FXMJew==", "dependencies": { - "@ipld/dag-pb": "^4.0.0", - "@multiformats/murmur3": "^2.0.0", + "@ipld/dag-pb": "^4.1.0", + "@multiformats/murmur3": "^2.1.8", "err-code": "^3.0.1", - "hamt-sharding": "^3.0.0", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.1", + "hamt-sharding": "^3.0.6", + "interface-blockstore": "^5.2.10", + "interface-store": "^5.1.8", "ipfs-unixfs": "^11.0.0", - "it-all": "^3.0.2", - "it-batch": "^3.0.2", - "it-first": "^3.0.2", - "it-parallel-batch": "^3.0.1", - "multiformats": "^13.0.0", + "it-all": "^3.0.4", + "it-batch": "^3.0.4", + "it-first": "^3.0.4", + "it-parallel-batch": "^3.0.4", + "multiformats": "^13.1.0", "progress-events": "^1.0.0", - "rabin-wasm": "^0.1.4", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0" + "rabin-wasm": "^0.1.5", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" } }, "node_modules/ipns": { @@ -10017,25 +11044,26 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "peer": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -10052,6 +11080,21 @@ "node": ">=8" } }, + "node_modules/is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "dependencies": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-loopback-addr": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", @@ -10114,6 +11157,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", @@ -10164,7 +11218,8 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "peer": true }, "node_modules/isexe": { "version": "2.0.0", @@ -10190,91 +11245,69 @@ } }, "node_modules/it-all": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.4.tgz", - "integrity": "sha512-UMiy0i9DqCHBdWvMbzdYvVGa5/w4t1cc4nchpbnjdLhklglv8mQeEYnii0gvKESJuL1zV32Cqdb33R6/GPfxpQ==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.6.tgz", + "integrity": "sha512-HXZWbxCgQZJfrv5rXvaVeaayXED8nTKx9tj9fpBhmcUJcedVZshMMMqTj0RG2+scGypb9Ut1zd1ifbf3lA8L+Q==" }, "node_modules/it-batch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.4.tgz", - "integrity": "sha512-WRu2mqOYIs+T9k7+yxSK9VJdk0UE4R0jKQsWQcti5c6vhb1FhjC2+yCB5XBrctQ9edNfCMU/wVzdDj8qSwimbA==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.6.tgz", + "integrity": "sha512-pQAAlSvJ4aV6xM/6LRvkPdKSKXxS4my2fGzNUxJyAQ8ccFdxPmK1bUuF5OoeUDkcdrbs8jtsmc4DypCMrGY6sg==" }, "node_modules/it-byte-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-1.0.8.tgz", - "integrity": "sha512-H32LbN6kdX8HXqH68z5uivfkVYJEi5tIPRwIQNR5Qsx3uoDRhYdBRHzf3NOVAf6vqulFUSQLuU+Y0rs/QeWn3A==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-1.0.10.tgz", + "integrity": "sha512-wjEADMuCS7PtnAGDjLGZ9n2+J+c6F/3a64ZfLVw2DCSKJWxwEQv+kQ2GUqArQVwkF+cKL6p7ka5cfbm+rwkwzg==", "dependencies": { "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0", - "race-signal": "^1.0.1", - "uint8arraylist": "^2.4.1" + "p-defer": "^4.0.1", + "race-signal": "^1.0.2", + "uint8arraylist": "^2.4.8" } }, "node_modules/it-drain": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.5.tgz", - "integrity": "sha512-qYFe4SWdvs9oJGUY5bSjvmiLUMLzFEODNOQUdYdCIkuIgQF+AUB2INhM4yQ09buJ2rhHKDFxvTD/+yUq6qg0XA==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.7.tgz", + "integrity": "sha512-vy6S1JKjjHSIFHgBpLpD1zhkCRl3z1zYWUxE14+kAYf+BL9ssWSFImJfhl361IIcwr0ofw8etzg11VqqB+ntUA==" }, "node_modules/it-filter": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.0.4.tgz", - "integrity": "sha512-e0sz+st4sudK/zH6GZ/gRTRP8A/ADuJFCYDmRgMbZvR79y5+v4ZXav850bBZk5wL9zXaYZFxS1v/6Qi+Vjwh5g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.0.tgz", + "integrity": "sha512-FiYuzdsUhmMZJTJQ8YLdgX3ArjQmAtCG1lyrtZd+92/2eC6YO9UoybdrwVj/yyZkuXAPykrSipLuZ+KSKpt29A==", "dependencies": { "it-peekable": "^3.0.0" } }, "node_modules/it-first": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.4.tgz", - "integrity": "sha512-FtQl84iTNxN5EItP/JgL28V2rzNMkCzTUlNoj41eVdfix2z1DBuLnBqZ0hzYhGGa1rMpbQf0M7CQSA2adlrLJg==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.6.tgz", + "integrity": "sha512-ExIewyK9kXKNAplg2GMeWfgjUcfC1FnUXz/RPfAvIXby+w7U4b3//5Lic0NV03gXT8O/isj5Nmp6KiY0d45pIQ==" }, "node_modules/it-foreach": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.0.6.tgz", - "integrity": "sha512-OVosBHJsdXpAyeFlCbe3IGZia+65UykyAznakNsKXK+b99dbhuu/mOnXxTadDEo1GWhKx+WA8RNanKkMf07zQw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.0.tgz", + "integrity": "sha512-nobWUecq9E2ED1kcXz2o27yN6KePauSdmxJNMwCduWByrF4WNB2UgBHjr9QV2jPXpEWPDuzxZas9fVhQj1Vovg==", "dependencies": { "it-peekable": "^3.0.0" } }, "node_modules/it-glob": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.6.tgz", - "integrity": "sha512-4C6ccz4nhqrq7yZMzBr3MsKhyL+rlnLXIPceyGG6ogl3Lx3eeWMv1RtlySJwFi6q+jVcPyTpeYt/xftwI2JEQQ==", - "dependencies": { - "minimatch": "^9.0.0" - } - }, - "node_modules/it-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/it-glob/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-3.0.1.tgz", + "integrity": "sha512-IUWE9f6XVUJLugK7pQmQPqTWj4BiQJhufnvxfsCmNIGEDQEkKVs3Ld9gFZq/Vude6g/OpndhsiuFrA730Bc59A==", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "fast-glob": "^3.3.2" } }, "node_modules/it-last": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.4.tgz", - "integrity": "sha512-Ns+KTsQWhs0KCvfv5X3Ck3lpoYxHcp4zUp4d+AOdmC8cXXqDuoZqAjfWhgCbxJubXyIYWdfE2nRcfWqgvZHP8Q==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.6.tgz", + "integrity": "sha512-M4/get95O85u2vWvWQinF8SJUc/RPC5bWTveBTYXvlP2q5TF9Y+QhT3nz+CRCyS2YEc66VJkyl/da6WrJ0wKhw==" }, "node_modules/it-length": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-length/-/it-length-3.0.4.tgz", - "integrity": "sha512-RS3thYkvqtWksrV7SaAnTv+pgY7ozpS17HlRvWvcnoRjVyNJMuffdCkIKpKNPTq5uZw9zVnkVKLO077pJn5Yhg==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-length/-/it-length-3.0.6.tgz", + "integrity": "sha512-R7bxHAzpRzYz7vghc2DDH7x4KXvEkeLfN/h316++jzbkEHIRXbEPLbE20p5yrqqBdOeK6/FRUDuHlTJ0H1hysw==" }, "node_modules/it-length-prefixed": { "version": "9.0.4", @@ -10294,36 +11327,36 @@ } }, "node_modules/it-length-prefixed-stream": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-1.1.6.tgz", - "integrity": "sha512-MEby4r8n3XIYXjaWT3DweCuhBPQmFVT8RdI1BNjYQ5gelbFD3NLdjYpTI3TVmSEs/aJfgpfVFZzy6iP7OCxIgw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-1.1.7.tgz", + "integrity": "sha512-tH38h/wChpR6As/PD6yWZlpoMuB4wDW2Rxf3QbSt4+O1HTsLYbyZasNhTyIuvQqhebQ30OYrdM0yr9ig5qUvYQ==", "dependencies": { "it-byte-stream": "^1.0.0", "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.1", - "uint8arraylist": "^2.4.1" + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8" } }, "node_modules/it-map": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.0.5.tgz", - "integrity": "sha512-hB0TDXo/h4KSJJDSRLgAPmDroiXP6Fx1ck4Bzl3US9hHfZweTKsuiP0y4gXuTMcJlS6vj0bb+f70rhkD47ZA3w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.0.tgz", + "integrity": "sha512-B7zNmHYRE0qes8oTiNYU7jXEF5WvKZNAUosskCks1JT9Z4DNwRClrQyd+C/hgITG8ewDbVZMGx9VXAx3KMY2kA==", "dependencies": { "it-peekable": "^3.0.0" } }, "node_modules/it-merge": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.3.tgz", - "integrity": "sha512-FYVU15KC5pb/GQX1Ims+lee8d4pdqGVCpWr0lkNj8o4xuNo7jY71k6GuEiWdP+T7W1bJqewSxX5yoTy5yZpRVA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.5.tgz", + "integrity": "sha512-2l7+mPf85pyRF5pqi0dKcA54E5Jm/2FyY5GsOaN51Ta0ipC7YZ3szuAsH8wOoB6eKY4XsU4k2X+mzPmFBMayEA==", "dependencies": { - "it-pushable": "^3.2.0" + "it-pushable": "^3.2.3" } }, "node_modules/it-ndjson": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.5.tgz", - "integrity": "sha512-2UEROCo458dDu9dABKb9fvD34p2YL6SqV5EOXN8SysX2Fpx0MSN69EiBmLLDDYSpQlrW0I5j3Tm8DtEIL5NsIw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.6.tgz", + "integrity": "sha512-44QB+rmfB2C8L2WDv03+J3kcOQYuIPBP3KBN+szZTEsvvNyoQEQSnOXoGouNpRfgMA1rEqqoK6roenCJaBeYSQ==" }, "node_modules/it-pair": { "version": "2.0.6", @@ -10339,25 +11372,25 @@ } }, "node_modules/it-parallel": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.6.tgz", - "integrity": "sha512-i7UM7I9LTkDJw3YIqXHFAPZX6CWYzGc+X3irdNrVExI4vPazrJdI7t5OqrSVN8CONXLAunCiqaSV/zZRbQR56A==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.7.tgz", + "integrity": "sha512-aIIc2t8knfER/mQu4uEHaAYZrnj/2Tdp+Vj6BA94Gi7xghx1kblvpyrLkCYO9K+eDyPS1cE3Vfhh9a20MEmzXA==", "dependencies": { - "p-defer": "^4.0.0" + "p-defer": "^4.0.1" } }, "node_modules/it-parallel-batch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.4.tgz", - "integrity": "sha512-O1omh8ss8+UtXiMjE+8kM5C20DT0Ma4VtKVfrSHOJU0UHZ+iWBXarabzPYEp+WiuQmrv+klDPPlTZ9KaLN9xOA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.5.tgz", + "integrity": "sha512-WxoFCMj61zV7rxaDj8Uox2aU+GI/JQzYQwcLi4QT1wN5cjAV+kA0GjaF9I95YG2JB6LVHb2qSbpfZEM7YVtnfQ==", "dependencies": { "it-batch": "^3.0.0" } }, "node_modules/it-peekable": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.3.tgz", - "integrity": "sha512-Wx21JX/rMzTEl9flx3DGHuPV1KQFGOl8uoKfQtmZHgPQtGb89eQ6RyVd82h3HuP9Ghpt0WgBDlmmdWeHXqyx7w==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.4.tgz", + "integrity": "sha512-Bb4xyMX5xAveFyh9ySbCrHMCpIF0+fIbl+0ZkcxP94JVofLe5j/mSBK0gjrrISsSVURVyey8X4L/IqrekOxjiA==" }, "node_modules/it-pipe": { "version": "3.0.1", @@ -10374,14 +11407,13 @@ } }, "node_modules/it-protobuf-stream": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-1.1.2.tgz", - "integrity": "sha512-epZBuG+7cPaTxCR/Lf3ApshBdA9qfflGPQLfLLrp9VQ0w67Z2xo4H+SLLetav57/29oPtAXwVaoyemg99JOWzA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-1.1.3.tgz", + "integrity": "sha512-96n+e6X8CXL0JerxTJuEnfivmfLzGKpIGAlJLoH7HEGo2nPRrMe+HxeWGwDF4Un3FphI/Z62JNxSvq/5DxfiQw==", "dependencies": { "it-length-prefixed-stream": "^1.0.0", "it-stream-types": "^2.0.1", - "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.1" + "uint8arraylist": "^2.4.8" } }, "node_modules/it-pushable": { @@ -10406,9 +11438,9 @@ } }, "node_modules/it-sort": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.4.tgz", - "integrity": "sha512-tvnC93JZZWjX4UxALy0asow0dzXabkoaRbrPJKClTKhNCqw4gzHr+H5axf1gohcthedRRkqd/ae+wl7WqoxFhw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.5.tgz", + "integrity": "sha512-vFo3wYR+aRDwklp8iH8LKeePmWqXGQrS8JqEdZmbJ58DIGj67n0RT/t5BR8iYps/C/v5IdWsbow1bOCEUfY+hA==", "dependencies": { "it-all": "^3.0.0" } @@ -10423,9 +11455,9 @@ } }, "node_modules/it-take": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.4.tgz", - "integrity": "sha512-RG8HDjAZlvkzz5Nav4xq6gK5zNT+Ff1UTIf+CrSJW8nIl6N1FpBH5e7clUshiCn+MmmMoSdIEpw4UaTolszxhA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.5.tgz", + "integrity": "sha512-4CzqXzx7FAeXsRYBTH0GhkxerH8Sv0nEGIXrO0ZIpECHth59Dm9ZYZ161VPrCQccWIL/Vu6M9YptlbMiEpCIlQ==" }, "node_modules/it-tar": { "version": "6.0.5", @@ -10446,16 +11478,19 @@ } }, "node_modules/it-to-browser-readablestream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/it-to-browser-readablestream/-/it-to-browser-readablestream-2.0.6.tgz", - "integrity": "sha512-VAOmkPv6S4x1m8WYR7QIBSh3qTgTd6syx146g5a4lPUjB7U7gZgqZFXRljk969UMvvROS3fJSzxnGxFmQaT2FA==" + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/it-to-browser-readablestream/-/it-to-browser-readablestream-2.0.9.tgz", + "integrity": "sha512-Q/0OXLOMgZ4Fk62Mrqp3x9QqN/UH9fotftr3dEfqr74Bt8ceClLOTmhkMyrk53R/gRcxdM6tBjzHGBeSjhmPeQ==", + "dependencies": { + "get-iterator": "^2.0.1" + } }, "node_modules/it-to-buffer": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/it-to-buffer/-/it-to-buffer-4.0.5.tgz", - "integrity": "sha512-DoQWOBhYmVHa0ooMauJLVbZ8V8K3AsFgqBs7I+kX7f3KbFMEy0MA9w7TJo9Utd4T4H+iUScyLFwo7REA4dWreA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/it-to-buffer/-/it-to-buffer-4.0.7.tgz", + "integrity": "sha512-c7JXrFg8xntJTPzhg7Dg6WJYm+XW0wBUebvEBrc6zrL/QukGRXclw1OBz6M9Qmqkiorgb3qpsRwKlI/4Q3tmkQ==", "dependencies": { - "uint8arrays": "^5.0.0" + "uint8arrays": "^5.0.3" } }, "node_modules/it-ws": { @@ -10538,16 +11573,29 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" } }, "node_modules/jest-message-util/node_modules/pretty-format": { @@ -10564,12 +11612,36 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "peer": true }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -10601,6 +11673,43 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -10618,16 +11727,29 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" } }, "node_modules/jest-validate/node_modules/pretty-format": { @@ -10644,12 +11766,36 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-validate/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "peer": true }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -10699,9 +11845,9 @@ } }, "node_modules/joi": { - "version": "17.12.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", - "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", + "version": "17.13.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", + "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", "peer": true, "dependencies": { "@hapi/hoek": "^9.3.0", @@ -10726,13 +11872,12 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "peer": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -10783,6 +11928,60 @@ "@babel/preset-env": "^7.1.6" } }, + "node_modules/jscodeshift/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jscodeshift/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jscodeshift/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "peer": true + }, + "node_modules/jscodeshift/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -10814,9 +12013,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { @@ -10919,30 +12118,34 @@ } }, "node_modules/libp2p": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.3.1.tgz", - "integrity": "sha512-mcVAQRECnn7TBXVDGihgAKRSL+v7otA9jgpeBnfPJvlbVbtFE9tKAUeBBgHVLifM2dXPjvl5fxzFMgYyP+fGbA==", - "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/logger": "^4.0.8", - "@libp2p/multistream-select": "^5.1.5", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/peer-store": "^10.0.12", - "@libp2p/utils": "^5.2.7", - "@multiformats/dns": "^1.0.1", - "@multiformats/multiaddr": "^12.2.0", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.5.1.tgz", + "integrity": "sha512-RzfOGjFRpSUDsv5c1ZA2TsWbPLaS86AVbIqhCrh273F9x3N/CkCXmRfWFmaRFmezWhP3SHbjsFWyQRJGF9U5mw==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/logger": "^4.0.12", + "@libp2p/multistream-select": "^5.1.9", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-id-factory": "^4.1.1", + "@libp2p/peer-store": "^10.0.17", + "@libp2p/utils": "^5.4.0", + "@multiformats/dns": "^1.0.5", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "any-signal": "^4.1.1", - "datastore-core": "^9.2.8", + "datastore-core": "^9.2.9", "interface-datastore": "^8.2.11", "it-merge": "^3.0.3", "it-parallel": "^3.0.6", "merge-options": "^3.0.4", "multiformats": "^13.1.0", - "uint8arrays": "^5.0.2" + "p-defer": "^4.0.1", + "race-event": "^1.3.0", + "race-signal": "^1.0.2", + "uint8arrays": "^5.0.3" } }, "node_modules/lighthouse-logger": { @@ -10983,232 +12186,65 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/lint-staged": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", - "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", - "dev": true, - "dependencies": { - "chalk": "5.3.0", - "commander": "11.1.0", - "debug": "4.3.4", - "execa": "8.0.1", - "lilconfig": "3.0.0", - "listr2": "8.0.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.4" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/lint-staged/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/lint-staged/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lint-staged/node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/listr2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", - "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", - "dev": true, - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.0.0", - "rfdc": "^1.3.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true - }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", - "dev": true, - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", + "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=12" + "node": ">=18.12.0" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://opencollective.com/lint-staged" } }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "node_modules/listr2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", + "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", "dev": true, "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.0.0" } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -11299,6 +12335,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/log-update": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", @@ -11369,12 +12442,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true - }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", @@ -11430,6 +12497,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/log-update/node_modules/slice-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", @@ -11446,23 +12519,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", - "dev": true, - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -11478,23 +12534,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/logkitty": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", @@ -11529,18 +12568,67 @@ "wrap-ansi": "^6.2.0" } }, - "node_modules/logkitty/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/logkitty/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" }, "engines": { "node": ">=8" } }, + "node_modules/logkitty/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/logkitty/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -11609,14 +12697,12 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "peer": true, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/luxon": { @@ -11664,77 +12750,42 @@ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "peer": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "peer": true - }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "peer": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "peer": true + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/merge-descriptors": { @@ -11762,7 +12813,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -11776,9 +12826,9 @@ } }, "node_modules/metro": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.8.tgz", - "integrity": "sha512-in7S0W11mg+RNmcXw+2d9S3zBGmCARDxIwoXJAmLUQOQoYsRP3cpGzyJtc7WOw8+FXfpgXvceD0u+PZIHXEL7g==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.9.tgz", + "integrity": "sha512-Bc57Xf3GO2Xe4UWQsBj/oW6YfLPABEu8jfDVDiNmJvoQW4CO34oDPuYKe4KlXzXhcuNsqOtSxpbjCRRVjhhREg==", "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", @@ -11802,18 +12852,18 @@ "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.8", - "metro-cache": "0.80.8", - "metro-cache-key": "0.80.8", - "metro-config": "0.80.8", - "metro-core": "0.80.8", - "metro-file-map": "0.80.8", - "metro-resolver": "0.80.8", - "metro-runtime": "0.80.8", - "metro-source-map": "0.80.8", - "metro-symbolicate": "0.80.8", - "metro-transform-plugins": "0.80.8", - "metro-transform-worker": "0.80.8", + "metro-babel-transformer": "0.80.9", + "metro-cache": "0.80.9", + "metro-cache-key": "0.80.9", + "metro-config": "0.80.9", + "metro-core": "0.80.9", + "metro-file-map": "0.80.9", + "metro-resolver": "0.80.9", + "metro-runtime": "0.80.9", + "metro-source-map": "0.80.9", + "metro-symbolicate": "0.80.9", + "metro-transform-plugins": "0.80.9", + "metro-transform-worker": "0.80.9", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", @@ -11833,9 +12883,9 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.8.tgz", - "integrity": "sha512-TTzNwRZb2xxyv4J/+yqgtDAP2qVqH3sahsnFu6Xv4SkLqzrivtlnyUbaeTdJ9JjtADJUEjCbgbFgUVafrXdR9Q==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.9.tgz", + "integrity": "sha512-d76BSm64KZam1nifRZlNJmtwIgAeZhZG3fi3K+EmPOlrR8rDtBxQHDSN3fSGeNB9CirdTyabTMQCkCup6BXFSQ==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", @@ -11862,12 +12912,12 @@ } }, "node_modules/metro-cache": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.8.tgz", - "integrity": "sha512-5svz+89wSyLo7BxdiPDlwDTgcB9kwhNMfNhiBZPNQQs1vLFXxOkILwQiV5F2EwYT9DEr6OPZ0hnJkZfRQ8lDYQ==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.9.tgz", + "integrity": "sha512-ujEdSI43QwI+Dj2xuNax8LMo8UgKuXJEdxJkzGPU6iIx42nYa1byQ+aADv/iPh5sh5a//h5FopraW5voXSgm2w==", "peer": true, "dependencies": { - "metro-core": "0.80.8", + "metro-core": "0.80.9", "rimraf": "^3.0.2" }, "engines": { @@ -11875,9 +12925,9 @@ } }, "node_modules/metro-cache-key": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.8.tgz", - "integrity": "sha512-qWKzxrLsRQK5m3oH8ePecqCc+7PEhR03cJE6Z6AxAj0idi99dHOSitTmY0dclXVB9vP2tQIAE8uTd8xkYGk8fA==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.9.tgz", + "integrity": "sha512-hRcYGhEiWIdM87hU0fBlcGr+tHDEAT+7LYNCW89p5JhErFt/QaAkVx4fb5bW3YtXGv5BTV7AspWPERoIb99CXg==", "peer": true, "engines": { "node": ">=18" @@ -11899,40 +12949,112 @@ } }, "node_modules/metro-config": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.8.tgz", - "integrity": "sha512-VGQJpfJawtwRzGzGXVUoohpIkB0iPom4DmSbAppKfumdhtLA8uVeEPp2GM61kL9hRvdbMhdWA7T+hZFDlo4mJA==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.9.tgz", + "integrity": "sha512-28wW7CqS3eJrunRGnsibWldqgwRP9ywBEf7kg+uzUHkSFJNKPM1K3UNSngHmH0EZjomizqQA2Zi6/y6VdZMolg==", "peer": true, "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "jest-validate": "^29.6.3", - "metro": "0.80.8", - "metro-cache": "0.80.8", - "metro-core": "0.80.8", - "metro-runtime": "0.80.8" + "metro": "0.80.9", + "metro-cache": "0.80.9", + "metro-core": "0.80.9", + "metro-runtime": "0.80.9" }, "engines": { "node": ">=18" } }, + "node_modules/metro-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/metro-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/metro-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/metro-core": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.8.tgz", - "integrity": "sha512-g6lud55TXeISRTleW6SHuPFZHtYrpwNqbyFIVd9j9Ofrb5IReiHp9Zl8xkAfZQp8v6ZVgyXD7c130QTsCz+vBw==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.9.tgz", + "integrity": "sha512-tbltWQn+XTdULkGdzHIxlxk4SdnKxttvQQV3wpqqFbHDteR4gwCyTR2RyYJvxgU7HELfHtrVbqgqAdlPByUSbg==", "peer": true, "dependencies": { "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.8" + "metro-resolver": "0.80.9" }, "engines": { "node": ">=18" } }, "node_modules/metro-file-map": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.8.tgz", - "integrity": "sha512-eQXMFM9ogTfDs2POq7DT2dnG7rayZcoEgRbHPXvhUWkVwiKkro2ngcBE++ck/7A36Cj5Ljo79SOkYwHaWUDYDw==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.9.tgz", + "integrity": "sha512-sBUjVtQMHagItJH/wGU9sn3k2u0nrCl0CdR4SFMO1tksXLKbkigyQx4cbpcyPVOAmGTVuy3jyvBlELaGCAhplQ==", "peer": true, "dependencies": { "anymatch": "^3.0.3", @@ -11969,9 +13091,9 @@ "peer": true }, "node_modules/metro-minify-terser": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.8.tgz", - "integrity": "sha512-y8sUFjVvdeUIINDuW1sejnIjkZfEF+7SmQo0EIpYbWmwh+kq/WMj74yVaBWuqNjirmUp1YNfi3alT67wlbBWBQ==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.9.tgz", + "integrity": "sha512-FEeCeFbkvvPuhjixZ1FYrXtO0araTpV6UbcnGgDUpH7s7eR5FG/PiJz3TsuuPP/HwCK19cZtQydcA2QrCw446A==", "peer": true, "dependencies": { "terser": "^5.15.0" @@ -11981,18 +13103,18 @@ } }, "node_modules/metro-resolver": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.8.tgz", - "integrity": "sha512-JdtoJkP27GGoZ2HJlEsxs+zO7jnDUCRrmwXJozTlIuzLHMRrxgIRRby9fTCbMhaxq+iA9c+wzm3iFb4NhPmLbQ==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.9.tgz", + "integrity": "sha512-wAPIjkN59BQN6gocVsAvvpZ1+LQkkqUaswlT++cJafE/e54GoVkMNCmrR4BsgQHr9DknZ5Um/nKueeN7kaEz9w==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/metro-runtime": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.8.tgz", - "integrity": "sha512-2oScjfv6Yb79PelU1+p8SVrCMW9ZjgEiipxq7jMRn8mbbtWzyv3g8Mkwr+KwOoDFI/61hYPUbY8cUnu278+x1g==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.9.tgz", + "integrity": "sha512-8PTVIgrVcyU+X/rVCy/9yxNlvXsBCk5JwwkbAm/Dm+Abo6NBGtNjWF0M1Xo/NWCb4phamNWcD7cHdR91HhbJvg==", "peer": true, "dependencies": { "@babel/runtime": "^7.0.0" @@ -12002,17 +13124,17 @@ } }, "node_modules/metro-source-map": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.8.tgz", - "integrity": "sha512-+OVISBkPNxjD4eEKhblRpBf463nTMk3KMEeYS8Z4xM/z3qujGJGSsWUGRtH27+c6zElaSGtZFiDMshEb8mMKQg==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.9.tgz", + "integrity": "sha512-RMn+XS4VTJIwMPOUSj61xlxgBvPeY4G6s5uIn6kt6HB6A/k9ekhr65UkkDD7WzHYs3a9o869qU8tvOZvqeQzgw==", "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.8", + "metro-symbolicate": "0.80.9", "nullthrows": "^1.1.1", - "ob1": "0.80.8", + "ob1": "0.80.9", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -12030,13 +13152,13 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.8.tgz", - "integrity": "sha512-nwhYySk79jQhwjL9QmOUo4wS+/0Au9joEryDWw7uj4kz2yvw1uBjwmlql3BprQCBzRdB3fcqOP8kO8Es+vE31g==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.9.tgz", + "integrity": "sha512-Ykae12rdqSs98hg41RKEToojuIW85wNdmSe/eHUgMkzbvCFNVgcC0w3dKZEhSsqQOXapXRlLtHkaHLil0UD/EA==", "peer": true, "dependencies": { "invariant": "^2.2.4", - "metro-source-map": "0.80.8", + "metro-source-map": "0.80.9", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -12059,9 +13181,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.8.tgz", - "integrity": "sha512-sSu8VPL9Od7w98MftCOkQ1UDeySWbsIAS5I54rW22BVpPnI3fQ42srvqMLaJUQPjLehUanq8St6OMBCBgH/UWw==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.9.tgz", + "integrity": "sha512-UlDk/uc8UdfLNJhPbF3tvwajyuuygBcyp+yBuS/q0z3QSuN/EbLllY3rK8OTD9n4h00qZ/qgxGv/lMFJkwP4vg==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", @@ -12075,28 +13197,44 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.8.tgz", - "integrity": "sha512-+4FG3TQk3BTbNqGkFb2uCaxYTfsbuFOCKMMURbwu0ehCP8ZJuTUramkaNZoATS49NSAkRgUltgmBa4YaKZ5mqw==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.9.tgz", + "integrity": "sha512-c/IrzMUVnI0hSVVit4TXzt3A1GiUltGVlzCmLJWxNrBGHGrJhvgePj38+GXl1Xf4Fd4vx6qLUkKMQ3ux73bFLQ==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "metro": "0.80.8", - "metro-babel-transformer": "0.80.8", - "metro-cache": "0.80.8", - "metro-cache-key": "0.80.8", - "metro-minify-terser": "0.80.8", - "metro-source-map": "0.80.8", - "metro-transform-plugins": "0.80.8", + "metro": "0.80.9", + "metro-babel-transformer": "0.80.9", + "metro-cache": "0.80.9", + "metro-cache-key": "0.80.9", + "metro-minify-terser": "0.80.9", + "metro-source-map": "0.80.9", + "metro-transform-plugins": "0.80.9", "nullthrows": "^1.1.1" }, "engines": { "node": ">=18" } }, + "node_modules/metro/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/metro/node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -12112,6 +13250,15 @@ "ms": "2.0.0" } }, + "node_modules/metro/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/metro/node_modules/hermes-estree": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", @@ -12157,13 +13304,13 @@ "node": ">=0.10.0" } }, - "node_modules/metro/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/metro/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" @@ -12319,6 +13466,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -12347,20 +13495,21 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "version": "2.0.0-rc.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.0-rc.4.tgz", + "integrity": "sha512-YkjV/rOzngEBxtZV8s6BcBcB/z2v/dfDcIpWagmMsKBq0QnQCu/NpCUDd2ElExfnxRXMVXKrFh94/UKe4NdxBQ==", + "dependencies": { + "@fastify/busboy": "^1.0.0", + "append-field": "^2.0.0", + "bytes": "^3.1.0", + "fs-temp": "^2.0.1", + "has-own-property": "^2.0.0", + "on-finished": "^2.3.0", + "stream-file-type": "^0.6.1", + "type-is": "^1.6.18" }, "engines": { - "node": ">= 6.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/multibase": { @@ -12425,6 +13574,18 @@ "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" }, + "node_modules/murmur-32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmur-32/-/murmur-32-1.0.0.tgz", + "integrity": "sha512-l6QKUGWXzZWHash7lmZzycpIifOrnc3PeMaoFrMv90c+xqOyOSzJU0q2T/1d15MzAdEWTpCh1paC855APqt1Gw==", + "dependencies": { + "encode-utf8": "^2.0.0", + "fmix": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/murmurhash3js-revisited": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", @@ -12497,9 +13658,9 @@ } }, "node_modules/node-abi": { - "version": "3.56.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz", - "integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==", + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", "dependencies": { "semver": "^7.3.5" }, @@ -12514,13 +13675,14 @@ "peer": true }, "node_modules/node-datachannel": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.5.5.tgz", - "integrity": "sha512-B0MS/iK0qjCiNY5Go4055zuRCOuiFkOrhNOZuj2PAnaJN7YlnKxKidOLhLXf/lAcsJQXgj8BR31zsqJKKA16jA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.8.0.tgz", + "integrity": "sha512-05vYthhjESfO5qumWRbqHI1PN71rc19DJjc14ofOLapJrTCnmhx0JYw0G5wcL56b57uHQP9WjEak3+XklrB+Og==", "hasInstallScript": true, "dependencies": { "node-domexception": "^2.0.1", - "prebuild-install": "^7.0.1" + "prebuild-install": "^7.0.1", + "rollup": "^4.14.1" }, "engines": { "node": ">=16.0.0" @@ -12575,6 +13737,15 @@ } } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "peer": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12698,9 +13869,9 @@ "peer": true }, "node_modules/ob1": { - "version": "0.80.8", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.8.tgz", - "integrity": "sha512-QHJQk/lXMmAW8I7AIM3in1MSlwe1umR72Chhi8B7Xnq6mzjhBKkA6Fy/zAhQnGkA4S912EPCEvTij5yh+EQTAA==", + "version": "0.80.9", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.9.tgz", + "integrity": "sha512-v9yOxowkZbxWhKOaaTyLjIm1aLy4ebMNcSn4NYJKOAI/Qv+SkfEfszpLr2GIxsccmb2Y2HA9qtsqiIJ80ucpVA==", "peer": true, "engines": { "node": ">=18" @@ -12710,6 +13881,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12740,10 +13912,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "peer": true, + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -12804,17 +13975,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -12878,13 +14049,52 @@ "ieee754": "^1.1.13" } }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" @@ -12925,28 +14135,33 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue": { @@ -13011,26 +14226,22 @@ "node": ">=6" } }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "peer": true, + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { + "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parseurl": { @@ -13042,11 +14253,12 @@ } }, "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/path-is-absolute": { @@ -13087,9 +14299,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -13159,50 +14371,35 @@ } }, "node_modules/pino": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz", - "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", + "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.1.0", + "pino-abstract-transport": "^1.2.0", "pino-std-serializers": "^6.0.0", "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^3.7.0", - "thread-stream": "^2.0.0" + "thread-stream": "^2.6.0" }, "bin": { "pino": "bin.js" } }, "node_modules/pino-abstract-transport": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", - "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, - "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/pino-http": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-9.0.0.tgz", @@ -13239,40 +14436,6 @@ "pino-pretty": "bin.js" } }, - "node_modules/pino-pretty/node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/pino-pretty/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pino-std-serializers": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", @@ -13324,6 +14487,21 @@ "node": ">=6" } }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -13446,6 +14624,43 @@ "@types/yargs-parser": "*" } }, + "node_modules/pretty-format/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -13457,7 +14672,8 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "peer": true }, "node_modules/process-warning": { "version": "3.0.0", @@ -13495,23 +14711,6 @@ "node": ">= 6" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "peer": true - }, "node_modules/protons-runtime": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.4.0.tgz", @@ -13588,6 +14787,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "peer": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -13601,7 +14810,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -13647,16 +14855,41 @@ "rabin-wasm": "cli/bin.js" } }, + "node_modules/rabin-wasm/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/race-event": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.2.0.tgz", - "integrity": "sha512-7EvAjTu9uuKa03Jky8yfSy6/SnnMTh6nouNmdeWv9b0dT8eDZC5ylx30cOR9YO9otQorVjjkIuSHQ5Ml/bKwMw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.3.0.tgz", + "integrity": "sha512-kaLm7axfOnahIqD3jQ4l1e471FIFcEGebXEnhxyLscuUzV8C94xVHtWEqDDXxll7+yu/6lW0w1Ff4HbtvHvOHg==" }, "node_modules/race-signal": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.0.2.tgz", "integrity": "sha512-o3xNv0iTcIDQCXFlF6fPAMEBRjFxssgGoRqLbg06m+AdzEXXLUmoNOoUHTVz2NoBI8hHwKFKoC6IqyNtWr2bww==" }, + "node_modules/random-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-path/-/random-path-1.0.0.tgz", + "integrity": "sha512-I6FGG7uFR3tZqHP7TzcP3Ikt5IyVEG59u7KTjIIjizcdPY6MDjD9CbbKqE+znIv4mrDF6HMlBshoemk0oRRwsQ==", + "dependencies": { + "base32-encode": "^2.0.0", + "murmur-32": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -13679,14 +14912,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -13701,6 +14926,19 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -13714,9 +14952,9 @@ } }, "node_modules/react-devtools-core": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", - "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.1.0.tgz", + "integrity": "sha512-NRtLBqYVLrIY+lOa2oTpFiAhI7Hru0AUXI0tP9neCyaPPAzlZyeH0i+VZ0shIyRTJbpvyqbD/uCsewA2hpfZHw==", "peer": true, "dependencies": { "shell-quote": "^1.6.1", @@ -13751,28 +14989,27 @@ "peer": true }, "node_modules/react-native": { - "version": "0.73.6", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.6.tgz", - "integrity": "sha512-oqmZe8D2/VolIzSPZw+oUd6j/bEmeRHwsLn1xLA5wllEYsZ5zNuMsDus235ONOnCRwexqof/J3aztyQswSmiaA==", + "version": "0.74.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.1.tgz", + "integrity": "sha512-0H2XpmghwOtfPpM2LKqHIN7gxy+7G/r1hwJHKLV6uoyXGC/gCojRtoo5NqyKrWpFC8cqyT6wTYCLuG7CxEKilg==", "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "12.3.6", - "@react-native-community/cli-platform-android": "12.3.6", - "@react-native-community/cli-platform-ios": "12.3.6", - "@react-native/assets-registry": "0.73.1", - "@react-native/codegen": "0.73.3", - "@react-native/community-cli-plugin": "0.73.17", - "@react-native/gradle-plugin": "0.73.4", - "@react-native/js-polyfills": "0.73.1", - "@react-native/normalize-colors": "0.73.2", - "@react-native/virtualized-lists": "0.73.4", + "@react-native-community/cli": "13.6.6", + "@react-native-community/cli-platform-android": "13.6.6", + "@react-native-community/cli-platform-ios": "13.6.6", + "@react-native/assets-registry": "0.74.83", + "@react-native/codegen": "0.74.83", + "@react-native/community-cli-plugin": "0.74.83", + "@react-native/gradle-plugin": "0.74.83", + "@react-native/js-polyfills": "0.74.83", + "@react-native/normalize-colors": "0.74.83", + "@react-native/virtualized-lists": "0.74.83", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "base64-js": "^1.5.1", "chalk": "^4.0.0", - "deprecated-react-native-prop-types": "^5.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", @@ -13785,7 +15022,7 @@ "nullthrows": "^1.1.1", "pretty-format": "^26.5.2", "promise": "^8.3.0", - "react-devtools-core": "^4.27.7", + "react-devtools-core": "^5.0.0", "react-refresh": "^0.14.0", "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", @@ -13802,13 +15039,19 @@ "node": ">=18" }, "peerDependencies": { + "@types/react": "^18.2.6", "react": "18.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/react-native-webrtc": { - "version": "118.0.3", - "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.3.tgz", - "integrity": "sha512-qw+aa4rxGJTvltmYwwHonx4Qcgk/tcoojONu/6y5nsXGctkUqo886EIBb29Jv4ssHnudDzvkxyG/xVKK2vJc7Q==", + "version": "118.0.7", + "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.7.tgz", + "integrity": "sha512-odgd4CNSGQmI8n/pEbxlUtJBTJ8uqE51B1/NUEAvO1AQbeXsyFNHEG0H2T27eMefo5u0GKcRpNkZpXi6fctTkQ==", "dependencies": { "base64-js": "1.5.1", "debug": "4.3.4", @@ -13829,6 +15072,43 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/react-native/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-native/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/react-native/node_modules/ws": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", @@ -13839,9 +15119,9 @@ } }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "peer": true, "engines": { "node": ">=0.10.0" @@ -13880,16 +15160,77 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/type-fest": { @@ -13919,24 +15260,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -13956,16 +15279,18 @@ } }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/readable-web-to-node-stream": { @@ -13983,6 +15308,19 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -14169,12 +15507,12 @@ } }, "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "peer": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/restore-cursor": { @@ -14211,6 +15549,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -14223,7 +15566,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -14299,6 +15641,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", + "fsevents": "~2.3.2" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -14312,7 +15688,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -14350,9 +15725,23 @@ "dev": true }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-stable-stringify": { "version": "2.4.3", @@ -14395,6 +15784,19 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", "dev": true }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "peer": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -14409,6 +15811,22 @@ "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -14461,25 +15879,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", @@ -14588,9 +15987,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/simple-concat": { "version": "1.0.1", @@ -14662,46 +16068,33 @@ } }, "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "peer": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "peer": true, - "dependencies": { - "color-name": "1.1.3" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "peer": true - }, "node_modules/sonic-boom": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", @@ -14828,28 +16221,102 @@ "node": ">=6" } }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-file-type": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/stream-file-type/-/stream-file-type-0.6.1.tgz", + "integrity": "sha512-//KIXMQan4ow4gD//dfPu15nhH/sFdt41PzAOpD9EBFUBy/MtFjocTPO8v1dTOJnyi47TlPo6Qj+67sSE1lJKA==", + "dependencies": { + "file-type": "^16.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/stream-file-type/node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/stream-file-type/node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stream-file-type/node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stream-file-type/node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, "node_modules/stream-to-it": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-0.2.4.tgz", - "integrity": "sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-1.0.1.tgz", + "integrity": "sha512-AqHYAYPHcmvMrcLNgncE/q0Aj/ajP6A4qGhxP6EVn7K3YTNs0bJpJyk57wc2Heb7MUL64jurvmnmui8D9kjZgA==", "dependencies": { - "get-iterator": "^1.0.2" + "it-stream-types": "^2.0.1" } }, - "node_modules/stream-to-it/node_modules/get-iterator": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-1.0.2.tgz", - "integrity": "sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==" - }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -14866,25 +16333,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -14931,18 +16379,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -14951,7 +16387,7 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/strip-ansi": { + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -14962,18 +16398,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -14987,15 +16411,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -15020,11 +16435,15 @@ } }, "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strnum": { @@ -15055,6 +16474,22 @@ "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", "peer": true }, + "node_modules/super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "dependencies": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -15152,6 +16587,19 @@ "ieee754": "^1.1.13" } }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/temp": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", @@ -15186,9 +16634,9 @@ } }, "node_modules/terser": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.0.tgz", - "integrity": "sha512-Y/SblUl5kEyEFzhMAQdsxVHh+utAxd4IuRNJzKywY/4uzSogh3G219jqbDDxYu4MXO9CzY3tSEqmZvW6AoEDJw==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -15209,6 +16657,11 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "peer": true }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -15228,9 +16681,9 @@ "dev": true }, "node_modules/thread-stream": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", - "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", "dependencies": { "real-require": "^0.2.0" } @@ -15272,6 +16725,12 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "peer": true + }, "node_modules/through2/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -15286,6 +16745,20 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/timestamp-nano": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", @@ -15312,6 +16785,14 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "peer": true }, + "node_modules/to-data-view": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-2.0.0.tgz", + "integrity": "sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -15449,12 +16930,15 @@ } }, "node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "peer": true, + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { @@ -15469,15 +16953,10 @@ "node": ">= 0.6" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -15593,9 +17072,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.14.tgz", + "integrity": "sha512-JixKH8GR2pWYshIPUg/NujK3JO7JiqEEUiNArE86NQyrgUuZeTlZQN3xuS/yiV5Kb48ev9K6RqNkaJjXsdg7Jw==", "funding": [ { "type": "opencollective", @@ -15612,7 +17091,7 @@ ], "peer": true, "dependencies": { - "escalade": "^3.1.1", + "escalade": "^3.1.2", "picocolors": "^1.0.0" }, "bin": { @@ -15748,17 +17227,27 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "peer": true }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -15782,27 +17271,66 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrappy": { @@ -15811,20 +17339,28 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "peer": true, + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "dependencies": { - "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "engines": { "node": ">=10.0.0" }, @@ -15865,6 +17401,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "peer": true, "engines": { "node": ">=0.4" } @@ -15878,18 +17415,15 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "peer": true }, "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "engines": { "node": ">= 14" } @@ -15920,11 +17454,12 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 411b38f..4f492ca 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", "@types/express": "^4.17.21", - "@types/multer": "^1.4.11", "@types/node": "^20.11.30", "cross-env": "^7.0.3", "eslint": "^8.57.0", @@ -65,7 +64,7 @@ "it-last": "^3.0.4", "json5": "^2.2.3", "libp2p": "^1.3.1", - "multer": "^1.4.5-lts.1", + "multer": "^2.0.0-rc.4", "multiformats": "^13.1.0", "pino": "^8.20.0", "pino-http": "^9.0.0" diff --git a/src/index.ts b/src/index.ts index 3cd4972..8f0ca50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,8 @@ import { unixfs } from '@helia/unixfs' import { peerIdFromString } from '@libp2p/peer-id' -import { fileTypeFromBuffer } from 'file-type' import { CID } from 'multiformats/cid' import express from 'express' -import multer from 'multer' +import multer, { Multer } from 'multer' import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import { autoPeeringHandler, autoPeering } from './cron.js' import { helia } from './helia.js' @@ -16,13 +15,13 @@ import { pino } from './utils/logger.js' import type { PingService } from '@libp2p/ping' import { KadDHT } from '@libp2p/kad-dht' import { PeerId } from '@libp2p/interface' -import { filestore } from './store.js' import config, { configFileName } from './config.js' +import { Writable } from 'node:stream' pino.logger.info(`Using config file: ${configFileName}`) const verifiedFetch = await createVerifiedFetch(helia) -const upload = multer({ storage: filestore }) +const upload = multer({ limits: { fileSize: config.uploadLimitSizeBytes } }) let logNewPeers = false @@ -266,15 +265,13 @@ app.get('/file/:cid', async (req, res) => { }) const result = await Promise.race([filePromise, timeoutPromise]) - const data = Buffer.from(await result.arrayBuffer()) - - // If filePromise wins the race, send the file - const fileType = await fileTypeFromBuffer(data) - if (fileType) { - res.set('Content-Type', fileType.mime) + const data = result.body + if (!data) { + throw new Error('Empty data') } - - res.send(data) + res.set('Content-Type', 'application/octet-stream') + const responseStream = Writable.toWeb(res) + await data.pipeTo(responseStream) } catch (error) { pino.logger.error(error) if (error.message === 'Operation timed out') { @@ -334,54 +331,61 @@ app.get('/pins/isPinned/:cid', async (req, res) => { }) }) -app.post('/file/upload', upload.array('files', 5), async (req, res) => { - if (!req.files) { - res.statusCode = 400 - return res.send({ - error: 'No file uploaded' - }) - } - const files = flatFiles(req.files) - pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) - - const cids: CID[] = [] - for (const file of files) { - console.log(`Adding ${file.originalname} to IPFS`) +app.post( + '/file/upload', + upload.array('files', 5), + async ( + req: Request & { files?: { [fieldname: string]: Multer.File[] } | Multer.File[] }, + res + ) => { + if (!req.files) { + res.statusCode = 400 + return res.send({ + error: 'No file uploaded' + }) + } + const files = flatFiles(req.files) + pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalName))}`) - const { buffer, originalname } = file + const cids: CID[] = [] + for (const file of files) { + console.log(`Adding ${file.originalName} to IPFS`) - const cid = await ifs.addFile({ - path: `/${originalname}`, - content: buffer - }) - pino.logger.info(`Successfully added file ${cid}`) - cids.push(cid) + const { stream, originalName } = file - const isPinned = await helia.pins.isPinned(cid) - if (isPinned) { - pino.logger.info(`File already pinned ${cid}`) - } else { - // Pin the file - for await (const pinned of helia.pins.add(cid)) { - pino.logger.info(`Filed pinned: ${pinned}`) + const cid = await ifs.addFile({ + path: `/${originalName}`, + content: stream + }) + pino.logger.info(`Successfully added file ${cid}`) + cids.push(cid) + + const isPinned = await helia.pins.isPinned(cid) + if (isPinned) { + pino.logger.info(`File already pinned ${cid}`) + } else { + // Pin the file + for await (const pinned of helia.pins.add(cid)) { + pino.logger.info(`Filed pinned: ${pinned}`) + } } - } - // Tell the network we can provide content for the passed CID - const dht = helia.libp2p.services.dht as KadDHT - await dht.provide(cid) - pino.logger.info(`Provided CID via DHT ${cid}`) + // Tell the network we can provide content for the passed CID + const dht = helia.libp2p.services.dht as KadDHT + await dht.provide(cid) + pino.logger.info(`Provided CID via DHT ${cid}`) - pino.logger.info(`Routing: Providing ${cid}`) - void helia.routing.provide(cid) - pino.logger.info('Routing: Provide DONE') - } + pino.logger.info(`Routing: Providing ${cid}`) + void helia.routing.provide(cid) + pino.logger.info('Routing: Provide DONE') + } - res.send({ - filesNames: files.map((file) => file.originalname), - cids: cids.map((cid) => cid.toString()) - }) -}) + res.send({ + filesNames: files.map((file) => file.originalName), + cids: cids.map((cid) => cid.toString()) + }) + } +) app.post('/test', async (req, res) => { const textEncoder = new TextEncoder() diff --git a/src/multer.d.ts b/src/multer.d.ts new file mode 100644 index 0000000..29f582b --- /dev/null +++ b/src/multer.d.ts @@ -0,0 +1,20 @@ +declare module 'multer' { + import { ReadStream } from 'node:fs' + + class Multer { + array(fieldName: string, maxFileCount: number) + } + namespace Multer { + export interface File { + fieldName: string + originalName: string + size: number + stream: ReadStream + detectedMimeType: string + detectedFileExtension: string + clientReportedMimeType: string + clientReportedFileExtension: string + } + } + export default function multer(options: {}): Multer +} diff --git a/src/store.ts b/src/store.ts index 5aad84f..025325d 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,26 +1,10 @@ import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' import { homedir } from 'os' -import { existsSync, mkdirSync } from 'fs' import { join } from 'path' import config from './config.js' -import multer from 'multer' const userHomeDir = homedir() export const blockstore = new FsBlockstore(join(userHomeDir, config.storeFolder, 'blockstore')) export const datastore = new FsDatastore(join(userHomeDir, config.storeFolder, 'datastore')) - -const filestorePath = join(userHomeDir, config.storeFolder, 'filestore') -if (!existsSync(filestorePath)) { - mkdirSync(filestorePath, { recursive: true }) -} -export const filestore = multer.diskStorage({ - destination: function (req, file, cb) { - cb(null, filestorePath) - }, - filename: function (req, file, cb) { - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9) - cb(null, file.fieldname + '-' + uniqueSuffix) - } -}) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 90d878a..4c11d35 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,6 +2,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import config from '../config.js' import { ConfigNode, NodeWithPeerId } from './types.js' +import { Multer } from 'multer' /** * Get peerId from multiaddr string @@ -49,10 +50,8 @@ export function getAllowNodesMultiaddrs() { return parseNodes().map((node) => node.multiAddr.toString()) } -export function flatFiles( - files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] -) { - let resultFiles: Express.Multer.File[] = [] +export function flatFiles(files: { [fieldname: string]: Multer.File[] } | Multer.File[]) { + let resultFiles: Multer.File[] = [] if (Array.isArray(files)) { return files } else { From 63c6d9cb21e149af41b574d8ae846ebeb56cf5cb Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 4 May 2024 16:07:33 +0400 Subject: [PATCH 027/167] feat: move findFileTimeout to config --- config.default.json5 | 3 ++- src/index.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index bb641ce..d769cdb 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -32,5 +32,6 @@ }, serverPort: 4000, autoPeeringPeriod: '*/10 * * * * *', - uploadLimitSizeBytes: 268435456 + uploadLimitSizeBytes: 268435456, + findFileTimeout: 30000 } diff --git a/src/index.ts b/src/index.ts index 8f0ca50..331fc1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -256,7 +256,7 @@ app.get('/file/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Operation timed out')), 30000) + setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) ) try { From 2a727c43714e3ae473f52fc0b677182a9240d136 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Mon, 6 May 2024 15:52:06 +0400 Subject: [PATCH 028/167] fix: corsOptions + remove helia Version --- src/index.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index de49e5e..e8f5085 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,11 +75,12 @@ const app = express() app.use(pino) -const corsOptions = { - origin: config.cors.originRegexps.map((item: string) => new RegExp(`^https?:\\/\\/${item}`)), - methods: ['GET', 'POST'] -} -app.use(cors(corsOptions)) +app.use( + cors({ + origin: config.cors.originRegexps.map((item: string) => new RegExp(`^https?:\\/\\/${item}`)), + methods: ['GET', 'POST'] + }) +) app.get('/', (req, res) => { res.send('IPFS node') @@ -417,11 +418,9 @@ app.get('/node/health', async (req, res) => { app.get('/node/info', async (req, res) => { const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() - const heliaVersion = (packageJson.dependencies['helia'] || 'unknown').replaceAll('^', '') res.send({ version: packageJson.version, timestamp: Date.now(), - heliaVersion, heliaStatus: helia.libp2p.status, peerId: helia.libp2p.peerId, multiAddresses: helia.libp2p.getMultiaddrs(), From e57f235998a81c3541659c97b2cd3cc60c4f4282 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Tue, 7 May 2024 21:22:09 +0400 Subject: [PATCH 029/167] fix: logger --- config.default.json5 | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index d769cdb..a45261d 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -32,6 +32,6 @@ }, serverPort: 4000, autoPeeringPeriod: '*/10 * * * * *', - uploadLimitSizeBytes: 268435456, + uploadLimitSizeBytes: 268435456, //256 Mb. findFileTimeout: 30000 } diff --git a/src/index.ts b/src/index.ts index 331fc1a..555de3c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -349,7 +349,7 @@ app.post( const cids: CID[] = [] for (const file of files) { - console.log(`Adding ${file.originalName} to IPFS`) + pino.logger.info(`Adding ${file.originalName} to IPFS`) const { stream, originalName } = file From 22c23e8a735b5d0d7c82f93a00b46a92af5546f2 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Wed, 8 May 2024 23:34:37 +0400 Subject: [PATCH 030/167] fix: multer 1.4.5 with UnixFsStorage --- config.default.json5 | 2 +- package-lock.json | 272 ++++++++--------------------- package.json | 3 +- src/index.ts | 115 ++++++------ src/multer.d.ts | 20 --- src/utils/types.ts | 3 + src/utils/unixfs-multer.storage.ts | 44 +++++ src/utils/utils.ts | 13 +- 8 files changed, 185 insertions(+), 287 deletions(-) delete mode 100644 src/multer.d.ts create mode 100644 src/utils/unixfs-multer.storage.ts diff --git a/config.default.json5 b/config.default.json5 index a45261d..7d33563 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -32,6 +32,6 @@ }, serverPort: 4000, autoPeeringPeriod: '*/10 * * * * *', - uploadLimitSizeBytes: 268435456, //256 Mb. + uploadLimitSizeBytes: 268435456, // 256 Mb. findFileTimeout: 30000 } diff --git a/package-lock.json b/package-lock.json index 9690544..e75d2c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "it-last": "^3.0.4", "json5": "^2.2.3", "libp2p": "^1.3.1", - "multer": "^2.0.0-rc.4", + "multer": "^1.4.5-lts.1", "multiformats": "^13.1.0", "pino": "^8.20.0", "pino-http": "^9.0.0" @@ -48,6 +48,7 @@ "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", "@types/node": "^20.11.30", "cross-env": "^7.0.3", "eslint": "^8.57.0", @@ -2700,17 +2701,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", - "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", - "dependencies": { - "text-decoding": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -6481,6 +6471,15 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/multicast-dns": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", @@ -7056,12 +7055,9 @@ "peer": true }, "node_modules/append-field": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-2.0.0.tgz", - "integrity": "sha512-yUPXgerKgcuwakzrRyklfhX+Ma2aYYMjb+BO2RPUwq+tk928V/i5DFWcCUS3hQhj468N+Ktmwb0tfbEtmfC6WA==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, "node_modules/argparse": { "version": "2.0.1", @@ -7223,17 +7219,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/base32-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base32-encode/-/base32-encode-2.0.0.tgz", - "integrity": "sha512-mlmkfc2WqdDtMl/id4qm3A7RjW6jxcbAoMjdRmsPiwQP0ufD4oXItYMnPgVHe80lnAIy+1xwzhHE1s4FoIceSw==", - "dependencies": { - "to-data-view": "^2.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7477,8 +7462,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "peer": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/busboy": { "version": "1.6.0", @@ -7999,6 +7983,47 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -8175,8 +8200,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "peer": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cosmiconfig": { "version": "9.0.0", @@ -8646,14 +8670,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/encode-utf8": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-2.0.0.tgz", - "integrity": "sha512-3EyMFxZj1/7oMotElDQUEQcP7N4TIe1aJ0m1uBDoyQ8I2LBHhBsXx8P3KsPbqNlGzG+NYxFwEauUwMPHZg3YDQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -9636,14 +9652,6 @@ "node": ">=0.4.0" } }, - "node_modules/fmix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fmix/-/fmix-1.0.0.tgz", - "integrity": "sha512-PIaqOGvVH5P+R92Ywy5PumsNEHvondVQh42SGOmkA9A0ZTFbfguzZpjZ/Gy3WVRUqT9Ia8k5tWlJeiZQzRHA7g==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -9704,17 +9712,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs-temp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fs-temp/-/fs-temp-2.0.1.tgz", - "integrity": "sha512-WYE7cUGOA0xRKsiYxNf/+WDuj0T20OtX85bVhsXpY+wJmjrRIUQftTI6JF9PVpo2RpGkh0l8lZsV8zszisYQ0Q==", - "dependencies": { - "random-path": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -10389,14 +10386,6 @@ "node": ">=4" } }, - "node_modules/has-own-property": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-property/-/has-own-property-2.0.0.tgz", - "integrity": "sha512-oupojxEPq/nfAshi0hOFDjpmYO4JXtymFyPC9YSBmFqGamZ2zUgrCkYInt5tf7f1j6iumlzJBpfLNRxZIQCM0w==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -11218,8 +11207,7 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "peer": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -13466,7 +13454,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -13495,21 +13482,20 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { - "version": "2.0.0-rc.4", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.0-rc.4.tgz", - "integrity": "sha512-YkjV/rOzngEBxtZV8s6BcBcB/z2v/dfDcIpWagmMsKBq0QnQCu/NpCUDd2ElExfnxRXMVXKrFh94/UKe4NdxBQ==", - "dependencies": { - "@fastify/busboy": "^1.0.0", - "append-field": "^2.0.0", - "bytes": "^3.1.0", - "fs-temp": "^2.0.1", - "has-own-property": "^2.0.0", - "on-finished": "^2.3.0", - "stream-file-type": "^0.6.1", - "type-is": "^1.6.18" + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 6.0.0" } }, "node_modules/multibase": { @@ -13574,18 +13560,6 @@ "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" }, - "node_modules/murmur-32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmur-32/-/murmur-32-1.0.0.tgz", - "integrity": "sha512-l6QKUGWXzZWHash7lmZzycpIifOrnc3PeMaoFrMv90c+xqOyOSzJU0q2T/1d15MzAdEWTpCh1paC855APqt1Gw==", - "dependencies": { - "encode-utf8": "^2.0.0", - "fmix": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/murmurhash3js-revisited": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", @@ -13881,7 +13855,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14672,8 +14645,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "peer": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/process-warning": { "version": "3.0.0", @@ -14878,18 +14850,6 @@ "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.0.2.tgz", "integrity": "sha512-o3xNv0iTcIDQCXFlF6fPAMEBRjFxssgGoRqLbg06m+AdzEXXLUmoNOoUHTVz2NoBI8hHwKFKoC6IqyNtWr2bww==" }, - "node_modules/random-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-path/-/random-path-1.0.0.tgz", - "integrity": "sha512-I6FGG7uFR3tZqHP7TzcP3Ikt5IyVEG59u7KTjIIjizcdPY6MDjD9CbbKqE+znIv4mrDF6HMlBshoemk0oRRwsQ==", - "dependencies": { - "base32-encode": "^2.0.0", - "murmur-32": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -16238,77 +16198,6 @@ "node": ">= 0.8" } }, - "node_modules/stream-file-type": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/stream-file-type/-/stream-file-type-0.6.1.tgz", - "integrity": "sha512-//KIXMQan4ow4gD//dfPu15nhH/sFdt41PzAOpD9EBFUBy/MtFjocTPO8v1dTOJnyi47TlPo6Qj+67sSE1lJKA==", - "dependencies": { - "file-type": "^16.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/stream-file-type/node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/stream-file-type/node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/stream-file-type/node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/stream-file-type/node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/stream-to-it": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-1.0.1.tgz", @@ -16657,11 +16546,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "peer": true }, - "node_modules/text-decoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", - "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" - }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -16785,14 +16669,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "peer": true }, - "node_modules/to-data-view": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-2.0.0.tgz", - "integrity": "sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -16953,6 +16829,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -17401,7 +17282,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true, "engines": { "node": ">=0.4" } diff --git a/package.json b/package.json index 4f492ca..411b38f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@swc/core": "1.3.18", "@types/busboy": "^1.5.3", "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", "@types/node": "^20.11.30", "cross-env": "^7.0.3", "eslint": "^8.57.0", @@ -64,7 +65,7 @@ "it-last": "^3.0.4", "json5": "^2.2.3", "libp2p": "^1.3.1", - "multer": "^2.0.0-rc.4", + "multer": "^1.4.5-lts.1", "multiformats": "^13.1.0", "pino": "^8.20.0", "pino-http": "^9.0.0" diff --git a/src/index.ts b/src/index.ts index 555de3c..7db7618 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ import { unixfs } from '@helia/unixfs' import { peerIdFromString } from '@libp2p/peer-id' -import { CID } from 'multiformats/cid' -import express from 'express' -import multer, { Multer } from 'multer' +import { CID, Version } from 'multiformats/cid' +import express, { NextFunction } from 'express' +import multer from 'multer' import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import { autoPeeringHandler, autoPeering } from './cron.js' import { helia } from './helia.js' @@ -17,11 +17,21 @@ import { KadDHT } from '@libp2p/kad-dht' import { PeerId } from '@libp2p/interface' import config, { configFileName } from './config.js' import { Writable } from 'node:stream' +import { UnixfsMulterStorage } from './utils/unixfs-multer.storage.js' +import { UnixFsMulterFile } from './utils/types.js' pino.logger.info(`Using config file: ${configFileName}`) +const ifs = unixfs(helia) const verifiedFetch = await createVerifiedFetch(helia) -const upload = multer({ limits: { fileSize: config.uploadLimitSizeBytes } }) +const upload = multer({ + storage: new UnixfsMulterStorage({ + unixfs: ifs, + destination: (req, file) => '/', + filename: (req, file) => file.originalname + }), + limits: { fileSize: config.uploadLimitSizeBytes } +}) let logNewPeers = false @@ -60,8 +70,6 @@ helia.libp2p.addEventListener('stop', (event) => { pino.logger.info('Libp2p node stopped') }) -const ifs = unixfs(helia) - pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) autoPeering.start() @@ -331,78 +339,55 @@ app.get('/pins/isPinned/:cid', async (req, res) => { }) }) -app.post( - '/file/upload', - upload.array('files', 5), - async ( - req: Request & { files?: { [fieldname: string]: Multer.File[] } | Multer.File[] }, - res - ) => { - if (!req.files) { - res.statusCode = 400 - return res.send({ - error: 'No file uploaded' - }) - } - const files = flatFiles(req.files) - pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalName))}`) +app.post('/file/upload', upload.array('files', 5), async (req, res) => { + if (!req.files) { + res.statusCode = 400 + return res.send({ + error: 'No file uploaded' + }) + } + const files = flatFiles(req.files as UnixFsMulterFile[]) + pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) - const cids: CID[] = [] - for (const file of files) { - pino.logger.info(`Adding ${file.originalName} to IPFS`) + const cids: CID[] = [] + for (const file of files) { + pino.logger.info(`Adding ${file.originalname} to IPFS`) - const { stream, originalName } = file + const { cid } = file + pino.logger.info(`Successfully added file ${cid}`) + cids.push(cid) - const cid = await ifs.addFile({ - path: `/${originalName}`, - content: stream - }) - pino.logger.info(`Successfully added file ${cid}`) - cids.push(cid) - - const isPinned = await helia.pins.isPinned(cid) - if (isPinned) { - pino.logger.info(`File already pinned ${cid}`) - } else { - // Pin the file - for await (const pinned of helia.pins.add(cid)) { - pino.logger.info(`Filed pinned: ${pinned}`) - } + const isPinned = await helia.pins.isPinned(cid) + if (isPinned) { + pino.logger.info(`File already pinned ${cid}`) + } else { + // Pin the file + for await (const pinned of helia.pins.add(cid)) { + pino.logger.info(`Filed pinned: ${pinned}`) } - - // Tell the network we can provide content for the passed CID - const dht = helia.libp2p.services.dht as KadDHT - await dht.provide(cid) - pino.logger.info(`Provided CID via DHT ${cid}`) - - pino.logger.info(`Routing: Providing ${cid}`) - void helia.routing.provide(cid) - pino.logger.info('Routing: Provide DONE') } - res.send({ - filesNames: files.map((file) => file.originalName), - cids: cids.map((cid) => cid.toString()) - }) - } -) + // Tell the network we can provide content for the passed CID + const dht = helia.libp2p.services.dht as KadDHT + await dht.provide(cid) + pino.logger.info(`Provided CID via DHT ${cid}`) -app.post('/test', async (req, res) => { - const textEncoder = new TextEncoder() - const cid = await ifs.addFile({ - content: textEncoder.encode('Hello world asldgfhkasjdghsk;adjflkasgjd!') - }) - - const dht = helia.libp2p.services.dht as KadDHT - for await (const event of dht.provide(cid)) { - pino.logger.info('PROVIDE', event) + pino.logger.info(`Routing: Providing ${cid}`) + void helia.routing.provide(cid) + pino.logger.info('Routing: Provide DONE') } res.send({ - cid: cid.toString() + filesNames: files.map((file) => file.originalname), + cids: cids.map((cid) => cid.toString()) }) }) +app.use((err: Error, req: Request, res: Response, next: NextFunction) => { + pino.logger.error(`${err.message}\n${err.stack}`) + res.status(500).send({ error: 'Internal Server Error. See logs.' }) +}) + app.listen(PORT, () => { pino.logger.info(`Server is running on http://localhost:${PORT}`) }) diff --git a/src/multer.d.ts b/src/multer.d.ts deleted file mode 100644 index 29f582b..0000000 --- a/src/multer.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -declare module 'multer' { - import { ReadStream } from 'node:fs' - - class Multer { - array(fieldName: string, maxFileCount: number) - } - namespace Multer { - export interface File { - fieldName: string - originalName: string - size: number - stream: ReadStream - detectedMimeType: string - detectedFileExtension: string - clientReportedMimeType: string - clientReportedFileExtension: string - } - } - export default function multer(options: {}): Multer -} diff --git a/src/utils/types.ts b/src/utils/types.ts index 6044526..9efebcb 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,6 @@ import { Multiaddr } from '@multiformats/multiaddr' import { PeerId } from '@libp2p/interface' +import { CID, Version } from 'multiformats/cid' export type NodeWithPeerId = { name: string @@ -11,3 +12,5 @@ export type ConfigNode = { name: string multiAddr: string } + +export type UnixFsMulterFile = Express.Multer.File & { cid: CID } diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts new file mode 100644 index 0000000..503d8ea --- /dev/null +++ b/src/utils/unixfs-multer.storage.ts @@ -0,0 +1,44 @@ +import { StorageEngine } from 'multer' +import * as e from 'express' +import { UnixFS } from '@helia/unixfs' +import { pino } from './logger.js' +import { UnixFsMulterFile } from './types.js' + +export interface UnixfsStorageOptions { + unixfs: UnixFS + destination: (req: e.Request, file: Express.Multer.File) => string + filename: (req: e.Request, file: Express.Multer.File) => string +} + +export class UnixfsMulterStorage implements StorageEngine { + constructor(private readonly options: UnixfsStorageOptions) {} + + _handleFile( + req: e.Request, + file: Express.Multer.File, + callback: (error?: Error, info?: Partial) => void + ): void { + const folder = this.options.destination(req, file) + const filename = this.options.filename(req, file) + this.options.unixfs + .addFile({ + path: `${folder}/${filename}`, + content: file.stream + }) + .then((cid) => { + callback(undefined, { ...file, cid }) + }) + .catch((err) => { + callback(err, undefined) + }) + } + + _removeFile( + req: e.Request, + file: Express.Multer.File, + callback: (error: Error | null) => void + ): void { + pino.logger.info(`Need remove file ${file.originalname}`) + callback(null) + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4c11d35..117c235 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,8 +1,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import config from '../config.js' -import { ConfigNode, NodeWithPeerId } from './types.js' -import { Multer } from 'multer' +import { ConfigNode, NodeWithPeerId, UnixFsMulterFile } from './types.js' /** * Get peerId from multiaddr string @@ -50,8 +49,14 @@ export function getAllowNodesMultiaddrs() { return parseNodes().map((node) => node.multiAddr.toString()) } -export function flatFiles(files: { [fieldname: string]: Multer.File[] } | Multer.File[]) { - let resultFiles: Multer.File[] = [] +export function flatFiles( + files: + | { + [fieldname: string]: UnixFsMulterFile[] + } + | UnixFsMulterFile[] +) { + let resultFiles: UnixFsMulterFile[] = [] if (Array.isArray(files)) { return files } else { From 7eb763d49bdc13e30a371d71aab200a896361b38 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Thu, 9 May 2024 12:15:30 +0400 Subject: [PATCH 031/167] fix: comments from PR --- src/disk-usage.cron.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index 4498275..c5b63aa 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -4,6 +4,8 @@ import { CronJob } from 'cron' import { config } from './config.js' import { pino } from './utils/logger.js' +const oneMb = 1048576 + let blockstoreSizeMb = 0 let datastoreSizeMb = 0 let availableSizeInMb = 0 @@ -20,9 +22,9 @@ export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { async function scan() { pino.logger.info('[Cron] Running "diskUsage" cronjob.') - blockstoreSizeMb = (await dirSize(blockstorePath)) / 1048576 - datastoreSizeMb = (await dirSize(datastorePath)) / 1048576 - availableSizeInMb = Number((await availableStorageSize()) / 1048576n) + blockstoreSizeMb = (await dirSize(blockstorePath)) / oneMb + datastoreSizeMb = (await dirSize(datastorePath)) / oneMb + availableSizeInMb = Number((await availableStorageSize()) / BigInt(oneMb)) } scan().catch(pino.logger.error) From 6bf8a5886d8f9a254d0916275ae322fb7efe668b Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Thu, 9 May 2024 21:30:45 +0400 Subject: [PATCH 032/167] fix: dirSize reworked --- README.md | 3 +++ config.default.json5 | 2 +- src/disk-usage.cron.ts | 17 +++++++++++++--- src/utils/utils.ts | 46 ++++++++++++++++++------------------------ 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 99e6e5a..07c0083 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # ipfs-node IPFS decentralized file storage and transfers + +## Caveats +System-dependent commands are used to determine which disk space is used. The code will only work on Linux systems. diff --git a/config.default.json5 b/config.default.json5 index 3b74a3c..e0d9637 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -32,7 +32,7 @@ }, serverPort: 4000, autoPeeringPeriod: '*/10 * * * * *', - diskUsageScanPeriod: '0 */1 * * * *', + diskUsageScanPeriod: '*/30 * * * * *', cors: { originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh'] } diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index c5b63aa..404f8e6 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -22,12 +22,23 @@ export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { async function scan() { pino.logger.info('[Cron] Running "diskUsage" cronjob.') - blockstoreSizeMb = (await dirSize(blockstorePath)) / oneMb - datastoreSizeMb = (await dirSize(datastorePath)) / oneMb + const start = Date.now() + + const blockstoreSize = await dirSize(blockstorePath) + if (blockstoreSize > 0) { + blockstoreSizeMb = blockstoreSize / oneMb + } + + const datastoreSize = await dirSize(datastorePath) + if (datastoreSize > 0) { + datastoreSizeMb = datastoreSize / oneMb + } + availableSizeInMb = Number((await availableStorageSize()) / BigInt(oneMb)) + pino.logger.info(`Check folder size took ${Date.now() - start} ms.`) } -scan().catch(pino.logger.error) +scan().catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) export function getDiskUsageStats() { return { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 95bb8fc..8da8bc2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,9 +2,11 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { config } from '../config.js' import { ConfigNode, NodeWithPeerId } from './types.js' -import { readdir, stat, statfs } from 'node:fs/promises' -import { join } from 'path' +import { statfs } from 'node:fs/promises' import { pino } from './logger.js' +import { exec } from 'child_process' +import { promisify } from 'node:util' +const execPromise = promisify(exec) /** * Get peerId from multiaddr string @@ -68,31 +70,23 @@ export function flatFiles( } } -export async function dirSize(dir: string): Promise { - const files = await readdir(dir, { withFileTypes: true }) - - const paths = files.map(async (file) => { - const path = join(dir, file.name) - - if (file.isDirectory()) return await dirSize(path) - - if (file.isFile()) { - try { - const { size } = await stat(path) - - return size - } catch (e) { - pino.logger.debug(`${e.message}\n${e.stack}`) - } - } - - return 0 - }) - - return (await Promise.all(paths)).flat(Infinity).reduce((i, size) => i + size, 0) -} - export async function availableStorageSize() { const statistics = await statfs('/', { bigint: true }) return statistics.bsize * statistics.bavail } + +export async function dirSize(dir: string): Promise { + try { + const { stdout } = await execPromise('du -sb .', { cwd: dir }) + const match = /^(\d+)/.exec(stdout) + + if (match && match.length >= 2) { + return Number(match[1]) + } else { + pino.logger.error('Cant parse cmd output') + } + } catch (err) { + pino.logger.error(`${err.message}\n${err.stack}`) + } + return 0 +} From b34fdeca9abd0ea0442b15674a32dac9a3b66b1f Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Thu, 9 May 2024 23:40:44 +0400 Subject: [PATCH 033/167] fix: bump helia libs versions --- package-lock.json | 1173 +++++++++++++++++++++++++++++++-------------- package.json | 33 +- src/helia.ts | 10 + 3 files changed, 848 insertions(+), 368 deletions(-) diff --git a/package-lock.json b/package-lock.json index b5918ca..2718e9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,20 +12,21 @@ "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/mfs": "^3.0.2", - "@helia/unixfs": "^3.0.2", - "@helia/verified-fetch": "^1.3.12", - "@libp2p/autonat": "^1.0.14", - "@libp2p/bootstrap": "^10.0.17", - "@libp2p/circuit-relay-v2": "^1.0.17", - "@libp2p/identify": "^1.0.16", - "@libp2p/kad-dht": "^12.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/tcp": "^9.0.17", - "@libp2p/upnp-nat": "^1.0.15", - "@libp2p/webrtc": "^4.0.23", - "blockstore-core": "^4.4.0", + "@helia/mfs": "^3.0.6", + "@helia/unixfs": "^3.0.6", + "@helia/verified-fetch": "^1.3.14", + "@libp2p/autonat": "^1.0.19", + "@libp2p/bootstrap": "^10.0.22", + "@libp2p/circuit-relay-v2": "^1.0.22", + "@libp2p/identify": "^2.0.0", + "@libp2p/interface-transport": "^4.0.3", + "@libp2p/kad-dht": "^12.0.15", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-id-factory": "^4.1.1", + "@libp2p/tcp": "^9.0.24", + "@libp2p/upnp-nat": "^1.0.20", + "@libp2p/webrtc": "^4.0.31", + "blockstore-core": "^4.4.1", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", "cors": "^2.8.5", @@ -34,10 +35,10 @@ "datastore-fs": "^9.1.8", "express": "^4.19.2", "file-type": "^19.0.0", - "helia": "^4.1.0", + "helia": "^4.2.1", "it-last": "^3.0.4", "json5": "^2.2.3", - "libp2p": "^1.3.1", + "libp2p": "^1.5.1", "multer": "^1.4.5-lts.1", "multiformats": "^13.1.0", "pino": "^8.20.0", @@ -3193,24 +3194,21 @@ } }, "node_modules/@helia/bitswap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.0.0.tgz", - "integrity": "sha512-ZcQNBp2TyU6OVvt8J3GvdQ3f5er8GrjEQrIKLl5jHSoXtbyNaCv9gBzLn85aMMXWBIUQcaom+5s1LIaJ5bqbjw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.0.tgz", + "integrity": "sha512-/RF3vxEHM6KWliLYYvukf+z388SWkJU1NJkdXdbzU7W0YjyCmanYAusOXVf5sfHIiOtK+YFO6oJzy+kcp+eySQ==", "dependencies": { - "@helia/interface": "^4.2.0", - "@helia/utils": "^0.2.0", + "@helia/interface": "^4.3.0", + "@helia/utils": "^0.3.0", "@libp2p/interface": "^1.1.2", "@libp2p/logger": "^4.0.5", "@libp2p/peer-collections": "^5.1.6", "@libp2p/utils": "^5.2.3", "@multiformats/multiaddr": "^12.1.14", "any-signal": "^4.1.1", - "debug": "^4.3.4", "interface-blockstore": "^5.2.9", "interface-store": "^5.1.7", - "it-all": "^3.0.4", "it-drain": "^3.0.5", - "it-filter": "^3.0.4", "it-length-prefixed": "^9.0.0", "it-length-prefixed-stream": "^1.1.6", "it-map": "^3.0.5", @@ -3226,40 +3224,6 @@ "uint8arrays": "^5.0.1" } }, - "node_modules/@helia/bitswap/node_modules/@helia/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-osC2VIozCfXpZyuoIm5Tor2eYD5zfW/OPiziJPnpInKM4hXD6QLexM+Zx3cz2T4X7zWRG9pLrHFJpAsKoRfJqg==", - "dependencies": { - "@helia/interface": "^4.2.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.1", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "murmurhash3js-revisited": "^3.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" - } - }, "node_modules/@helia/block-brokers": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-2.1.0.tgz", @@ -3399,9 +3363,9 @@ } }, "node_modules/@helia/interface": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.2.0.tgz", - "integrity": "sha512-8F1rZAskqxcIpXEcJOa050fsdYQLDbf6WRgUyPve+gPIZVTvwgfuGuOlZxxrXAFtH7CujRiP3yMAN3V6iTbuRA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.3.0.tgz", + "integrity": "sha512-gaWQSVGIcOkK0Wx12qUiUMFLPI8AjQCYaf4dpZdciSYPNfKOtPpW54QGzgrQYBcvBgPzg16hnnvDGwLeL2WlAQ==", "dependencies": { "@libp2p/interface": "^1.1.4", "@multiformats/dns": "^1.0.1", @@ -3431,11 +3395,11 @@ } }, "node_modules/@helia/mfs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.2.tgz", - "integrity": "sha512-vFhU2AMBGcw54FHoJPKG74lBkFGheJFJA69Tl0n531QKdaZwEgnjZB97zhx+Hxyx2S3a92gEdQiCTNRYAkOJ6g==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.6.tgz", + "integrity": "sha512-ew30vM+oLeoYx6zdzdLZNwJ6vzCHCE1twzNMeccUPQdFdGY2x/P5+izBljPId6W4zuq8XXMD2rYOmtPUeSC5sw==", "dependencies": { - "@helia/unixfs": "^3.0.2", + "@helia/unixfs": "^3.0.6", "@libp2p/interfaces": "^3.3.2", "@libp2p/logger": "^4.0.7", "interface-blockstore": "^5.2.10", @@ -3447,13 +3411,14 @@ } }, "node_modules/@helia/routers": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.0.3.tgz", - "integrity": "sha512-bX6I5ir2YCK+LW/G0YrHhqJW0SjG4DDnkxLOk2dzmZNy2OCB+Ogt0+W2keyz5nHGfTuwcBBn4qH5ouN0vTZ74w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.1.0.tgz", + "integrity": "sha512-WwHcvN8No6+3alTrxNXtTJdqfyX0Ymm41u5yL+1YFdsvRKKy5RLxNlgla34kFJaHuQGPFdROYP/9X4UaHpvcIQ==", "dependencies": { "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.2.0", + "@helia/interface": "^4.3.0", "@libp2p/interface": "^1.1.4", + "@multiformats/uri-to-multiaddr": "^8.0.0", "ipns": "^9.0.0", "it-first": "^3.0.4", "it-map": "^3.0.5", @@ -3462,11 +3427,11 @@ } }, "node_modules/@helia/unixfs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.2.tgz", - "integrity": "sha512-zGOCFPJn8aAxNCImo67PReV2WJIdmjh5pPBDp2MTyeMoa1G5VuhfW6GnSYzQQNagbY/LYdSxDIE+a33rgjGtdw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.6.tgz", + "integrity": "sha512-x3hoHUjwNk2M9Hg0Z9sQE8O8S2rcj13O9dYexGHckxdpeEPhNOwRWT1cMpY8NTK0vpuIs0TvlQm9gYfHm94Clw==", "dependencies": { - "@helia/interface": "^4.1.0", + "@helia/interface": "^4.3.0", "@ipld/dag-pb": "^4.1.0", "@libp2p/interface": "^1.1.4", "@libp2p/logger": "^4.0.7", @@ -3477,7 +3442,7 @@ "ipfs-unixfs-exporter": "^13.5.0", "ipfs-unixfs-importer": "^15.2.4", "it-all": "^3.0.4", - "it-glob": "^2.0.6", + "it-glob": "^3.0.0", "it-last": "^3.0.4", "it-pipe": "^3.0.1", "merge-options": "^3.0.4", @@ -3487,22 +3452,31 @@ "uint8arrays": "^5.0.2" } }, + "node_modules/@helia/unixfs/node_modules/it-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-3.0.1.tgz", + "integrity": "sha512-IUWE9f6XVUJLugK7pQmQPqTWj4BiQJhufnvxfsCmNIGEDQEkKVs3Ld9gFZq/Vude6g/OpndhsiuFrA730Bc59A==", + "dependencies": { + "fast-glob": "^3.3.2" + } + }, "node_modules/@helia/utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.1.0.tgz", - "integrity": "sha512-uRUDzGpeWbPANFlq3J3x14tE89enVEBoaRXM1CKpE25vXUOij9qoYjE8GlXDNCQgkazAzrVswdjcRn6Eza+bIg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-lCOd5DsimmUrp9+TyxWDkvhDFvI+Hgn7PevQHGya3AwmJXxsuCY6FtRgd7YkdaoqAm4+q7L710UnUtp5AmHlVA==", "dependencies": { - "@helia/interface": "^4.1.0", + "@helia/interface": "^4.3.0", "@ipld/dag-cbor": "^9.2.0", "@ipld/dag-json": "^10.2.0", "@ipld/dag-pb": "^4.1.0", + "@libp2p/crypto": "^4.0.6", "@libp2p/interface": "^1.1.4", "@libp2p/logger": "^4.0.7", - "@libp2p/peer-collections": "^5.1.7", "@libp2p/utils": "^5.2.6", "@multiformats/dns": "^1.0.1", + "@types/murmurhash3js-revisited": "^3.0.3", "any-signal": "^4.1.1", - "blockstore-core": "^4.4.0", + "blockstore-core": "^4.4.1", "cborg": "^4.0.9", "interface-blockstore": "^5.2.10", "interface-datastore": "^8.2.11", @@ -3513,14 +3487,17 @@ "it-merge": "^3.0.3", "mortice": "^3.0.4", "multiformats": "^13.1.0", + "murmurhash3js-revisited": "^3.0.0", + "p-defer": "^4.0.1", "progress-events": "^1.0.0", + "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.2" } }, "node_modules/@helia/verified-fetch": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.12.tgz", - "integrity": "sha512-eLDGvrDLDs09vZk5PxYmPTB7WlJ88ff4GwF7gRbH5zaqZVVCUP3S6lkEvmNM+9MYPfElfR/wUOmN2GJIVqDQ/A==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.3.14.tgz", + "integrity": "sha512-UK2mMMIZpgDIH5MfsBdlRtrBfx9wjGZL1xqIWmA91Fuk+DovQN12jui7zpfaARe4S1B4QyYHNhggUADGRx0UHA==", "dependencies": { "@helia/block-brokers": "^2.1.0", "@helia/car": "^3.1.3", @@ -3887,16 +3864,16 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@libp2p/autonat": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.14.tgz", - "integrity": "sha512-yd6Qlltzt7/kkec9wOtgUJzzDXXreVXyKVR1E5zms58sQmVavc3PWEJE1cFxK0GYMF2QjjzJY13/z9WLeQBzIA==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.19.tgz", + "integrity": "sha512-TyptjlqUvxeRIE8wrr54j3vAteSuJof1U1ryHDLjoM3fXk64a8Ie9Yc1PkcOEMXBJ7oiAEoxe0LmQ+VW89q+hQ==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-id-factory": "^4.1.1", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "it-first": "^3.0.4", "it-length-prefixed": "^9.0.4", "it-map": "^3.0.5", @@ -3907,46 +3884,46 @@ } }, "node_modules/@libp2p/bootstrap": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.17.tgz", - "integrity": "sha512-NO+YTigMn62YMbPAz4jK8sOC6tY2K5Q7wnRI2lmHzP9albIX+Nf4/Tyj7tu8Owi5CP+v07eKXWejmrXm+MkJPg==", + "version": "10.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.22.tgz", + "integrity": "sha512-qAEBvmsksPnuVYMF2Khfvw3Nvatv+VHvrW36A1LQM20nIZ4D25c0t0D9xUyMZd87R4/T+fqCPsW8JKG+6XQH7w==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14" + "@multiformats/multiaddr": "^12.2.1" } }, "node_modules/@libp2p/circuit-relay-v2": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.17.tgz", - "integrity": "sha512-FSTqidxco90MjMdKTGhFZoGxf/EaZMuwRWyEq5oGw1aG8lBKrH2fwP1fTRfwAQVhQ8Pxd9B9/SfBU6QSHA5YKQ==", - "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@libp2p/utils": "^5.2.7", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.22.tgz", + "integrity": "sha512-N913QHAsjIkOtS3cbr1oANG8UbUMYNAxOkgNkTzBNTqTiNbJQxOellK00DpX+quEOP0T+YUc7EnCSrU5wtvL+g==", + "dependencies": { + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr": "^12.2.1", "any-signal": "^4.1.1", "it-protobuf-stream": "^1.1.2", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", - "p-defer": "^4.0.0", + "p-defer": "^4.0.1", "p-retry": "^6.2.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/crypto": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.6.tgz", - "integrity": "sha512-AJ4i3DHOTlY961O26M3k1IjmU4rUd5WgeK4t9IRzFfLIbD6uwA+cevJMG2qr0UHJfbYdGKKQ2Po1wqZONoIA9Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.1.tgz", + "integrity": "sha512-ju7P18Swd0CAa4ud31PBQdxPN++6dFl49RrH4F4yh5HYA3hXL18nbioH18iJ4WAahzjNQsdRMYzTQLWAUZPAkg==", "dependencies": { - "@libp2p/interface": "^1.2.0", + "@libp2p/interface": "^1.3.1", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "asn1js": "^3.0.5", @@ -3973,27 +3950,29 @@ } }, "node_modules/@libp2p/identify": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-1.0.16.tgz", - "integrity": "sha512-uDXae1DGok+rFF5T8yixf3lkadr5U3O+x72BjgZaIeduWLCbPDczlKMNJuFJF3+ys45qcO8WuySuZqyK2aq8kw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.0.0.tgz", + "integrity": "sha512-1vdXRHIkENE+iyN4yXo00ia02I63UCm4FVeM5pQZZxbeZz4+1Eo8dJuDYU9h0SFTghhkIYeyTT6CnkqfvMcy8A==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "it-drain": "^3.0.7", + "it-parallel": "^3.0.7", "it-protobuf-stream": "^1.1.2", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2", + "uint8arrays": "^5.0.3", "wherearewe": "^2.0.1" } }, "node_modules/@libp2p/interface": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.2.0.tgz", - "integrity": "sha512-ImnGNl3El/AukgaojACT8i9SNW1FOsrThcQU/qA3w5tEBR5p84Uwgzl/nxa4X5vGinItUJ9jLEJmtkQJENoiGQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.3.1.tgz", + "integrity": "sha512-KJoYP6biAgIHUU3pxaixaaYCvIHZshzXetxfoNigadAZ3hCGuwpdFhk7IABEaI3RgadOOYUwW3MXPbL+cxnXVQ==", "dependencies": { "@multiformats/multiaddr": "^12.2.1", "it-pushable": "^3.2.3", @@ -4003,17 +3982,114 @@ "uint8arraylist": "^2.4.8" } }, + "node_modules/@libp2p/interface-connection": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-5.1.1.tgz", + "integrity": "sha512-ytknMbuuNW72LYMmTP7wFGP5ZTaUSGBCmV9f+uQ55XPcFHtKXLtKWVU/HE8IqPmwtyU8AO7veGoJ/qStMHNRVA==", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^2.0.1", + "uint8arraylist": "^2.4.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@libp2p/interface-internal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.1.0.tgz", - "integrity": "sha512-B6Cu3Mhp5kY2Z1cU0soCR4ZtjZtE4FuWE0qdJNauOpcQe9HOjPF8SanFmeEIZ0FKSOo0onQdQi2YdNUTtOVyvQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.2.0.tgz", + "integrity": "sha512-7jrVw3I9B7l6TiKIq199sn0y1BlPVfFGrYE8h02RLLEEuq64q4Gx+/UGmCo7aD3E4+wCcrCLP37O4LZxjAhWUQ==", "dependencies": { - "@libp2p/interface": "^1.2.0", - "@libp2p/peer-collections": "^5.1.10", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-collections": "^5.2.0", "@multiformats/multiaddr": "^12.2.1", "uint8arraylist": "^2.4.8" } }, + "node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-id/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-stream-muxer": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-stream-muxer/-/interface-stream-muxer-4.1.2.tgz", + "integrity": "sha512-dQJcn67UaAa8YQFRJDhbo4uT453z/2lCzD/ZwTk1YOqJxATXbXgVcB8dXDQFEUiUX3ZjVQ1IBu+NlQd+IZ++zw==", + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interfaces": "^3.0.0", + "@libp2p/logger": "^2.0.7", + "abortable-iterator": "^5.0.1", + "any-signal": "^4.1.1", + "it-pushable": "^3.1.3", + "it-stream-types": "^2.0.1", + "uint8arraylist": "^2.4.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-stream-muxer/node_modules/@libp2p/logger": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-2.1.1.tgz", + "integrity": "sha512-2UbzDPctg3cPupF6jrv6abQnAUTrbLybNOj0rmmrdGm1cN2HJ1o/hBu0sXuq4KF9P1h/eVRn1HIRbVIEKnEJrA==", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.2", + "@multiformats/multiaddr": "^12.1.3", + "debug": "^4.3.4", + "interface-datastore": "^8.2.0", + "multiformats": "^11.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-stream-muxer/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-transport": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@libp2p/interface-transport/-/interface-transport-4.0.3.tgz", + "integrity": "sha512-jXFQ3blhFMEyQbFw/U8Glo3F/fUO5LEaX5HIdeqNpCliK+XnwTfpkcaG+WsJrcApWK4FFyUHc+GGqiWR0hAFFg==", + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-stream-muxer": "^4.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@libp2p/interfaces": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", @@ -4024,17 +4100,17 @@ } }, "node_modules/@libp2p/kad-dht": { - "version": "12.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.13.tgz", - "integrity": "sha512-as+pIHcrjVTXkf6kyULtnJg/RRvrbVjyycPD8HtqD5SKB0PDiYvJO0LY0oVAHFFi5oGMCqSG5777VQfC/tmRhQ==", - "dependencies": { - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.2.0", - "@libp2p/interface-internal": "^1.1.0", - "@libp2p/peer-collections": "^5.1.10", - "@libp2p/peer-id": "^4.0.10", - "@libp2p/record": "^4.0.0", - "@libp2p/utils": "^5.3.1", + "version": "12.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.15.tgz", + "integrity": "sha512-BY4/0hok8PhvaOL31AvX0CkQC3j0yUgahzMNr2gqLI9wbCiuiOqzo4FSuJ0gIkv8I9XcGS6L+CfZ44SpsUZPEA==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/record": "^4.0.1", + "@libp2p/utils": "^5.4.0", "@multiformats/multiaddr": "^12.2.1", "any-signal": "^4.1.1", "hashlru": "^2.3.0", @@ -4077,11 +4153,11 @@ } }, "node_modules/@libp2p/logger": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.10.tgz", - "integrity": "sha512-JiRfJHO/D9Jlh2rJ6STnONoeQevBAdAZaGUxrtvBf4RFfucldSFEMOtdkFO8xFGuiA90Q2kj4BE2douG6fB3Lw==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.12.tgz", + "integrity": "sha512-eSiHY06Zijq6b1rMBob/ZuGBEavFm8IPNPEzeOU3JrBU7v/OV8Da0FesbemtkudZVPRi8mqRoOiIwmWn0mObng==", "dependencies": { - "@libp2p/interface": "^1.2.0", + "@libp2p/interface": "^1.3.1", "@multiformats/multiaddr": "^12.2.1", "debug": "^4.3.4", "interface-datastore": "^8.2.11", @@ -4119,86 +4195,87 @@ } }, "node_modules/@libp2p/multistream-select": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.5.tgz", - "integrity": "sha512-fQSF3bjlaSqneD50/cj9Kbl2YjFVy45kHxtjnMGb04cnyL8CefJ89dY2aF3bWCbbEqUxzc4yCCNi48y+Pfo34w==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.9.tgz", + "integrity": "sha512-Cs7KgIZXmHnTbtbK0nHVezgghCuo/8IjdioeViXyljTe2/ye4lds86lSkuZTEEbggM/4djT0fBdvqAJ2F5rhVQ==", "dependencies": { - "@libp2p/interface": "^1.1.5", + "@libp2p/interface": "^1.3.1", "it-length-prefixed": "^9.0.4", "it-length-prefixed-stream": "^1.1.6", "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0", + "p-defer": "^4.0.1", "race-signal": "^1.0.2", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-collections": { - "version": "5.1.10", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.1.10.tgz", - "integrity": "sha512-Edr4FBzCgE7FRgc0wfYfcmihQ4GDHwkQP7xMG4oOVoIxHEzuk9Nb2opK9cLbK+nU4oAROgFLzJEJuiG7BGV2hg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.0.tgz", + "integrity": "sha512-I8fXe+3ToYJaXXodddEtj1Ix2gyfz46NzL5i47e776wwCZhtQqjA1kbhTkO8bQogHYSub0SEWxsjm1HL9dtSYw==", "dependencies": { - "@libp2p/interface": "^1.2.0", - "@libp2p/peer-id": "^4.0.10" + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0" } }, "node_modules/@libp2p/peer-id": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.0.10.tgz", - "integrity": "sha512-cR5dQ5fPcxP4LLSXDgo+TSOhtElZSwRXVSSgT/GM/Vvbua5M91NzsksYfd/lg8XwTCSvTER0qmE6ZIR05vjQrA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.1.1.tgz", + "integrity": "sha512-Fu25drhhdc5JDWb3x33u+5p9I3F2tmYrlx2aiwD5Kv70RLKC9k7B1m4iywBkFE82QKUPtT21IrVYlwsm46znqQ==", "dependencies": { - "@libp2p/interface": "^1.2.0", + "@libp2p/interface": "^1.3.1", "multiformats": "^13.1.0", "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-id-factory": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.0.8.tgz", - "integrity": "sha512-Kk9TqhSY0dPr2RmLUSJj+V5j4vgxrB25G5bf+nNfY79s/YNh0uBIq2ZmeBezXnG4q5egH1e9vbrgHdl7PUllUA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.1.1.tgz", + "integrity": "sha512-8/tRsKQLtgXmM8KklE1XB5VFfHytnbhibH/tRMN9CFZWkpkK1KmFgMxN8t9jZ2+zSK1ozZQQi4jyvkYEyLi5zQ==", "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-record": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.11.tgz", - "integrity": "sha512-jAi/TX1NSdNPsO0TmJd5fEKQfjdkxxBPmlwofBx2dPqtud/Aac4wa6dvIFQKqGkh8ZeC8FAQbv8jE/a8jgg3MQ==", - "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.16.tgz", + "integrity": "sha512-U6oDKvz40CDSgQkbd7FIORJFB8XCPJ+At4i4JFNtyKX/c34nPINTPCH0lL6exsTnus8L2XHhRC9Hh9zfMyXdNg==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "protons-runtime": "^5.4.0", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/peer-store": { - "version": "10.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.12.tgz", - "integrity": "sha512-lX8wdgX0NNxIcnutJ3hxfhyt+QXl0fVxYY3O1tKtEPoyilbxBA66qVVdU3nVmETkku7TcUuSOEfwevwmQC7qhw==", + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.17.tgz", + "integrity": "sha512-LHo63U8dbeqrdohc19h4M9qkI7iDTpIHG+kLv2A2lNGwrR22op0mk0ARRPUTi8ZBWvLg1Q3cpXih5JBAdU2n7w==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-record": "^7.0.11", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/interface": "^1.3.1", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-record": "^7.0.16", + "@multiformats/multiaddr": "^12.2.1", "interface-datastore": "^8.2.11", "it-all": "^3.0.4", "mortice": "^3.0.4", "multiformats": "^13.1.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/ping": { @@ -4236,9 +4313,9 @@ } }, "node_modules/@libp2p/record": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.0.tgz", - "integrity": "sha512-qEbLgKj1DY6K0B9LFXXm0hdCmMJdkfrxFMa5/pOe9PufaRPCAOABYmbPYQQmDNI8UrwWJbf47tj+pmDS57SLTw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.1.tgz", + "integrity": "sha512-hflqTiuuMI7GzmTyvycv7y9wvR/xePD/06gcY2ePnWkA1jWlIuebegEbs6+E6MSHQRnmnfecpeZDVRFOQVUSEw==", "dependencies": { "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", @@ -4246,66 +4323,71 @@ } }, "node_modules/@libp2p/tcp": { - "version": "9.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.17.tgz", - "integrity": "sha512-joGy4Zs/2jQ1pe8fD7yr9lLqJ2ZXxZN6dZhhzknf3Opq1YRQ3yyz2eu5G1LNdN0jqyO259UO+TnLtEkruel/QQ==", + "version": "9.0.24", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.24.tgz", + "integrity": "sha512-cv5Kp0z/MOKJNS1OAgYGQNHfXuH1/mdPkUl/3ifjlj1+LMOnWalXm/WM13l2gDCQcwSsEYMRt4wKGcja9J+BTQ==", "dependencies": { - "@libp2p/interface": "^1.1.5", - "@libp2p/utils": "^5.2.7", + "@libp2p/interface": "^1.3.1", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", + "@multiformats/multiaddr": "^12.2.1", "@types/sinon": "^17.0.3", - "stream-to-it": "^0.2.4" + "stream-to-it": "^1.0.0" } }, "node_modules/@libp2p/upnp-nat": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.15.tgz", - "integrity": "sha512-F9NStswMhpKNfzFf91mHxWCw+yKTZfbeadgb5wW6sFh6gQNpMWHdvUDoC8hg2qL8snHcMn5OqGNgE+KvNvSKAw==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.20.tgz", + "integrity": "sha512-Mzcqsu0Ylg2nHk3aiRafFNE1U09vIBvzszD32+RavK18OdC8Heh7OweCtWQNUpyHehrHZAw7Rn3XT+f1aVVaFQ==", "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.13", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/utils": "^5.2.7", - "@multiformats/multiaddr": "^12.1.14", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/utils": "^5.4.0", + "@multiformats/multiaddr": "^12.2.1", "wherearewe": "^2.0.1" } }, "node_modules/@libp2p/utils": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.3.1.tgz", - "integrity": "sha512-FdGzRU50PJLYSEOmVXqqtq27yjUVXkU4QNRZzMVuXF9L/sKgSC2oXwj0Satc9fHx5tG3MCX1ZOSAmYEIl2fu+w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.0.tgz", + "integrity": "sha512-tEBhg5g06vQbR+x51U2N5xAWFNrQpVIVnJZX8urrKcxTRQnJk57Uka2eTEtBj8AxgQ0DDC7a5r2km2X50ja/EA==", "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/interface": "^1.2.0", - "@libp2p/logger": "^4.0.10", + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/logger": "^4.0.12", "@multiformats/multiaddr": "^12.2.1", "@multiformats/multiaddr-matcher": "^1.2.0", + "@sindresorhus/fnv1a": "^3.1.0", + "@types/murmurhash3js-revisited": "^3.0.3", "delay": "^6.0.0", "get-iterator": "^2.0.1", "is-loopback-addr": "^2.0.2", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", + "murmurhash3js-revisited": "^3.0.0", "netmask": "^2.0.2", "p-defer": "^4.0.1", "race-event": "^1.2.0", "race-signal": "^1.0.2", - "uint8arraylist": "^2.4.8" + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/webrtc": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.23.tgz", - "integrity": "sha512-/HI60R9KLivtFCB2vIKDcwyppnKpB2T7BYBej5CCO6KQQM2zEA9/jd/dZ94hs7DUQZCOynJHcUjRKG/rIDyncQ==", + "version": "4.0.31", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.31.tgz", + "integrity": "sha512-+MIQ+qv2j0H2iOOnnyniiYpDbEBq1s7gna+ndElV5A7cimv+4OXdCu8CcmtGdfPvrUbZxFYKmSvbcBF87YLZcw==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/utils": "^5.2.7", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/utils": "^5.4.0", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.1.14", - "@multiformats/multiaddr-matcher": "^1.1.2", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "detect-browser": "^5.3.0", "it-length-prefixed": "^9.0.4", "it-protobuf-stream": "^1.1.2", @@ -4313,15 +4395,15 @@ "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", "multihashes": "^4.0.3", - "node-datachannel": "^0.5.5", - "p-defer": "^4.0.0", - "p-event": "^6.0.0", + "node-datachannel": "^0.8.0", + "p-defer": "^4.0.1", + "p-event": "^6.0.1", "p-timeout": "^6.1.2", "protons-runtime": "^5.4.0", "race-signal": "^1.0.2", - "react-native-webrtc": "^118.0.1", + "react-native-webrtc": "^118.0.3", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "uint8arrays": "^5.0.3" } }, "node_modules/@libp2p/websockets": { @@ -4429,6 +4511,15 @@ "npm": ">=7.0.0" } }, + "node_modules/@multiformats/uri-to-multiaddr": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@multiformats/uri-to-multiaddr/-/uri-to-multiaddr-8.0.0.tgz", + "integrity": "sha512-86O+gY6JTnCv0O/IxTKV+1+GACoEBTr5Cfyh+FdzStWneviz2AZwLK8Hsys5dbfMgT//Vs7FolMiEHURlCel8w==", + "dependencies": { + "@multiformats/multiaddr": "^12.1.14", + "is-ip": "^5.0.0" + } + }, "node_modules/@noble/ciphers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", @@ -4463,7 +4554,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4476,7 +4566,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -4485,7 +4574,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -5512,54 +5600,246 @@ "node": ">=18" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", - "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", - "peer": true, - "engines": { - "node": ">=18" - } + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.21", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.15", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", - "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", - "peer": true, - "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.21", - "hermes-parser": "0.15.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@react-native/normalize-colors": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", - "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", - "peer": true + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", - "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", - "peer": true, - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react-native": "*" - } + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@sideway/address": { "version": "4.1.5", @@ -5588,6 +5868,17 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "peer": true }, + "node_modules/@sindresorhus/fnv1a": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", + "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -5726,6 +6017,11 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -6159,6 +6455,19 @@ "node": ">=6.5" } }, + "node_modules/abortable-iterator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/abortable-iterator/-/abortable-iterator-5.0.1.tgz", + "integrity": "sha512-hlZ5Z8UwqrKsJcelVPEqDduZowJPBQJ9ZhBC2FXpja3lXy8X6MoI5uMzIgmrA8+3jcVnp8TF/tx+IBBqYJNUrg==", + "dependencies": { + "get-iterator": "^2.0.0", + "it-stream-types": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7154,6 +7463,20 @@ "node": ">=6" } }, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "dependencies": { + "is-regexp": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7415,6 +7738,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -8601,7 +8935,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -8670,7 +9003,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -8933,6 +9265,19 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -8941,6 +9286,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9061,7 +9417,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9579,22 +9934,22 @@ } }, "node_modules/helia": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/helia/-/helia-4.1.0.tgz", - "integrity": "sha512-ZDRW4fir8GdUKjI+M21BpzGGzbgWeD5G4LGpMEiXBYwwgIdn+ITCeLIIIOA9pPc0BJSIOJGpsMtaCV8FztgQIg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.1.tgz", + "integrity": "sha512-b3ZXDeddNtSiUbz8mJumL0chTRYjGGjmPb5qQhilXOG5PKFwffffYaR0Py4eaZ5eTlBlCfKCqjiBNeS4oerAWA==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/block-brokers": "^2.0.3", + "@helia/block-brokers": "^3.0.0", "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.1.0", - "@helia/routers": "^1.0.2", - "@helia/utils": "^0.1.0", + "@helia/interface": "^4.3.0", + "@helia/routers": "^1.1.0", + "@helia/utils": "^0.3.0", "@libp2p/autonat": "^1.0.13", "@libp2p/bootstrap": "^10.0.16", "@libp2p/circuit-relay-v2": "^1.0.16", "@libp2p/dcutr": "^1.0.13", - "@libp2p/identify": "^1.0.15", + "@libp2p/identify": "^2.0.0", "@libp2p/interface": "^1.1.4", "@libp2p/kad-dht": "^12.0.8", "@libp2p/keychain": "^4.0.9", @@ -9617,6 +9972,25 @@ "multiformats": "^13.1.0" } }, + "node_modules/helia/node_modules/@helia/block-brokers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.0.tgz", + "integrity": "sha512-f93Lklv+ZyySxIAiZDG6p2kRF+OV3hdhVUAyWNK6TWpyxuFNj45cYQci9nz/ZTmEHqg2ylswwiq4lEGml3WS7Q==", + "dependencies": { + "@helia/bitswap": "^1.1.0", + "@helia/interface": "^4.3.0", + "@helia/utils": "^0.3.0", + "@libp2p/interface": "^1.1.4", + "@libp2p/utils": "^5.2.6", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", + "@multiformats/multiaddr-to-uri": "^10.0.1", + "interface-blockstore": "^5.2.10", + "interface-store": "^5.1.8", + "multiformats": "^13.1.0", + "progress-events": "^1.0.0" + } + }, "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", @@ -9895,6 +10269,17 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -9938,26 +10323,26 @@ } }, "node_modules/ipfs-unixfs-importer": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.2.4.tgz", - "integrity": "sha512-3b7d/pLPwGvAEXvpJ0WyYlbn2pb2j7qY6FayuMSzbZNdFxdJ82l6VkJ9vK1d/G/AHx+0ZfB06eSdGKjX0GVCAg==", + "version": "15.2.5", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.2.5.tgz", + "integrity": "sha512-K8tybgmhOthQ/ThPcjohYnTaVhv1hi3YKo+gPREBXcMUUG7Yd2HYfz1jExuOfhaMzICl0hDKwUMJsaU6FXMJew==", "dependencies": { - "@ipld/dag-pb": "^4.0.0", - "@multiformats/murmur3": "^2.0.0", + "@ipld/dag-pb": "^4.1.0", + "@multiformats/murmur3": "^2.1.8", "err-code": "^3.0.1", - "hamt-sharding": "^3.0.0", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.1", + "hamt-sharding": "^3.0.6", + "interface-blockstore": "^5.2.10", + "interface-store": "^5.1.8", "ipfs-unixfs": "^11.0.0", - "it-all": "^3.0.2", - "it-batch": "^3.0.2", - "it-first": "^3.0.2", - "it-parallel-batch": "^3.0.1", - "multiformats": "^13.0.0", + "it-all": "^3.0.4", + "it-batch": "^3.0.4", + "it-first": "^3.0.4", + "it-parallel-batch": "^3.0.4", + "multiformats": "^13.1.0", "progress-events": "^1.0.0", - "rabin-wasm": "^0.1.4", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0" + "rabin-wasm": "^0.1.5", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.0.3" } }, "node_modules/ipns": { @@ -10040,7 +10425,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10058,7 +10442,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -10075,6 +10458,21 @@ "node": ">=8" } }, + "node_modules/is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "dependencies": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-loopback-addr": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", @@ -10137,6 +10535,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", @@ -10234,9 +10643,9 @@ } }, "node_modules/it-drain": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.5.tgz", - "integrity": "sha512-qYFe4SWdvs9oJGUY5bSjvmiLUMLzFEODNOQUdYdCIkuIgQF+AUB2INhM4yQ09buJ2rhHKDFxvTD/+yUq6qg0XA==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.7.tgz", + "integrity": "sha512-vy6S1JKjjHSIFHgBpLpD1zhkCRl3z1zYWUxE14+kAYf+BL9ssWSFImJfhl361IIcwr0ofw8etzg11VqqB+ntUA==" }, "node_modules/it-filter": { "version": "3.0.4", @@ -10362,11 +10771,11 @@ } }, "node_modules/it-parallel": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.6.tgz", - "integrity": "sha512-i7UM7I9LTkDJw3YIqXHFAPZX6CWYzGc+X3irdNrVExI4vPazrJdI7t5OqrSVN8CONXLAunCiqaSV/zZRbQR56A==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.7.tgz", + "integrity": "sha512-aIIc2t8knfER/mQu4uEHaAYZrnj/2Tdp+Vj6BA94Gi7xghx1kblvpyrLkCYO9K+eDyPS1cE3Vfhh9a20MEmzXA==", "dependencies": { - "p-defer": "^4.0.0" + "p-defer": "^4.0.1" } }, "node_modules/it-parallel-batch": { @@ -10942,30 +11351,34 @@ } }, "node_modules/libp2p": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.3.1.tgz", - "integrity": "sha512-mcVAQRECnn7TBXVDGihgAKRSL+v7otA9jgpeBnfPJvlbVbtFE9tKAUeBBgHVLifM2dXPjvl5fxzFMgYyP+fGbA==", - "dependencies": { - "@libp2p/crypto": "^4.0.4", - "@libp2p/interface": "^1.1.5", - "@libp2p/interface-internal": "^1.0.10", - "@libp2p/logger": "^4.0.8", - "@libp2p/multistream-select": "^5.1.5", - "@libp2p/peer-collections": "^5.1.8", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/peer-store": "^10.0.12", - "@libp2p/utils": "^5.2.7", - "@multiformats/dns": "^1.0.1", - "@multiformats/multiaddr": "^12.2.0", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.5.1.tgz", + "integrity": "sha512-RzfOGjFRpSUDsv5c1ZA2TsWbPLaS86AVbIqhCrh273F9x3N/CkCXmRfWFmaRFmezWhP3SHbjsFWyQRJGF9U5mw==", + "dependencies": { + "@libp2p/crypto": "^4.1.1", + "@libp2p/interface": "^1.3.1", + "@libp2p/interface-internal": "^1.2.0", + "@libp2p/logger": "^4.0.12", + "@libp2p/multistream-select": "^5.1.9", + "@libp2p/peer-collections": "^5.2.0", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-id-factory": "^4.1.1", + "@libp2p/peer-store": "^10.0.17", + "@libp2p/utils": "^5.4.0", + "@multiformats/dns": "^1.0.5", + "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr-matcher": "^1.2.0", "any-signal": "^4.1.1", - "datastore-core": "^9.2.8", + "datastore-core": "^9.2.9", "interface-datastore": "^8.2.11", "it-merge": "^3.0.3", "it-parallel": "^3.0.6", "merge-options": "^3.0.4", "multiformats": "^13.1.0", - "uint8arrays": "^5.0.2" + "p-defer": "^4.0.1", + "race-event": "^1.3.0", + "race-signal": "^1.0.2", + "uint8arrays": "^5.0.3" } }, "node_modules/lighthouse-logger": { @@ -11785,7 +12198,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -12520,9 +12932,9 @@ } }, "node_modules/node-abi": { - "version": "3.56.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz", - "integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==", + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", "dependencies": { "semver": "^7.3.5" }, @@ -12537,13 +12949,14 @@ "peer": true }, "node_modules/node-datachannel": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.5.5.tgz", - "integrity": "sha512-B0MS/iK0qjCiNY5Go4055zuRCOuiFkOrhNOZuj2PAnaJN7YlnKxKidOLhLXf/lAcsJQXgj8BR31zsqJKKA16jA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.8.0.tgz", + "integrity": "sha512-05vYthhjESfO5qumWRbqHI1PN71rc19DJjc14ofOLapJrTCnmhx0JYw0G5wcL56b57uHQP9WjEak3+XklrB+Og==", "hasInstallScript": true, "dependencies": { "node-domexception": "^2.0.1", - "prebuild-install": "^7.0.1" + "prebuild-install": "^7.0.1", + "rollup": "^4.14.1" }, "engines": { "node": ">=16.0.0" @@ -13624,7 +14037,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -13671,9 +14083,9 @@ } }, "node_modules/race-event": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.2.0.tgz", - "integrity": "sha512-7EvAjTu9uuKa03Jky8yfSy6/SnnMTh6nouNmdeWv9b0dT8eDZC5ylx30cOR9YO9otQorVjjkIuSHQ5Ml/bKwMw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.3.0.tgz", + "integrity": "sha512-kaLm7axfOnahIqD3jQ4l1e471FIFcEGebXEnhxyLscuUzV8C94xVHtWEqDDXxll7+yu/6lW0w1Ff4HbtvHvOHg==" }, "node_modules/race-signal": { "version": "1.0.2", @@ -14246,7 +14658,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -14322,6 +14733,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", + "fsevents": "~2.3.2" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -14335,7 +14780,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -14861,18 +15305,13 @@ } }, "node_modules/stream-to-it": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-0.2.4.tgz", - "integrity": "sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-1.0.1.tgz", + "integrity": "sha512-AqHYAYPHcmvMrcLNgncE/q0Aj/ajP6A4qGhxP6EVn7K3YTNs0bJpJyk57wc2Heb7MUL64jurvmnmui8D9kjZgA==", "dependencies": { - "get-iterator": "^1.0.2" + "it-stream-types": "^2.0.1" } }, - "node_modules/stream-to-it/node_modules/get-iterator": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-1.0.2.tgz", - "integrity": "sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==" - }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -15078,6 +15517,22 @@ "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", "peer": true }, + "node_modules/super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "dependencies": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -15309,6 +15764,20 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/timestamp-nano": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", diff --git a/package.json b/package.json index 9085120..9a5bec1 100644 --- a/package.json +++ b/package.json @@ -41,20 +41,21 @@ "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/mfs": "^3.0.2", - "@helia/unixfs": "^3.0.2", - "@helia/verified-fetch": "^1.3.12", - "@libp2p/autonat": "^1.0.14", - "@libp2p/bootstrap": "^10.0.17", - "@libp2p/circuit-relay-v2": "^1.0.17", - "@libp2p/identify": "^1.0.16", - "@libp2p/kad-dht": "^12.0.10", - "@libp2p/peer-id": "^4.0.8", - "@libp2p/peer-id-factory": "^4.0.8", - "@libp2p/tcp": "^9.0.17", - "@libp2p/upnp-nat": "^1.0.15", - "@libp2p/webrtc": "^4.0.23", - "blockstore-core": "^4.4.0", + "@helia/mfs": "^3.0.6", + "@helia/unixfs": "^3.0.6", + "@helia/verified-fetch": "^1.3.14", + "@libp2p/autonat": "^1.0.19", + "@libp2p/bootstrap": "^10.0.22", + "@libp2p/circuit-relay-v2": "^1.0.22", + "@libp2p/identify": "^2.0.0", + "@libp2p/kad-dht": "^12.0.15", + "@libp2p/peer-id": "^4.1.1", + "@libp2p/peer-id-factory": "^4.1.1", + "@libp2p/tcp": "^9.0.24", + "@libp2p/upnp-nat": "^1.0.20", + "@libp2p/webrtc": "^4.0.31", + "@libp2p/interface-transport": "^4.0.3", + "blockstore-core": "^4.4.1", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", "cors": "^2.8.5", @@ -63,10 +64,10 @@ "datastore-fs": "^9.1.8", "express": "^4.19.2", "file-type": "^19.0.0", - "helia": "^4.1.0", + "helia": "^4.2.1", "it-last": "^3.0.4", "json5": "^2.2.3", - "libp2p": "^1.3.1", + "libp2p": "^1.5.1", "multer": "^1.4.5-lts.1", "multiformats": "^13.1.0", "pino": "^8.20.0", diff --git a/src/helia.ts b/src/helia.ts index 79da64d..55b7fcb 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -3,6 +3,10 @@ import { createHelia } from 'helia' import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' +import { tcp } from '@libp2p/tcp' +import { yamux } from '@chainsafe/libp2p-yamux' +import { noise } from '@chainsafe/libp2p-noise' +import { FaultTolerance } from '@libp2p/interface-transport' export const helia = await createHelia({ datastore, @@ -37,6 +41,12 @@ export const helia = await createHelia({ * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections */ allow: getAllowNodesMultiaddrs() + }, + transports: [tcp()], + streamMuxers: [yamux()], + connectionEncryption: [noise()], + transportManager: { + faultTolerance: FaultTolerance.NO_FATAL } } }) From 800ef0d616d17b52af9edd269a3ea6eb8837d578 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Mon, 13 May 2024 14:56:57 +0400 Subject: [PATCH 034/167] fix: url "api" prefix added, localhost in cors --- config.default.json5 | 4 +-- src/index.ts | 65 +++++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 36b831f..43774ef 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -10,7 +10,7 @@ }, { name: "ipfs3", - multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWGmrZg4iEV8zX3CfiitGakNDKgrDjnphbZSkW2QPW1dbD" + multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWJWvP5JtFawfKENvUybMdM24QrWFVGRkTW2UcwbGwT6h2" } ], storeFolder: '.adm-ipfs', @@ -36,6 +36,6 @@ uploadLimitSizeBytes: 268435456, // 256 Mb. findFileTimeout: 30000, cors: { - originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh'] + originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] } } diff --git a/src/index.ts b/src/index.ts index 668cd5f..688a639 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,7 +93,7 @@ app.get('/', (req, res) => { res.send('IPFS node') }) -app.get('/routing/findProviders/:cid', async (req, res) => { +app.get('/api/routing/findProviders/:cid', async (req, res) => { try { const cid = CID.parse(req.params.cid) @@ -114,7 +114,7 @@ app.get('/routing/findProviders/:cid', async (req, res) => { } }) -app.get('/cron/autopeering', async (req, res) => { +app.get('/api/cron/autopeering', async (req, res) => { const successPeers = await autoPeeringHandler() res.send({ @@ -123,7 +123,7 @@ app.get('/cron/autopeering', async (req, res) => { }) app.get( - '/libp2p/services/ping', + '/api/libp2p/services/ping', async (req: Request, res: Response) => { try { const peerId = peerIdFromString(req.query.peerId || '') @@ -142,7 +142,7 @@ app.get( ) app.get( - '/libp2p/peerStore', + '/api/libp2p/peerStore', async (req: Request, res: Response) => { const peerId = req.query.peerId @@ -176,28 +176,31 @@ app.get( } ) -app.get('/libp2p/peerInfo', async (req: Request, res: Response) => { - const peerId = req.query.peerId +app.get( + '/api/libp2p/peerInfo', + async (req: Request, res: Response) => { + const peerId = req.query.peerId - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [(peer) => peer.id.toString() === peerId], - limit: 10 - }) + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [(peer) => peer.id.toString() === peerId], + limit: 10 + }) - res.send({ - length: peers.length, - peer: peers - }) - } catch (err) { - res.send({ - error: err.message - }) + res.send({ + length: peers.length, + peer: peers + }) + } catch (err) { + res.send({ + error: err.message + }) + } } -}) +) app.get( - '/libp2p/dial', + '/api/libp2p/dial', async (req: Request, res: Response) => { let peerId: PeerId | undefined let multiAddr: Multiaddr | undefined @@ -243,7 +246,7 @@ app.get( } ) -app.get('/libp2p/new-peers/log', async (req, res) => { +app.get('/api/libp2p/new-peers/log', async (req, res) => { logNewPeers = !logNewPeers res.send({ @@ -252,7 +255,7 @@ app.get('/libp2p/new-peers/log', async (req, res) => { }) app.get( - '/libp2p/connections', + '/api/libp2p/connections', async (req: Request, res: Response) => { const peerId = req.query.peerId?.toString() || '' const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) @@ -264,13 +267,13 @@ app.get( } ) -app.get('/libp2p/status', async (req, res) => { +app.get('/api/libp2p/status', async (req, res) => { res.send({ status: helia.libp2p.status }) }) -app.get('/file/:cid', async (req, res) => { +app.get('/api/file/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) const timeoutPromise = new Promise((_, reject) => @@ -304,7 +307,7 @@ app.get('/file/:cid', async (req, res) => { } }) -app.get('/pins', async (req, res) => { +app.get('/api/pins', async (req, res) => { const pins: Pin[] = [] for await (const pin of helia.pins.ls()) { @@ -317,7 +320,7 @@ app.get('/pins', async (req, res) => { }) }) -app.post('/pins/pin/:cid', async (req, res) => { +app.post('/api/pins/pin/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) try { @@ -338,7 +341,7 @@ app.post('/pins/pin/:cid', async (req, res) => { }) }) -app.get('/pins/isPinned/:cid', async (req, res) => { +app.get('/api/pins/isPinned/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) const isPinned = await helia.pins.isPinned(cid) @@ -349,7 +352,7 @@ app.get('/pins/isPinned/:cid', async (req, res) => { }) }) -app.post('/file/upload', upload.array('files', 5), async (req, res) => { +app.post('/api/file/upload', upload.array('files', 5), async (req, res) => { if (!req.files) { res.statusCode = 400 return res.send({ @@ -398,14 +401,14 @@ app.use((err: Error, req: Request, res: Response, next: NextFunction) => { res.status(500).send({ error: 'Internal Server Error. See logs.' }) }) -app.get('/node/health', async (req, res) => { +app.get('/api/node/health', async (req, res) => { res.send({ timestamp: Date.now(), heliaStatus: helia.libp2p.status }) }) -app.get('/node/info', async (req, res) => { +app.get('/api/node/info', async (req, res) => { const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() res.send({ version: packageJson.version, From 02131cae12e8ff9850f84aed1bb73cbb10196088 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Fri, 17 May 2024 00:17:35 +0400 Subject: [PATCH 035/167] fix: renew helia config --- config.default.json5 | 6 +++++- package-lock.json | 1 + package.json | 1 + src/disk-usage.cron.ts | 7 ++++--- src/helia.ts | 30 ++++++++++++++++++++++++------ src/index.ts | 21 +++++++++++---------- src/store.ts | 6 ++++-- 7 files changed, 50 insertions(+), 22 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 43774ef..2364434 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -6,7 +6,7 @@ }, { name: "ipfs2", - multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", + multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWASdR1QteCFY3ZhypNk9QSnSbffg18t46tGR847rMutXD", }, { name: "ipfs3", @@ -17,6 +17,10 @@ logLevel: 'debug', peerDiscovery: { bootstrap: [ + '/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', diff --git a/package-lock.json b/package-lock.json index 06ee983..b9b8b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@libp2p/identify": "^2.0.0", "@libp2p/interface-transport": "^4.0.3", "@libp2p/kad-dht": "^12.0.15", + "@libp2p/mplex": "^10.0.22", "@libp2p/peer-id": "^4.1.1", "@libp2p/peer-id-factory": "^4.1.1", "@libp2p/tcp": "^9.0.24", diff --git a/package.json b/package.json index 9a5bec1..7897975 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", + "@libp2p/mplex": "^10.0.22", "@helia/mfs": "^3.0.6", "@helia/unixfs": "^3.0.6", "@helia/verified-fetch": "^1.3.14", diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index 404f8e6..fd157a0 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -1,4 +1,4 @@ -import { blockstorePath, datastorePath } from './store.js' +import { blockstorePath, heliaDatastorePath, libp2pDatastorePath } from './store.js' import { dirSize, availableStorageSize } from './utils/utils.js' import { CronJob } from 'cron' import { config } from './config.js' @@ -29,9 +29,10 @@ async function scan() { blockstoreSizeMb = blockstoreSize / oneMb } - const datastoreSize = await dirSize(datastorePath) + const datastoreSize = await dirSize(heliaDatastorePath) + const datastoreSizeP2P = await dirSize(libp2pDatastorePath) if (datastoreSize > 0) { - datastoreSizeMb = datastoreSize / oneMb + datastoreSizeMb = (datastoreSize + datastoreSizeP2P) / oneMb } availableSizeInMb = Number((await availableStorageSize()) / BigInt(oneMb)) diff --git a/src/helia.ts b/src/helia.ts index 55b7fcb..9fc81f7 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,17 +1,24 @@ import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' -import { blockstore, datastore } from './store.js' +import { blockstore, heliaDatastore, libp2pDatastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' import { tcp } from '@libp2p/tcp' import { yamux } from '@chainsafe/libp2p-yamux' import { noise } from '@chainsafe/libp2p-noise' import { FaultTolerance } from '@libp2p/interface-transport' +import { mplex } from '@libp2p/mplex' +import { identify } from '@libp2p/identify' +import { gossipsub } from '@chainsafe/libp2p-gossipsub' +import { SignaturePolicy } from '@chainsafe/libp2p-gossipsub/types' +import { kadDHT } from '@libp2p/kad-dht' +import { webRTC } from '@libp2p/webrtc' export const helia = await createHelia({ - datastore, + datastore: heliaDatastore, blockstore, libp2p: { + datastore: libp2pDatastore, peerDiscovery: [ bootstrap({ list: config.peerDiscovery.bootstrap @@ -24,13 +31,13 @@ export const helia = await createHelia({ /** * The total number of connections allowed to be open at one time */ - // maxConnections: 10, + maxConnections: 100, /** * If the number of open connections goes below this number, the node * will try to connect to randomly selected peers from the peer store */ - // minConnections: 5, + minConnections: 0, /** * How many connections can be open but not yet upgraded @@ -42,11 +49,22 @@ export const helia = await createHelia({ */ allow: getAllowNodesMultiaddrs() }, - transports: [tcp()], - streamMuxers: [yamux()], + transports: [tcp(), webRTC()], + streamMuxers: [yamux(), mplex()], connectionEncryption: [noise()], transportManager: { faultTolerance: FaultTolerance.NO_FATAL + }, + services: { + identify: identify(), + pubsub: gossipsub({ + emitSelf: false, // whether the node should emit to self on publish + globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy + }), + dht: kadDHT({ + kBucketSize: 20, + clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) + }) } } }) diff --git a/src/index.ts b/src/index.ts index 688a639..0cdb162 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,25 +43,25 @@ helia.libp2p.getMultiaddrs().forEach((addr) => { helia.libp2p.addEventListener('peer:discovery', (evt) => { const peer = evt.detail - if (logNewPeers) { - pino.logger.info(`Discovered peer: ${peer.id}`) - } + // if (logNewPeers) { + pino.logger.info(`Discovered peer: ${peer.id}`) + // } }) helia.libp2p.addEventListener('peer:connect', (evt) => { const peerId = evt.detail - if (logNewPeers) { - pino.logger.info(`Peer connected: ${peerId}`) - } + // if (logNewPeers) { + pino.logger.info(`Peer connected: ${peerId}`) + // } }) helia.libp2p.addEventListener('peer:disconnect', (evt) => { const peerId = evt.detail - if (logNewPeers) { - pino.logger.info(`Peer disconnected: ${peerId}`) - } + // if (logNewPeers) { + pino.logger.info(`Peer disconnected: ${peerId}`) + // } }) helia.libp2p.addEventListener('start', (event) => { @@ -74,7 +74,8 @@ helia.libp2p.addEventListener('stop', (event) => { pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) -autoPeering.start() +// autoPeering.start() +autoPeeringHandler().catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) diskUsageCron.start() const PORT = config.serverPort diff --git a/src/store.ts b/src/store.ts index c5bdd42..9e60b61 100644 --- a/src/store.ts +++ b/src/store.ts @@ -7,7 +7,9 @@ import { config } from './config.js' const userHomeDir = homedir() export const blockstorePath = join(userHomeDir, config.storeFolder, 'blockstore') -export const datastorePath = join(userHomeDir, config.storeFolder, 'datastore') +export const heliaDatastorePath = join(userHomeDir, config.storeFolder, 'datastore') +export const libp2pDatastorePath = join(userHomeDir, config.storeFolder, 'datastore_p2p') export const blockstore = new FsBlockstore(blockstorePath) -export const datastore = new FsDatastore(datastorePath) +export const heliaDatastore = new FsDatastore(heliaDatastorePath) +export const libp2pDatastore = new FsDatastore(libp2pDatastorePath) From 2f5bd695c0d45ca505b501881da14a0674e2f2e2 Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 17 May 2024 19:37:06 +0100 Subject: [PATCH 036/167] fix(config): fix ipfs2 addr --- config.default.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 2364434..1dac113 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -6,7 +6,7 @@ }, { name: "ipfs2", - multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWASdR1QteCFY3ZhypNk9QSnSbffg18t46tGR847rMutXD", + multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", }, { name: "ipfs3", @@ -20,7 +20,7 @@ '/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', '/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', From e8df5a16bcd0e250a5e95d96ee040f41e69e0eed Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 17 May 2024 23:49:42 +0100 Subject: [PATCH 037/167] fix(/api/file/:cid): fix uncatched error --- src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0cdb162..f2c4a65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -277,11 +277,11 @@ app.get('/api/libp2p/status', async (req, res) => { app.get('/api/file/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) - ) - try { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) + ) + const filePromise = await verifiedFetch(`ipfs://${cid}`, { headers: req.headers as Record }) From 0c7703cf46616270ed6963b1d8108248a9a6a33b Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 17 May 2024 23:53:44 +0100 Subject: [PATCH 038/167] fix(/api/file/upload): disable DHT (fix Query Aborted error) --- src/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index f2c4a65..9e686ec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -382,13 +382,13 @@ app.post('/api/file/upload', upload.array('files', 5), async (req, res) => { } // Tell the network we can provide content for the passed CID - const dht = helia.libp2p.services.dht as KadDHT - await dht.provide(cid) - pino.logger.info(`Provided CID via DHT ${cid}`) - - pino.logger.info(`Routing: Providing ${cid}`) - void helia.routing.provide(cid) - pino.logger.info('Routing: Provide DONE') + // const dht = helia.libp2p.services.dht as KadDHT + // await dht.provide(cid) + // pino.logger.info(`Provided CID via DHT ${cid}`) + // + // pino.logger.info(`Routing: Providing ${cid}`) + // void helia.routing.provide(cid) + // pino.logger.info('Routing: Provide DONE') } res.send({ From 993d26b9d817e4e85c31177d6c0fdc2d1c31ca26 Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 17 May 2024 23:50:02 +0100 Subject: [PATCH 039/167] chore(config): decrease file not found timeout --- config.default.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.default.json5 b/config.default.json5 index 1dac113..8313e3b 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -38,7 +38,7 @@ autoPeeringPeriod: '*/10 * * * * *', diskUsageScanPeriod: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. - findFileTimeout: 30000, + findFileTimeout: 10000, cors: { originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] } From c4a90d7cba6accc82b0a51c2c80f6ce5c6837a81 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sat, 18 May 2024 00:06:46 +0100 Subject: [PATCH 040/167] fix(/api/file/upload): fix uncatched timeout error --- src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9e686ec..d78107e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -281,8 +281,7 @@ app.get('/api/file/:cid', async (req, res) => { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) ) - - const filePromise = await verifiedFetch(`ipfs://${cid}`, { + const filePromise = verifiedFetch(`ipfs://${cid}`, { headers: req.headers as Record }) From 9b306d88e82383091c8e846c676ef8242f4e0647 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sat, 18 May 2024 02:35:30 +0100 Subject: [PATCH 041/167] fix: update nodes multiaddr --- config.default.json5 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 8313e3b..405be7b 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -2,15 +2,15 @@ nodes: [ { name: "ipfs1", - multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWDFkrfLKMt46J5uh77SpNkxhxtSURqDnKY7Bnfr9etSQi" + multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWNFxP2wX6fqRg5ySJucMKyQy1M99jpSP3HazSQ8XA7pw3" }, { name: "ipfs2", - multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWCkyBk2qneNhVJhhNoFat4foTQt877P28Q3VwFHcmYLw1", + multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWEgBWETmFP8NCbYzu343JWSHk5dHBakKFf4tsCsxrrM7F", }, { name: "ipfs3", - multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWJWvP5JtFawfKENvUybMdM24QrWFVGRkTW2UcwbGwT6h2" + multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWB33zCXrSFoRFhMwsj28BmK6UnzEUBNQjPtatuMyYESJg" } ], storeFolder: '.adm-ipfs', From e7170dad14087170cc855a33c218604e0c82021d Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 17:28:19 +0100 Subject: [PATCH 042/167] fix: use single datastore --- src/disk-usage.cron.ts | 7 +++---- src/helia.ts | 6 +++--- src/store.ts | 6 ++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index fd157a0..404f8e6 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -1,4 +1,4 @@ -import { blockstorePath, heliaDatastorePath, libp2pDatastorePath } from './store.js' +import { blockstorePath, datastorePath } from './store.js' import { dirSize, availableStorageSize } from './utils/utils.js' import { CronJob } from 'cron' import { config } from './config.js' @@ -29,10 +29,9 @@ async function scan() { blockstoreSizeMb = blockstoreSize / oneMb } - const datastoreSize = await dirSize(heliaDatastorePath) - const datastoreSizeP2P = await dirSize(libp2pDatastorePath) + const datastoreSize = await dirSize(datastorePath) if (datastoreSize > 0) { - datastoreSizeMb = (datastoreSize + datastoreSizeP2P) / oneMb + datastoreSizeMb = datastoreSize / oneMb } availableSizeInMb = Number((await availableStorageSize()) / BigInt(oneMb)) diff --git a/src/helia.ts b/src/helia.ts index 9fc81f7..d90d4e3 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,6 +1,6 @@ import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' -import { blockstore, heliaDatastore, libp2pDatastore } from './store.js' +import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' import { tcp } from '@libp2p/tcp' @@ -15,10 +15,10 @@ import { kadDHT } from '@libp2p/kad-dht' import { webRTC } from '@libp2p/webrtc' export const helia = await createHelia({ - datastore: heliaDatastore, + datastore, blockstore, libp2p: { - datastore: libp2pDatastore, + datastore, peerDiscovery: [ bootstrap({ list: config.peerDiscovery.bootstrap diff --git a/src/store.ts b/src/store.ts index 9e60b61..c5bdd42 100644 --- a/src/store.ts +++ b/src/store.ts @@ -7,9 +7,7 @@ import { config } from './config.js' const userHomeDir = homedir() export const blockstorePath = join(userHomeDir, config.storeFolder, 'blockstore') -export const heliaDatastorePath = join(userHomeDir, config.storeFolder, 'datastore') -export const libp2pDatastorePath = join(userHomeDir, config.storeFolder, 'datastore_p2p') +export const datastorePath = join(userHomeDir, config.storeFolder, 'datastore') export const blockstore = new FsBlockstore(blockstorePath) -export const heliaDatastore = new FsDatastore(heliaDatastorePath) -export const libp2pDatastore = new FsDatastore(libp2pDatastorePath) +export const datastore = new FsDatastore(datastorePath) From 257efc0951e5ac0a6924d33c5520ceade934c764 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 17:32:41 +0100 Subject: [PATCH 043/167] chore: increase find file timeout --- config.default.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.default.json5 b/config.default.json5 index 405be7b..ecae7b2 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -38,7 +38,7 @@ autoPeeringPeriod: '*/10 * * * * *', diskUsageScanPeriod: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. - findFileTimeout: 10000, + findFileTimeout: 20000, cors: { originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] } From 7d20a70bd9d3d9030c054ba555534998ed5881b8 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 18:23:54 +0100 Subject: [PATCH 044/167] chore: old way to find content by CID --- src/index.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index d78107e..078e397 100644 --- a/src/index.ts +++ b/src/index.ts @@ -281,18 +281,28 @@ app.get('/api/file/:cid', async (req, res) => { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) ) - const filePromise = verifiedFetch(`ipfs://${cid}`, { - headers: req.headers as Record - }) + // const filePromise = verifiedFetch(`ipfs://${cid}`, { + // headers: req.headers as Record + // }) + + const filePromise = (async () => { + const file = ifs.cat(cid) + const chunks = [] + for await (const chunk of file) { + chunks.push(chunk) + } + return Buffer.concat(chunks) + })() const result = await Promise.race([filePromise, timeoutPromise]) - const data = result.body + const data = result if (!data) { throw new Error('Empty data') } res.set('Content-Type', 'application/octet-stream') - const responseStream = Writable.toWeb(res) - await data.pipeTo(responseStream) + // const responseStream = Writable.toWeb(res) + + res.send(result) } catch (error) { pino.logger.error(error) if (error.message === 'Operation timed out') { From e8973ac2cedbbe8fd8153e2b51767445ebc8a5dc Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 18:51:38 +0100 Subject: [PATCH 045/167] chore: use default helia config --- src/helia.ts | 55 +++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/src/helia.ts b/src/helia.ts index d90d4e3..e52633e 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,5 +1,6 @@ import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' +import { createLibp2p } from 'libp2p' import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' @@ -19,52 +20,24 @@ export const helia = await createHelia({ blockstore, libp2p: { datastore, + addresses: { + listen: ['/ip4/0.0.0.0/tcp/4001'] + }, + transports: [tcp()], + connectionEncryption: [noise()], + streamMuxers: [yamux()], peerDiscovery: [ bootstrap({ - list: config.peerDiscovery.bootstrap + list: [ + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' + ] }) ], - addresses: { - listen: config.peerDiscovery.listen - }, - connectionManager: { - /** - * The total number of connections allowed to be open at one time - */ - maxConnections: 100, - - /** - * If the number of open connections goes below this number, the node - * will try to connect to randomly selected peers from the peer store - */ - minConnections: 0, - - /** - * How many connections can be open but not yet upgraded - */ - // maxIncomingPendingConnections: 10, - - /** - * A list of multiaddrs that will always be allowed (except if they are in the deny list) to open connections to this node even if we've reached maxConnections - */ - allow: getAllowNodesMultiaddrs() - }, - transports: [tcp(), webRTC()], - streamMuxers: [yamux(), mplex()], - connectionEncryption: [noise()], - transportManager: { - faultTolerance: FaultTolerance.NO_FATAL - }, services: { - identify: identify(), - pubsub: gossipsub({ - emitSelf: false, // whether the node should emit to self on publish - globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy - }), - dht: kadDHT({ - kBucketSize: 20, - clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) - }) + identify: identify() } } }) From 2588f9c020cbbd9a8d01482f442411e6bce8678a Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 18:54:17 +0100 Subject: [PATCH 046/167] chore: update multiaddresses --- config.default.json5 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index ecae7b2..93389f2 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -2,15 +2,15 @@ nodes: [ { name: "ipfs1", - multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWNFxP2wX6fqRg5ySJucMKyQy1M99jpSP3HazSQ8XA7pw3" + multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq" }, { name: "ipfs2", - multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWEgBWETmFP8NCbYzu343JWSHk5dHBakKFf4tsCsxrrM7F", + multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWJw99nqrQ2L2joFuGCF9EN9EyF8ZrvGr1odQ61HoPrbXd", }, { name: "ipfs3", - multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWB33zCXrSFoRFhMwsj28BmK6UnzEUBNQjPtatuMyYESJg" + multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWHTvrWSTCXZFR2vS8Cbw17b5peK2aSBr8CmkyA96CDqiU" } ], storeFolder: '.adm-ipfs', From c676361374b0b072bf0e2724d4f99bbaae8f890c Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 19:13:50 +0100 Subject: [PATCH 047/167] chore(p2p): add allowed list --- src/helia.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/helia.ts b/src/helia.ts index e52633e..0cdfc42 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -38,6 +38,10 @@ export const helia = await createHelia({ ], services: { identify: identify() + }, + connectionManager: { + maxConnections: 100, + allow: getAllowNodesMultiaddrs() } } }) From 03783000010bf543c1366eef2d823db040dda745 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 19:17:39 +0100 Subject: [PATCH 048/167] chore(p2p): update peer discovery list --- config.default.json5 | 8 +------- src/helia.ts | 7 +------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 93389f2..53e3a04 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -17,16 +17,10 @@ logLevel: 'debug', peerDiscovery: { bootstrap: [ - '/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', - '/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', - '/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', - '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', - '/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ' + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' ], listen: [ '/ip4/0.0.0.0/tcp/4001', diff --git a/src/helia.ts b/src/helia.ts index 0cdfc42..d755e49 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -28,12 +28,7 @@ export const helia = await createHelia({ streamMuxers: [yamux()], peerDiscovery: [ bootstrap({ - list: [ - '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' - ] + list: config.peerDiscovery.bootstrap }) ], services: { From 345f9f678d5b08be88be5154acecd87d190513b1 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 19:20:28 +0100 Subject: [PATCH 049/167] chore(p2p): disable autopeering --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 078e397..22b3a30 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,7 +75,7 @@ helia.libp2p.addEventListener('stop', (event) => { pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) // autoPeering.start() -autoPeeringHandler().catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) +// autoPeeringHandler().catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) diskUsageCron.start() const PORT = config.serverPort From 502b3a426c1ead650fd9bb1537b016fd2ed284ec Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 23:24:50 +0100 Subject: [PATCH 050/167] refactor: split endpoints into routers --- src/api/debug.ts | 22 +++ src/api/file.ts | 109 ++++++++++++++ src/api/helia.ts | 75 ++++++++++ src/api/index.ts | 7 + src/api/libp2p.ts | 177 ++++++++++++++++++++++ src/api/node.ts | 30 ++++ src/helia.ts | 16 +- src/index.ts | 372 +--------------------------------------------- src/multer.ts | 13 ++ 9 files changed, 445 insertions(+), 376 deletions(-) create mode 100644 src/api/debug.ts create mode 100644 src/api/file.ts create mode 100644 src/api/helia.ts create mode 100644 src/api/index.ts create mode 100644 src/api/libp2p.ts create mode 100644 src/api/node.ts create mode 100644 src/multer.ts diff --git a/src/api/debug.ts b/src/api/debug.ts new file mode 100644 index 0000000..663390f --- /dev/null +++ b/src/api/debug.ts @@ -0,0 +1,22 @@ +import { Router } from 'express' +import { pino } from '../utils/logger.js' +import { autoPeeringHandler } from '../auto-peering.cron.js' + +const router = Router() + +router.get('/debug/autopeering', async (req, res) => { + try { + const successPeers = await autoPeeringHandler() + + res.send({ + peeredSuccessfullyTo: successPeers + }) + } catch (err) { + pino.logger.error(err) + res.send({ + error: err.message + }) + } +}) + +export default router diff --git a/src/api/file.ts b/src/api/file.ts new file mode 100644 index 0000000..4686fb6 --- /dev/null +++ b/src/api/file.ts @@ -0,0 +1,109 @@ +import { Router } from 'express' +import { CID } from 'multiformats/cid' +import { multerStorage } from '../multer.js' +import { config } from '../config.js' +import { helia, ifs } from '../helia.js' +import { pino } from '../utils/logger.js' +import { UnixFsMulterFile } from '../utils/types.js' +import { flatFiles } from '../utils/utils.js' + +const router = Router() + +router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => { + if (!req.files) { + res.statusCode = 400 + return res.send({ + error: 'No file uploaded' + }) + } + + try { + const files = flatFiles(req.files as UnixFsMulterFile[]) + pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) + + const cids: CID[] = [] + for (const file of files) { + pino.logger.info(`Adding ${file.originalname} to IPFS`) + + const { cid } = file + pino.logger.info(`Successfully added file ${cid}`) + cids.push(cid) + + const isPinned = await helia.pins.isPinned(cid) + if (isPinned) { + pino.logger.info(`File already pinned ${cid}`) + } else { + // Pin the file + for await (const pinned of helia.pins.add(cid)) { + pino.logger.info(`Filed pinned: ${pinned}`) + } + } + + // Tell the network we can provide content for the passed CID + // const dht = helia.libp2p.services.dht as KadDHT + // await dht.provide(cid) + // pino.logger.info(`Provided CID via DHT ${cid}`) + // + // pino.logger.info(`Routing: Providing ${cid}`) + // void helia.routing.provide(cid) + // pino.logger.info('Routing: Provide DONE') + } + + res.send({ + filesNames: files.map((file) => file.originalname), + cids: cids.map((cid) => cid.toString()) + }) + } catch (err) { + pino.logger.error(err) + + res.status(400) + res.send({ + error: err.message + }) + } +}) + +router.get('/file/:cid', async (req, res) => { + const cid = CID.parse(req.params.cid) + + try { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) + ) + // const filePromise = verifiedFetch(`ipfs://${cid}`, { + // headers: req.headers as Record + // }) + + const filePromise = (async () => { + const file = ifs.cat(cid) + const chunks = [] + for await (const chunk of file) { + chunks.push(chunk) + } + return Buffer.concat(chunks) + })() + + const result = await Promise.race([filePromise, timeoutPromise]) + const data = result + if (!data) { + throw new Error('Empty data') + } + res.set('Content-Type', 'application/octet-stream') + // const responseStream = Writable.toWeb(res) + + res.send(result) + } catch (error) { + pino.logger.error(error) + if (error.message === 'Operation timed out') { + res.status(408).send({ + error: 'Can not find requested CID. Operation timed out.' + }) + } else { + res.status(500).send({ + error: 'Internal Server Error. Check the logs for details.' + }) + } + } +}) + +export default router diff --git a/src/api/helia.ts b/src/api/helia.ts new file mode 100644 index 0000000..8a11b43 --- /dev/null +++ b/src/api/helia.ts @@ -0,0 +1,75 @@ +import { Router } from 'express' +import { Pin } from 'helia' +import { CID } from 'multiformats/cid' +import { helia } from '../helia.js' +import { pino } from '../utils/logger.js' + +const router = Router() + +router.get('/pins', async (req, res) => { + const pins: Pin[] = [] + + for await (const pin of helia.pins.ls()) { + pino.logger.info('PIN LS', pin) + pins.push(pin) + } + + res.send({ + pins + }) +}) + +router.post('/pin/:cid', async (req, res) => { + const cid = CID.parse(req.params.cid) + + try { + for await (const pin of helia.pins.add(cid)) { + pino.logger.info('PINNED', pin) + } + } catch (err) { + pino.logger.error(`Error: ${err.message}`) + res.statusCode = 500 + return res.send({ + error: err.message + }) + } + + res.send({ + pinned: true, + cid: cid.toString() + }) +}) + +router.get('/pins/isPinned/:cid', async (req, res) => { + const cid = CID.parse(req.params.cid) + + const isPinned = await helia.pins.isPinned(cid) + + res.send({ + cid: cid.toString(), + isPinned + }) +}) + +router.get('/routing/findProviders/:cid', async (req, res) => { + try { + const cid = CID.parse(req.params.cid) + + const providers: string[] = [] + for await (const provider of helia.routing.findProviders(cid)) { + pino.logger.info(`Found provider of CID:${cid.toString()}, PeerId:${provider.id.toString()}`) + providers.push(provider.id.toString()) + } + + res.send({ + providers + }) + } catch (err) { + res.send({ + error: err.message + }) + pino.logger.error(err) + } +}) + +export default router diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..18d1d53 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,7 @@ +import file from './file.js' +import node from './node.js' +import helia from './helia.js' +import libp2p from './libp2p.js' +import debug from './debug.js' + +export { file, node, helia, libp2p, debug } diff --git a/src/api/libp2p.ts b/src/api/libp2p.ts new file mode 100644 index 0000000..fb9b8db --- /dev/null +++ b/src/api/libp2p.ts @@ -0,0 +1,177 @@ +import { PeerId } from '@libp2p/interface' +import { peerIdFromString } from '@libp2p/peer-id' +import type { PingService } from '@libp2p/ping' +import { multiaddr, Multiaddr } from '@multiformats/multiaddr' +import { Request, Response, Router } from 'express' +import { PeerIdDto } from '../dto/peer-id.dto.js' +import { helia } from '../helia.js' +import { pino } from '../utils/logger.js' + +const router = Router() + +router.get( + '/libp2p/services/ping', + async (req: Request, res: Response) => { + try { + const peerId = peerIdFromString(req.query.peerId || '') + const pingService = helia.libp2p.services.ping as PingService + const pong = await pingService.ping(peerId) + + res.send({ + pong + }) + } catch (err) { + res.send({ + error: err.message + }) + } + } +) + +router.get( + '/libp2p/peerStore', + async (req: Request, res: Response) => { + const peerId = req.query.peerId + + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [ + (peer) => { + if (!peerId) { + return true + } + + return peer.id.toString() === peerId + } + ], + limit: 10 + }) + + res.send({ + length: peers.length, + peers: peers.map((peer) => { + return { + id: peer.id.toString() + } + }) + }) + } catch (err) { + res.send({ + error: err.message + }) + } + } +) + +router.get( + '/libp2p/peerInfo', + async (req: Request, res: Response) => { + const peerId = req.query.peerId + + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [(peer) => peer.id.toString() === peerId], + limit: 10 + }) + + res.send({ + length: peers.length, + peer: peers + }) + } catch (err) { + res.send({ + error: err.message + }) + } + } +) + +router.get( + '/libp2p/dial', + async (req: Request, res: Response) => { + let peerId: PeerId | undefined + let multiAddr: Multiaddr | undefined + try { + if (req.query.peerId) { + peerId = peerIdFromString(req.query.peerId || '') + } + + if (req.query.multiAddr) { + multiAddr = multiaddr(req.query.multiAddr || '') + } + } catch (err) { + pino.logger.error('Invalid peer ID:' + err.message) + res.send({ + success: false, + error: 'Invalid peer ID' + }) + return + } + + if (multiAddr) { + pino.logger.info(`Peering by multiAddress: ${multiAddr}`) + } else if (peerId) { + pino.logger.info(`Peering by PeerID: ${peerId}`) + } + + try { + const peer = multiAddr || peerId + if (!peer) { + throw new Error('Empty peerId and MultiAddr') + } + const connection = await helia.libp2p.dial(peer) + res.send({ success: true, connection }) + } catch (err) { + pino.logger.warn(`Cannot dial peer: ${err.message}`) + + res.send({ + success: false, + error: err.message + }) + pino.logger.error(err) + } + } +) + +router.get( + '/libp2p/connections', + async (req: Request, res: Response) => { + try { + const peerId = req.query.peerId?.toString() || '' + const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) + + res.send({ + length: connections.length, + connections + }) + } catch (err) { + pino.logger.error(err) + res.status(400) + res.send({ + error: err.message + }) + } + } +) + +router.get('/libp2p/status', async (req, res) => { + res.send({ + status: helia.libp2p.status + }) +}) + +router.get('/libp2p/peers', (req, res) => { + try { + const peers = helia.libp2p.getPeers() + + res.send({ peers }) + } catch (err) { + pino.logger.error(err) + res.status(400) + res.send({ + error: err.message + }) + } +}) + +export default router diff --git a/src/api/node.ts b/src/api/node.ts new file mode 100644 index 0000000..4549f26 --- /dev/null +++ b/src/api/node.ts @@ -0,0 +1,30 @@ +import { Router } from 'express' +import { packageJson } from '../config.js' +import { getDiskUsageStats } from '../disk-usage.cron.js' +import { helia } from '../helia.js' + +const router = Router() + +router.get('/node/health', async (req, res) => { + res.send({ + timestamp: Date.now(), + heliaStatus: helia.libp2p.status + }) +}) + +router.get('/node/info', async (req, res) => { + const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() + + res.send({ + version: packageJson.version, + timestamp: Date.now(), + heliaStatus: helia.libp2p.status, + peerId: helia.libp2p.peerId, + multiAddresses: helia.libp2p.getMultiaddrs(), + blockstoreSizeMb, + datastoreSizeMb, + availableSizeInMb + }) +}) + +export default router diff --git a/src/helia.ts b/src/helia.ts index d755e49..2778561 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,19 +1,13 @@ +import { unixfs } from '@helia/unixfs' import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' -import { createLibp2p } from 'libp2p' -import { blockstore, datastore } from './store.js' -import { getAllowNodesMultiaddrs } from './utils/utils.js' -import { config } from './config.js' import { tcp } from '@libp2p/tcp' import { yamux } from '@chainsafe/libp2p-yamux' import { noise } from '@chainsafe/libp2p-noise' -import { FaultTolerance } from '@libp2p/interface-transport' -import { mplex } from '@libp2p/mplex' import { identify } from '@libp2p/identify' -import { gossipsub } from '@chainsafe/libp2p-gossipsub' -import { SignaturePolicy } from '@chainsafe/libp2p-gossipsub/types' -import { kadDHT } from '@libp2p/kad-dht' -import { webRTC } from '@libp2p/webrtc' +import { blockstore, datastore } from './store.js' +import { getAllowNodesMultiaddrs } from './utils/utils.js' +import { config } from './config.js' export const helia = await createHelia({ datastore, @@ -40,3 +34,5 @@ export const helia = await createHelia({ } } }) + +export const ifs = unixfs(helia) diff --git a/src/index.ts b/src/index.ts index 22b3a30..e813f52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,16 @@ -import { unixfs } from '@helia/unixfs' -import { peerIdFromString } from '@libp2p/peer-id' -import { CID, Version } from 'multiformats/cid' import express, { NextFunction } from 'express' -import multer from 'multer' -import { Multiaddr, multiaddr } from '@multiformats/multiaddr' -import { autoPeeringHandler, autoPeering } from './auto-peering.cron.js' import { helia } from './helia.js' -import { Pin } from 'helia' -import { flatFiles } from './utils/utils.js' import { Request, Response } from 'express' -import { PeerIdDto } from './dto/peer-id.dto.js' import { createVerifiedFetch } from '@helia/verified-fetch' import { pino } from './utils/logger.js' -import type { PingService } from '@libp2p/ping' -import { KadDHT } from '@libp2p/kad-dht' -import { PeerId } from '@libp2p/interface' -import { Writable } from 'node:stream' -import { UnixfsMulterStorage } from './utils/unixfs-multer.storage.js' -import { UnixFsMulterFile } from './utils/types.js' import { config, configFileName, packageJson } from './config.js' import { getDiskUsageStats, diskUsageCron } from './disk-usage.cron.js' import cors from 'cors' +import * as routers from './api/index.js' pino.logger.info(`Using config file: ${configFileName}`) -const ifs = unixfs(helia) const verifiedFetch = await createVerifiedFetch(helia) -const upload = multer({ - storage: new UnixfsMulterStorage({ - unixfs: ifs, - destination: (req, file) => '/', - filename: (req, file) => file.originalname - }), - limits: { fileSize: config.uploadLimitSizeBytes } -}) - -let logNewPeers = false helia.libp2p.getMultiaddrs().forEach((addr) => { pino.logger.info(`Listening on ${addr.toString()}`) @@ -43,25 +18,17 @@ helia.libp2p.getMultiaddrs().forEach((addr) => { helia.libp2p.addEventListener('peer:discovery', (evt) => { const peer = evt.detail - // if (logNewPeers) { pino.logger.info(`Discovered peer: ${peer.id}`) - // } }) helia.libp2p.addEventListener('peer:connect', (evt) => { const peerId = evt.detail - - // if (logNewPeers) { pino.logger.info(`Peer connected: ${peerId}`) - // } }) helia.libp2p.addEventListener('peer:disconnect', (evt) => { const peerId = evt.detail - - // if (logNewPeers) { pino.logger.info(`Peer disconnected: ${peerId}`) - // } }) helia.libp2p.addEventListener('start', (event) => { @@ -94,344 +61,17 @@ app.get('/', (req, res) => { res.send('IPFS node') }) -app.get('/api/routing/findProviders/:cid', async (req, res) => { - try { - const cid = CID.parse(req.params.cid) - - const providers: string[] = [] - for await (const provider of helia.routing.findProviders(cid)) { - pino.logger.info(`Found provider of CID:${cid.toString()}, PeerId:${provider.id.toString()}`) - providers.push(provider.id.toString()) - } - - res.send({ - providers - }) - } catch (err) { - res.send({ - error: err.message - }) - pino.logger.error(err) - } -}) - -app.get('/api/cron/autopeering', async (req, res) => { - const successPeers = await autoPeeringHandler() - - res.send({ - peeredSuccessfullyTo: successPeers - }) -}) - -app.get( - '/api/libp2p/services/ping', - async (req: Request, res: Response) => { - try { - const peerId = peerIdFromString(req.query.peerId || '') - const pingService = helia.libp2p.services.ping as PingService - const pong = await pingService.ping(peerId) - - res.send({ - pong - }) - } catch (err) { - res.send({ - error: err.message - }) - } - } -) - -app.get( - '/api/libp2p/peerStore', - async (req: Request, res: Response) => { - const peerId = req.query.peerId - - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [ - (peer) => { - if (!peerId) { - return true - } - - return peer.id.toString() === peerId - } - ], - limit: 10 - }) - - res.send({ - length: peers.length, - peers: peers.map((peer) => { - return { - id: peer.id.toString() - } - }) - }) - } catch (err) { - res.send({ - error: err.message - }) - } - } -) - -app.get( - '/api/libp2p/peerInfo', - async (req: Request, res: Response) => { - const peerId = req.query.peerId - - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [(peer) => peer.id.toString() === peerId], - limit: 10 - }) - - res.send({ - length: peers.length, - peer: peers - }) - } catch (err) { - res.send({ - error: err.message - }) - } - } -) - -app.get( - '/api/libp2p/dial', - async (req: Request, res: Response) => { - let peerId: PeerId | undefined - let multiAddr: Multiaddr | undefined - try { - if (req.query.peerId) { - peerId = peerIdFromString(req.query.peerId || '') - } - - if (req.query.multiAddr) { - multiAddr = multiaddr(req.query.multiAddr || '') - } - } catch (err) { - pino.logger.error('Invalid peer ID:' + err.message) - res.send({ - success: false, - error: 'Invalid peer ID' - }) - return - } - - if (multiAddr) { - pino.logger.info(`Peering by multiAddress: ${multiAddr}`) - } else if (peerId) { - pino.logger.info(`Peering by PeerID: ${peerId}`) - } - - try { - const peer = multiAddr || peerId - if (!peer) { - throw new Error('Empty peerId and MultiAddr') - } - const connection = await helia.libp2p.dial(peer) - res.send({ success: true, connection }) - } catch (err) { - pino.logger.warn(`Cannot dial peer: ${err.message}`) - - res.send({ - success: false, - error: err.message - }) - pino.logger.error(err) - } - } -) - -app.get('/api/libp2p/new-peers/log', async (req, res) => { - logNewPeers = !logNewPeers - - res.send({ - logNewPeers - }) -}) - -app.get( - '/api/libp2p/connections', - async (req: Request, res: Response) => { - const peerId = req.query.peerId?.toString() || '' - const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) - - res.send({ - length: connections.length, - connections - }) - } -) - -app.get('/api/libp2p/status', async (req, res) => { - res.send({ - status: helia.libp2p.status - }) -}) - -app.get('/api/file/:cid', async (req, res) => { - const cid = CID.parse(req.params.cid) - - try { - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) - ) - // const filePromise = verifiedFetch(`ipfs://${cid}`, { - // headers: req.headers as Record - // }) - - const filePromise = (async () => { - const file = ifs.cat(cid) - const chunks = [] - for await (const chunk of file) { - chunks.push(chunk) - } - return Buffer.concat(chunks) - })() - - const result = await Promise.race([filePromise, timeoutPromise]) - const data = result - if (!data) { - throw new Error('Empty data') - } - res.set('Content-Type', 'application/octet-stream') - // const responseStream = Writable.toWeb(res) - - res.send(result) - } catch (error) { - pino.logger.error(error) - if (error.message === 'Operation timed out') { - res.status(408).send({ - error: 'Can not find requested CID. Operation timed out.' - }) - } else { - res.status(500).send({ - error: 'Internal Server Error. Check the logs for details.' - }) - } - } -}) - -app.get('/api/pins', async (req, res) => { - const pins: Pin[] = [] - - for await (const pin of helia.pins.ls()) { - pino.logger.info('PIN LS', pin) - pins.push(pin) - } - - res.send({ - pins - }) -}) - -app.post('/api/pins/pin/:cid', async (req, res) => { - const cid = CID.parse(req.params.cid) - - try { - for await (const pin of helia.pins.add(cid)) { - pino.logger.info('PINNED', pin) - } - } catch (err) { - pino.logger.error(`Error: ${err.message}`) - res.statusCode = 500 - return res.send({ - error: err.message - }) - } - - res.send({ - pinned: true, - cid: cid.toString() - }) -}) - -app.get('/api/pins/isPinned/:cid', async (req, res) => { - const cid = CID.parse(req.params.cid) - - const isPinned = await helia.pins.isPinned(cid) - - res.send({ - cid: cid.toString(), - isPinned - }) -}) - -app.post('/api/file/upload', upload.array('files', 5), async (req, res) => { - if (!req.files) { - res.statusCode = 400 - return res.send({ - error: 'No file uploaded' - }) - } - const files = flatFiles(req.files as UnixFsMulterFile[]) - pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) - - const cids: CID[] = [] - for (const file of files) { - pino.logger.info(`Adding ${file.originalname} to IPFS`) - - const { cid } = file - pino.logger.info(`Successfully added file ${cid}`) - cids.push(cid) - - const isPinned = await helia.pins.isPinned(cid) - if (isPinned) { - pino.logger.info(`File already pinned ${cid}`) - } else { - // Pin the file - for await (const pinned of helia.pins.add(cid)) { - pino.logger.info(`Filed pinned: ${pinned}`) - } - } - - // Tell the network we can provide content for the passed CID - // const dht = helia.libp2p.services.dht as KadDHT - // await dht.provide(cid) - // pino.logger.info(`Provided CID via DHT ${cid}`) - // - // pino.logger.info(`Routing: Providing ${cid}`) - // void helia.routing.provide(cid) - // pino.logger.info('Routing: Provide DONE') - } - - res.send({ - filesNames: files.map((file) => file.originalname), - cids: cids.map((cid) => cid.toString()) - }) -}) +app.use('/api', routers.file) +app.use('/api', routers.node) +app.use('/api', routers.helia) +app.use('/api', routers.libp2p) +app.use('/api', routers.debug) app.use((err: Error, req: Request, res: Response, next: NextFunction) => { pino.logger.error(`${err.message}\n${err.stack}`) res.status(500).send({ error: 'Internal Server Error. See logs.' }) }) -app.get('/api/node/health', async (req, res) => { - res.send({ - timestamp: Date.now(), - heliaStatus: helia.libp2p.status - }) -}) - -app.get('/api/node/info', async (req, res) => { - const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() - res.send({ - version: packageJson.version, - timestamp: Date.now(), - heliaStatus: helia.libp2p.status, - peerId: helia.libp2p.peerId, - multiAddresses: helia.libp2p.getMultiaddrs(), - blockstoreSizeMb, - datastoreSizeMb, - availableSizeInMb - }) -}) - app.listen(PORT, () => { pino.logger.info(`Server is running on http://localhost:${PORT}`) }) diff --git a/src/multer.ts b/src/multer.ts new file mode 100644 index 0000000..860761c --- /dev/null +++ b/src/multer.ts @@ -0,0 +1,13 @@ +import multer from 'multer' +import { ifs } from './helia.js' +import { config } from './config.js' +import { UnixfsMulterStorage } from './utils/unixfs-multer.storage.js' + +export const multerStorage = multer({ + storage: new UnixfsMulterStorage({ + unixfs: ifs, + destination: (req, file) => '/', + filename: (req, file) => file.originalname + }), + limits: { fileSize: config.uploadLimitSizeBytes } +}) From 0ab2fc0153dcd8d764e421020fc7299f6f76fdd9 Mon Sep 17 00:00:00 2001 From: bludnic Date: Sun, 19 May 2024 23:43:27 +0100 Subject: [PATCH 051/167] chore(helia): use verifiedFetch --- src/api/file.ts | 25 ++++++++----------------- src/helia.ts | 3 +++ src/index.ts | 3 --- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index 4686fb6..d68eb77 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -1,8 +1,9 @@ import { Router } from 'express' import { CID } from 'multiformats/cid' +import { Writable } from 'node:stream' import { multerStorage } from '../multer.js' import { config } from '../config.js' -import { helia, ifs } from '../helia.js' +import { helia, verifiedFetch } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' @@ -70,28 +71,18 @@ router.get('/file/:cid', async (req, res) => { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) ) - // const filePromise = verifiedFetch(`ipfs://${cid}`, { - // headers: req.headers as Record - // }) - - const filePromise = (async () => { - const file = ifs.cat(cid) - const chunks = [] - for await (const chunk of file) { - chunks.push(chunk) - } - return Buffer.concat(chunks) - })() + const filePromise = verifiedFetch(`ipfs://${cid}`, { + headers: req.headers as Record + }) const result = await Promise.race([filePromise, timeoutPromise]) - const data = result + const data = result.body if (!data) { throw new Error('Empty data') } res.set('Content-Type', 'application/octet-stream') - // const responseStream = Writable.toWeb(res) - - res.send(result) + const responseStream = Writable.toWeb(res) + await data.pipeTo(responseStream) } catch (error) { pino.logger.error(error) if (error.message === 'Operation timed out') { diff --git a/src/helia.ts b/src/helia.ts index 2778561..1406559 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,4 +1,5 @@ import { unixfs } from '@helia/unixfs' +import { createVerifiedFetch } from '@helia/verified-fetch' import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' import { tcp } from '@libp2p/tcp' @@ -36,3 +37,5 @@ export const helia = await createHelia({ }) export const ifs = unixfs(helia) + +export const verifiedFetch = await createVerifiedFetch(helia) diff --git a/src/index.ts b/src/index.ts index e813f52..0b56740 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ import express, { NextFunction } from 'express' import { helia } from './helia.js' import { Request, Response } from 'express' -import { createVerifiedFetch } from '@helia/verified-fetch' import { pino } from './utils/logger.js' import { config, configFileName, packageJson } from './config.js' import { getDiskUsageStats, diskUsageCron } from './disk-usage.cron.js' @@ -10,8 +9,6 @@ import * as routers from './api/index.js' pino.logger.info(`Using config file: ${configFileName}`) -const verifiedFetch = await createVerifiedFetch(helia) - helia.libp2p.getMultiaddrs().forEach((addr) => { pino.logger.info(`Listening on ${addr.toString()}`) }) From cfdb0033f74ea8dba584b160575f2057a25fa1eb Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 20 May 2024 00:21:48 +0100 Subject: [PATCH 052/167] chore(/file/:cid): use ReadableStream with AbortController --- src/api/file.ts | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index d68eb77..54bb7e1 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -1,9 +1,9 @@ import { Router } from 'express' import { CID } from 'multiformats/cid' -import { Writable } from 'node:stream' +import { Readable } from 'node:stream' import { multerStorage } from '../multer.js' import { config } from '../config.js' -import { helia, verifiedFetch } from '../helia.js' +import { helia, ifs } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' @@ -68,21 +68,40 @@ router.get('/file/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) try { - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Operation timed out')), config.findFileTimeout) - ) - const filePromise = verifiedFetch(`ipfs://${cid}`, { - headers: req.headers as Record + let streamStarted = false + + const abortController = new AbortController() + const stream = new Readable({ + async read() { + try { + for await (const chunk of ifs.cat(cid, { signal: abortController.signal })) { + this.push(chunk) + } + this.push(null) // Signal the end of the stream + } catch (err) { + this.destroy(err) + } + } }) - const result = await Promise.race([filePromise, timeoutPromise]) - const data = result.body - if (!data) { - throw new Error('Empty data') - } - res.set('Content-Type', 'application/octet-stream') - const responseStream = Writable.toWeb(res) - await data.pipeTo(responseStream) + stream.on('data', () => { + res.set('Content-Type', 'application/octet-stream') + streamStarted = true + }) + stream.on('error', (err) => { + pino.logger.error(err) + res.status(408).send({ + error: 'Can not find requested CID. Operation timed out.' + }) + }) + + setTimeout(() => { + if (!streamStarted) { + abortController.abort() + } + }, config.findFileTimeout) + + stream.pipe(res) } catch (error) { pino.logger.error(error) if (error.message === 'Operation timed out') { From 5203cbfd890940770b72565ffd9de09c588ee574 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 20 May 2024 00:26:59 +0100 Subject: [PATCH 053/167] fix(/file/:cid): catch invalid CID errors --- src/api/file.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index 54bb7e1..1fe584c 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -65,9 +65,9 @@ router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => }) router.get('/file/:cid', async (req, res) => { - const cid = CID.parse(req.params.cid) - try { + const cid = CID.parse(req.params.cid) + let streamStarted = false const abortController = new AbortController() From ee9c1bcbc249d1c481a557bd311fd1e973c7b987 Mon Sep 17 00:00:00 2001 From: bludnic Date: Mon, 20 May 2024 00:30:05 +0100 Subject: [PATCH 054/167] fix(/file/:cid): fix error messages --- src/api/file.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index 1fe584c..eaf012e 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -91,7 +91,7 @@ router.get('/file/:cid', async (req, res) => { stream.on('error', (err) => { pino.logger.error(err) res.status(408).send({ - error: 'Can not find requested CID. Operation timed out.' + error: 'Cannot find requested CID' }) }) @@ -104,15 +104,9 @@ router.get('/file/:cid', async (req, res) => { stream.pipe(res) } catch (error) { pino.logger.error(error) - if (error.message === 'Operation timed out') { - res.status(408).send({ - error: 'Can not find requested CID. Operation timed out.' - }) - } else { - res.status(500).send({ - error: 'Internal Server Error. Check the logs for details.' - }) - } + res.status(500).send({ + error: 'Internal Server Error. Check the logs for details.' + }) } }) From 4f8c1ae37ce2180007c1c9a2066343a69b15cc17 Mon Sep 17 00:00:00 2001 From: bludnic Date: Tue, 21 May 2024 02:13:26 +0100 Subject: [PATCH 055/167] fix(/file/:cid): fix set headers multiple times --- src/api/file.ts | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index eaf012e..3fb39ad 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -69,38 +69,34 @@ router.get('/file/:cid', async (req, res) => { const cid = CID.parse(req.params.cid) let streamStarted = false - const abortController = new AbortController() - const stream = new Readable({ - async read() { - try { - for await (const chunk of ifs.cat(cid, { signal: abortController.signal })) { - this.push(chunk) - } - this.push(null) // Signal the end of the stream - } catch (err) { - this.destroy(err) - } + const timeout = setTimeout(() => { + if (!streamStarted) { + abortController.abort(new Error('Cannot find requested CID. Request timed out.')) } - }) + }, config.findFileTimeout) + + const stream = Readable.from( + ifs.cat(cid, { + signal: abortController.signal + }) + ) stream.on('data', () => { - res.set('Content-Type', 'application/octet-stream') - streamStarted = true + if (!streamStarted) { + streamStarted = true + res.set('Content-Type', 'application/octet-stream') + } }) stream.on('error', (err) => { pino.logger.error(err) res.status(408).send({ - error: 'Cannot find requested CID' + error: err.message }) }) - - setTimeout(() => { - if (!streamStarted) { - abortController.abort() - } - }, config.findFileTimeout) - + stream.on('end', () => { + clearTimeout(timeout) + }) stream.pipe(res) } catch (error) { pino.logger.error(error) From 994fa090ab08dfe1740fa6bfb29365aaa569ec80 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Thu, 23 May 2024 12:28:21 +0400 Subject: [PATCH 056/167] fix: disk usage checker + quickly search for a cid on another node --- config.default.json5 | 2 -- package-lock.json | 18 ++++++++++++++++++ package.json | 5 +++-- src/helia.ts | 8 ++++++-- src/utils/utils.ts | 17 ++++------------- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 53e3a04..87b0dba 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -24,8 +24,6 @@ ], listen: [ '/ip4/0.0.0.0/tcp/4001', - '/ip6/::/tcp/4002', - '/webrtc' ] }, serverPort: 4000, diff --git a/package-lock.json b/package-lock.json index b9b8b5d..e17b0b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "datastore-fs": "^9.1.8", "express": "^4.19.2", "file-type": "^19.0.0", + "go-get-folder-size": "^0.5.5", "helia": "^4.2.1", "it-last": "^3.0.4", "json5": "^2.2.3", @@ -9949,6 +9950,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/go-get-folder-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/go-get-folder-size/-/go-get-folder-size-0.5.5.tgz", + "integrity": "sha512-UB8PlOhxUXWPXvGivkYWhFWXXIDMxxKNXsu+W27APjNgfE11Gc+6+UODqUwC41neE829zQyhqmeY1p1AEBGXcg==", + "hasInstallScript": true, + "dependencies": { + "std-env": "^3.7.0" + }, + "bin": { + "go-get-folder-size": "bin/cli.js" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -16181,6 +16194,11 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + }, "node_modules/stream-to-it": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-1.0.1.tgz", diff --git a/package.json b/package.json index 7897975..88d3e71 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@libp2p/mplex": "^10.0.22", "@helia/mfs": "^3.0.6", "@helia/unixfs": "^3.0.6", "@helia/verified-fetch": "^1.3.14", @@ -49,13 +48,14 @@ "@libp2p/bootstrap": "^10.0.22", "@libp2p/circuit-relay-v2": "^1.0.22", "@libp2p/identify": "^2.0.0", + "@libp2p/interface-transport": "^4.0.3", "@libp2p/kad-dht": "^12.0.15", + "@libp2p/mplex": "^10.0.22", "@libp2p/peer-id": "^4.1.1", "@libp2p/peer-id-factory": "^4.1.1", "@libp2p/tcp": "^9.0.24", "@libp2p/upnp-nat": "^1.0.20", "@libp2p/webrtc": "^4.0.31", - "@libp2p/interface-transport": "^4.0.3", "blockstore-core": "^4.4.1", "blockstore-fs": "^1.1.10", "busboy": "^1.6.0", @@ -65,6 +65,7 @@ "datastore-fs": "^9.1.8", "express": "^4.19.2", "file-type": "^19.0.0", + "go-get-folder-size": "^0.5.5", "helia": "^4.2.1", "it-last": "^3.0.4", "json5": "^2.2.3", diff --git a/src/helia.ts b/src/helia.ts index 1406559..3384095 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -9,6 +9,7 @@ import { identify } from '@libp2p/identify' import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' +import { ConfigNode } from './utils/types.js' export const helia = await createHelia({ datastore, @@ -16,14 +17,17 @@ export const helia = await createHelia({ libp2p: { datastore, addresses: { - listen: ['/ip4/0.0.0.0/tcp/4001'] + listen: config.peerDiscovery.listen }, transports: [tcp()], connectionEncryption: [noise()], streamMuxers: [yamux()], peerDiscovery: [ bootstrap({ - list: config.peerDiscovery.bootstrap + list: [ + ...config.peerDiscovery.bootstrap, + ...config.nodes.map((item: ConfigNode) => item.multiAddr) + ] }) ], services: { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e5c50c4..ad9de8d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,9 +4,7 @@ import { config } from '../config.js' import { ConfigNode, NodeWithPeerId, UnixFsMulterFile } from './types.js' import { statfs } from 'node:fs/promises' import { pino } from './logger.js' -import { exec } from 'child_process' -import { promisify } from 'node:util' -const execPromise = promisify(exec) +import { getFolderSizeBin } from 'go-get-folder-size' /** * Get peerId from multiaddr string @@ -81,16 +79,9 @@ export async function availableStorageSize() { export async function dirSize(dir: string): Promise { try { - const { stdout } = await execPromise('du -sb .', { cwd: dir }) - const match = /^(\d+)/.exec(stdout) - - if (match && match.length >= 2) { - return Number(match[1]) - } else { - pino.logger.error('Cant parse cmd output') - } - } catch (err) { - pino.logger.error(`${err.message}\n${err.stack}`) + return await getFolderSizeBin(dir, false, { loose: true }) + } catch (e) { + pino.logger.error(e) } return 0 } From cc36596bec1d9220a01abc724862a0827a6911d0 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 23 May 2024 16:39:24 +0300 Subject: [PATCH 057/167] feat(file): resp content-length --- src/api/file.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/file.ts b/src/api/file.ts index 3fb39ad..91a1d84 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -7,6 +7,7 @@ import { helia, ifs } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' +import { unixfs } from '@helia/unixfs' const router = Router() @@ -67,7 +68,7 @@ router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => router.get('/file/:cid', async (req, res) => { try { const cid = CID.parse(req.params.cid) - + const fileStats = await unixfs(helia).stat(cid) let streamStarted = false const abortController = new AbortController() const timeout = setTimeout(() => { @@ -86,6 +87,7 @@ router.get('/file/:cid', async (req, res) => { if (!streamStarted) { streamStarted = true res.set('Content-Type', 'application/octet-stream') + res.set('Content-Length', fileStats.fileSize.toString()) } }) stream.on('error', (err) => { From 0c9b51b1963c73adcb6f32d39a63dd93c894fd24 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 23 May 2024 17:16:09 +0300 Subject: [PATCH 058/167] fix(file): unixfs -> ifs --- src/api/file.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index 91a1d84..8ad2b77 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -7,7 +7,6 @@ import { helia, ifs } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' -import { unixfs } from '@helia/unixfs' const router = Router() @@ -68,7 +67,7 @@ router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => router.get('/file/:cid', async (req, res) => { try { const cid = CID.parse(req.params.cid) - const fileStats = await unixfs(helia).stat(cid) + const fileStats = await ifs.stat(cid) let streamStarted = false const abortController = new AbortController() const timeout = setTimeout(() => { From 65ff30ed97e15dbfa831489347fa756d5daca81e Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Wed, 29 May 2024 16:27:24 +0400 Subject: [PATCH 059/167] fix: remove adamant ipfs nodes from bootstrap --- src/helia.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/helia.ts b/src/helia.ts index 3384095..a9a4818 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -24,10 +24,7 @@ export const helia = await createHelia({ streamMuxers: [yamux()], peerDiscovery: [ bootstrap({ - list: [ - ...config.peerDiscovery.bootstrap, - ...config.nodes.map((item: ConfigNode) => item.multiAddr) - ] + list: config.peerDiscovery.bootstrap }) ], services: { From 1df662ca6fcaafb629be6c1709c2f417dc4cbe7a Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 8 Jun 2024 18:01:34 +0400 Subject: [PATCH 060/167] fix: prepare routes --- src/api/debug.ts | 21 ++++++- src/api/file.ts | 14 +---- src/api/libp2p.ts | 131 ++++++++++++++++++--------------------- src/api/node.ts | 4 +- src/auto-peering.cron.ts | 43 ------------- src/helia.ts | 33 +++++++++- src/index.ts | 46 +++----------- 7 files changed, 120 insertions(+), 172 deletions(-) delete mode 100644 src/auto-peering.cron.ts diff --git a/src/api/debug.ts b/src/api/debug.ts index 663390f..b84d4c5 100644 --- a/src/api/debug.ts +++ b/src/api/debug.ts @@ -1,12 +1,27 @@ import { Router } from 'express' import { pino } from '../utils/logger.js' -import { autoPeeringHandler } from '../auto-peering.cron.js' +import { getNodesList } from '../utils/utils.js' +import { helia } from '../helia.js' const router = Router() -router.get('/debug/autopeering', async (req, res) => { +router.get('/autopeering', async (req, res) => { try { - const successPeers = await autoPeeringHandler() + const nodes = getNodesList([helia.libp2p.peerId.toString()]) + pino.logger.info(`Peering nodes list: ${nodes.map((node) => node.name)}`) + + const successPeers: string[] = [] + + for await (const node of nodes) { + pino.logger.info(`Start peering ${node.name} node (${node.multiAddr})...`) + try { + await helia.libp2p.dial(node.multiAddr) + pino.logger.info(`Successfully peered with ${node.name}`) + successPeers.push(node.name) + } catch (err) { + pino.logger.info(`Peering with ${node.name} failed. Error: ${err.message}`) + } + } res.send({ peeredSuccessfullyTo: successPeers diff --git a/src/api/file.ts b/src/api/file.ts index 8ad2b77..697ce46 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -10,7 +10,7 @@ import { flatFiles } from '../utils/utils.js' const router = Router() -router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => { +router.post('/upload', multerStorage.array('files', 5), async (req, res) => { if (!req.files) { res.statusCode = 400 return res.send({ @@ -34,20 +34,10 @@ router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => if (isPinned) { pino.logger.info(`File already pinned ${cid}`) } else { - // Pin the file for await (const pinned of helia.pins.add(cid)) { pino.logger.info(`Filed pinned: ${pinned}`) } } - - // Tell the network we can provide content for the passed CID - // const dht = helia.libp2p.services.dht as KadDHT - // await dht.provide(cid) - // pino.logger.info(`Provided CID via DHT ${cid}`) - // - // pino.logger.info(`Routing: Providing ${cid}`) - // void helia.routing.provide(cid) - // pino.logger.info('Routing: Provide DONE') } res.send({ @@ -64,7 +54,7 @@ router.post('/file/upload', multerStorage.array('files', 5), async (req, res) => } }) -router.get('/file/:cid', async (req, res) => { +router.get('/:cid', async (req, res) => { try { const cid = CID.parse(req.params.cid) const fileStats = await ifs.stat(cid) diff --git a/src/api/libp2p.ts b/src/api/libp2p.ts index fb9b8db..8c40fb9 100644 --- a/src/api/libp2p.ts +++ b/src/api/libp2p.ts @@ -10,7 +10,7 @@ import { pino } from '../utils/logger.js' const router = Router() router.get( - '/libp2p/services/ping', + '/services/ping', async (req: Request, res: Response) => { try { const peerId = peerIdFromString(req.query.peerId || '') @@ -28,66 +28,60 @@ router.get( } ) -router.get( - '/libp2p/peerStore', - async (req: Request, res: Response) => { - const peerId = req.query.peerId +router.get('/peerStore', async (req: Request, res: Response) => { + const peerId = req.query.peerId - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [ - (peer) => { - if (!peerId) { - return true - } - - return peer.id.toString() === peerId + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [ + (peer) => { + if (!peerId) { + return true } - ], - limit: 10 - }) - res.send({ - length: peers.length, - peers: peers.map((peer) => { - return { - id: peer.id.toString() - } - }) - }) - } catch (err) { - res.send({ - error: err.message + return peer.id.toString() === peerId + } + ], + limit: 10 + }) + + res.send({ + length: peers.length, + peers: peers.map((peer) => { + return { + id: peer.id.toString() + } }) - } + }) + } catch (err) { + res.send({ + error: err.message + }) } -) +}) -router.get( - '/libp2p/peerInfo', - async (req: Request, res: Response) => { - const peerId = req.query.peerId +router.get('/peerInfo', async (req: Request, res: Response) => { + const peerId = req.query.peerId - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [(peer) => peer.id.toString() === peerId], - limit: 10 - }) + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [(peer) => peer.id.toString() === peerId], + limit: 10 + }) - res.send({ - length: peers.length, - peer: peers - }) - } catch (err) { - res.send({ - error: err.message - }) - } + res.send({ + length: peers.length, + peer: peers + }) + } catch (err) { + res.send({ + error: err.message + }) } -) +}) router.get( - '/libp2p/dial', + '/dial', async (req: Request, res: Response) => { let peerId: PeerId | undefined let multiAddr: Multiaddr | undefined @@ -133,34 +127,31 @@ router.get( } ) -router.get( - '/libp2p/connections', - async (req: Request, res: Response) => { - try { - const peerId = req.query.peerId?.toString() || '' - const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) +router.get('/connections', async (req: Request, res: Response) => { + try { + const peerId = req.query.peerId?.toString() || '' + const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) - res.send({ - length: connections.length, - connections - }) - } catch (err) { - pino.logger.error(err) - res.status(400) - res.send({ - error: err.message - }) - } + res.send({ + length: connections.length, + connections + }) + } catch (err) { + pino.logger.error(err) + res.status(400) + res.send({ + error: err.message + }) } -) +}) -router.get('/libp2p/status', async (req, res) => { +router.get('/status', async (req, res) => { res.send({ status: helia.libp2p.status }) }) -router.get('/libp2p/peers', (req, res) => { +router.get('/peers', (req, res) => { try { const peers = helia.libp2p.getPeers() diff --git a/src/api/node.ts b/src/api/node.ts index 4549f26..0802312 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -5,14 +5,14 @@ import { helia } from '../helia.js' const router = Router() -router.get('/node/health', async (req, res) => { +router.get('/health', async (req, res) => { res.send({ timestamp: Date.now(), heliaStatus: helia.libp2p.status }) }) -router.get('/node/info', async (req, res) => { +router.get('/info', async (req, res) => { const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() res.send({ diff --git a/src/auto-peering.cron.ts b/src/auto-peering.cron.ts deleted file mode 100644 index 86c3b45..0000000 --- a/src/auto-peering.cron.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CronJob } from 'cron' -import { helia } from './helia.js' -import { getNodesList } from './utils/utils.js' -import { config } from './config.js' -import { pino } from './utils/logger.js' - -/** - * Auto-peering between ADM IPFS nodes. - */ -let started = false -export const autoPeering = new CronJob(config.autoPeeringPeriod, () => { - if (!started) { - started = true - autoPeeringHandler() - .catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) - .finally(() => (started = false)) - } -}) - -export async function autoPeeringHandler() { - const peerId = helia.libp2p.peerId - pino.logger.info( - `[Cron] Running "autoPeering" cronjob. Current node peerId: ${helia.libp2p.peerId}` - ) - - const nodes = getNodesList([peerId.toString()]) - pino.logger.info(`Peering nodes list: ${nodes.map((node) => node.name)}`) - - const successPeers: string[] = [] - - for await (const node of nodes) { - pino.logger.info(`Start peering ${node.name} node (${node.multiAddr})...`) - try { - await helia.libp2p.dial(node.multiAddr) - pino.logger.info(`Successfully peered with ${node.name}`) - successPeers.push(node.name) - } catch (err) { - pino.logger.info(`Peering with ${node.name} failed. Error: ${err.message}`) - } - } - - return successPeers -} diff --git a/src/helia.ts b/src/helia.ts index a9a4818..3956cff 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -9,7 +9,7 @@ import { identify } from '@libp2p/identify' import { blockstore, datastore } from './store.js' import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' -import { ConfigNode } from './utils/types.js' +import { pino } from './utils/logger.js' export const helia = await createHelia({ datastore, @@ -37,6 +37,33 @@ export const helia = await createHelia({ } }) -export const ifs = unixfs(helia) +helia.libp2p.getMultiaddrs().forEach((addr) => { + pino.logger.info(`Listening on ${addr.toString()}`) +}) + +helia.libp2p.addEventListener('peer:discovery', (evt) => { + const peer = evt.detail + pino.logger.info(`Discovered peer: ${peer.id}`) +}) + +helia.libp2p.addEventListener('peer:connect', (evt) => { + const peerId = evt.detail + pino.logger.info(`Peer connected: ${peerId}`) +}) + +helia.libp2p.addEventListener('peer:disconnect', (evt) => { + const peerId = evt.detail + pino.logger.info(`Peer disconnected: ${peerId}`) +}) + +helia.libp2p.addEventListener('start', (event) => { + pino.logger.info('Libp2p node started') +}) -export const verifiedFetch = await createVerifiedFetch(helia) +helia.libp2p.addEventListener('stop', (event) => { + pino.logger.info('Libp2p node stopped') +}) + +pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) + +export const ifs = unixfs(helia) diff --git a/src/index.ts b/src/index.ts index 0b56740..40802fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,45 +1,13 @@ import express, { NextFunction } from 'express' -import { helia } from './helia.js' import { Request, Response } from 'express' import { pino } from './utils/logger.js' -import { config, configFileName, packageJson } from './config.js' -import { getDiskUsageStats, diskUsageCron } from './disk-usage.cron.js' +import { config, configFileName } from './config.js' +import { diskUsageCron } from './disk-usage.cron.js' import cors from 'cors' import * as routers from './api/index.js' pino.logger.info(`Using config file: ${configFileName}`) -helia.libp2p.getMultiaddrs().forEach((addr) => { - pino.logger.info(`Listening on ${addr.toString()}`) -}) - -helia.libp2p.addEventListener('peer:discovery', (evt) => { - const peer = evt.detail - pino.logger.info(`Discovered peer: ${peer.id}`) -}) - -helia.libp2p.addEventListener('peer:connect', (evt) => { - const peerId = evt.detail - pino.logger.info(`Peer connected: ${peerId}`) -}) - -helia.libp2p.addEventListener('peer:disconnect', (evt) => { - const peerId = evt.detail - pino.logger.info(`Peer disconnected: ${peerId}`) -}) - -helia.libp2p.addEventListener('start', (event) => { - pino.logger.info('Libp2p node started') -}) - -helia.libp2p.addEventListener('stop', (event) => { - pino.logger.info('Libp2p node stopped') -}) - -pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) - -// autoPeering.start() -// autoPeeringHandler().catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) diskUsageCron.start() const PORT = config.serverPort @@ -58,11 +26,11 @@ app.get('/', (req, res) => { res.send('IPFS node') }) -app.use('/api', routers.file) -app.use('/api', routers.node) -app.use('/api', routers.helia) -app.use('/api', routers.libp2p) -app.use('/api', routers.debug) +app.use('/api/file', routers.file) +app.use('/api/node', routers.node) +app.use('/api/helia', routers.helia) +app.use('/api/libp2p', routers.libp2p) +app.use('/api/debug', routers.debug) app.use((err: Error, req: Request, res: Response, next: NextFunction) => { pino.logger.error(`${err.message}\n${err.stack}`) From 1daef311ebd00ff446a3066515453ca6a0d29925 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 8 Jun 2024 18:18:17 +0400 Subject: [PATCH 061/167] fix: move the maximum number of files to the configuration --- config.default.json5 | 1 + src/api/file.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config.default.json5 b/config.default.json5 index 87b0dba..2258bc9 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -30,6 +30,7 @@ autoPeeringPeriod: '*/10 * * * * *', diskUsageScanPeriod: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. + maxFileCount: 5, findFileTimeout: 20000, cors: { originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] diff --git a/src/api/file.ts b/src/api/file.ts index 697ce46..c1df237 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -10,7 +10,7 @@ import { flatFiles } from '../utils/utils.js' const router = Router() -router.post('/upload', multerStorage.array('files', 5), async (req, res) => { +router.post('/upload', multerStorage.array('files', config.maxFileCount), async (req, res) => { if (!req.files) { res.statusCode = 400 return res.send({ From 6ad9579a285c4416fdffc2b576abc22ce31d4b01 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 8 Jun 2024 18:20:30 +0400 Subject: [PATCH 062/167] fix: readme.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 07c0083..99e6e5a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,2 @@ # ipfs-node IPFS decentralized file storage and transfers - -## Caveats -System-dependent commands are used to determine which disk space is used. The code will only work on Linux systems. From c4481fca98c8f6c1c4cd5c0d16d48167af599e6c Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Sat, 8 Jun 2024 18:23:28 +0400 Subject: [PATCH 063/167] fix: removed unused verified-fetch --- package.json | 1 - src/helia.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index 88d3e71..9eab637 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "@chainsafe/libp2p-yamux": "^6.0.2", "@helia/mfs": "^3.0.6", "@helia/unixfs": "^3.0.6", - "@helia/verified-fetch": "^1.3.14", "@libp2p/autonat": "^1.0.19", "@libp2p/bootstrap": "^10.0.22", "@libp2p/circuit-relay-v2": "^1.0.22", diff --git a/src/helia.ts b/src/helia.ts index 3956cff..6ae3dec 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,5 +1,4 @@ import { unixfs } from '@helia/unixfs' -import { createVerifiedFetch } from '@helia/verified-fetch' import { bootstrap } from '@libp2p/bootstrap' import { createHelia } from 'helia' import { tcp } from '@libp2p/tcp' From b1821fab6a27884167d8e138de1e1ca10f40951e Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Wed, 26 Jun 2024 00:16:45 +0400 Subject: [PATCH 064/167] fix: readme --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README.md b/README.md index 99e6e5a..c093248 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ # ipfs-node IPFS decentralized file storage and transfers + +## How to start +- You will need nodejs v20.11.1 (You can install via nvm: https://github.com/nvm-sh/nvm): +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +``` +- nvm install 20.11.1 +- npm i -g pm2 +- git clone https://github.com/Adamant-im/ipfs-node.git +- cd ipfs-node & npm i +- npm run build +- pm2 start dist/index.js --name="IPFS node" + +## How to configure +Using config.default.json5 as a template, you can create various configuration files. +For example: +`node dist/index.js test1` — this command will launch the server with the configuration from the file config.test1.json5 + +Options available for configuration: +- nodes — a list of IPFS ADAMANT nodes between which interaction will take place. +- storeFolder — the directory in which the files will be stored (the directory is set from the user’s root directory) +- logLevel — logging level: fatal, error, warn, info, debug, trace +- peerDiscovery.bootstrap IPFS network nodes that will be used to search for new nodes. Details: https://github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap +- peerDiscovery.listen — Addresses that helia will listen to. +- serverPort — Port on which the API server will be deployed +- diskUsageScanPeriod — Disk space scanning period. Set in cron format: '* * * * * *' +- uploadLimitSizeBytes — Maximum upload file size +- maxFileCount — Maximum upload count of files per request +- findFileTimeout — Time limit for searching for a file on the IPFS network +- cors.originRegexps — Sites from which requests to the node are allowed. Set using regular expressions. + +## How to use +To upload a file to IPFS, you need to make a POST request `/api/file/upload` + +Request body type: `form-data` + +It should contain a "files" field, which can accept an array of files up to 5 pieces at a time. +Answer: +```json +{ + "filesNames": [ + "beach9.jpg" - name of the uploaded file + ], + "cids": [ + "bafkreiaeejc52w4pr6p4znyl77bljssgshvh4n23xpohkflrwxakpa42ra" - CID of the file + ] +} +``` + +To find a file, you need to perform a GET request `/api/file/:cid` + +The response will be a stream with the Content-Type header: `application/octet-stream` From fc6f44b1be0c0aca101d8ef9a06fdc5a097fa6a4 Mon Sep 17 00:00:00 2001 From: PaulDremanovich Date: Wed, 26 Jun 2024 00:39:05 +0400 Subject: [PATCH 065/167] fix: project description --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c093248..50fa3b5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # ipfs-node -IPFS decentralized file storage and transfers +ADAMANT ipfs-node. Designed for downloading and exchanging files in the ADAMANT Messenger. + +Unlike the standard libraries (helia or kubo), this ipfs-node is equipped with a web server for performing REST requests for downloading and receiving files. + +The plans also include the implementation of the Garbage collector function, which will save disk space by removing unsent files. ## How to start - You will need nodejs v20.11.1 (You can install via nvm: https://github.com/nvm-sh/nvm): From 6a32c7ffd455f76be6b1d34dfa866d8475c50480 Mon Sep 17 00:00:00 2001 From: confleux Date: Wed, 10 Jul 2024 16:09:46 +0300 Subject: [PATCH 066/167] refactor: readme --- README.md | 162 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 129 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 50fa3b5..ae8138e 100644 --- a/README.md +++ b/README.md @@ -3,56 +3,152 @@ ADAMANT ipfs-node. Designed for downloading and exchanging files in the ADAMANT Unlike the standard libraries (helia or kubo), this ipfs-node is equipped with a web server for performing REST requests for downloading and receiving files. -The plans also include the implementation of the Garbage collector function, which will save disk space by removing unsent files. +The plans also include the implementation of the Garbage Collector function, which will save disk space by removing unsent files. ## How to start -- You will need nodejs v20.11.1 (You can install via nvm: https://github.com/nvm-sh/nvm): +- You will need Node.js v20.11.1 (You can install via nvm: https://github.com/nvm-sh/nvm): ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +nvm install 20.11.1 ``` -- nvm install 20.11.1 -- npm i -g pm2 -- git clone https://github.com/Adamant-im/ipfs-node.git -- cd ipfs-node & npm i -- npm run build -- pm2 start dist/index.js --name="IPFS node" +- Cloning and building node: +```bash +git clone https://github.com/Adamant-im/ipfs-node.git +cd ipfs-node +npm i +npm run build +``` +- Running node with [pm2](https://github.com/Unitech/pm2): +```bash +npm i -g pm2 +pm2 start dist/index.js --name="IPFS node" +``` + ## How to configure -Using config.default.json5 as a template, you can create various configuration files. +Using `config.default.json5` as a template, you can create various configuration files. + For example: `node dist/index.js test1` — this command will launch the server with the configuration from the file config.test1.json5 -Options available for configuration: -- nodes — a list of IPFS ADAMANT nodes between which interaction will take place. -- storeFolder — the directory in which the files will be stored (the directory is set from the user’s root directory) -- logLevel — logging level: fatal, error, warn, info, debug, trace -- peerDiscovery.bootstrap IPFS network nodes that will be used to search for new nodes. Details: https://github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap -- peerDiscovery.listen — Addresses that helia will listen to. -- serverPort — Port on which the API server will be deployed -- diskUsageScanPeriod — Disk space scanning period. Set in cron format: '* * * * * *' -- uploadLimitSizeBytes — Maximum upload file size -- maxFileCount — Maximum upload count of files per request -- findFileTimeout — Time limit for searching for a file on the IPFS network -- cors.originRegexps — Sites from which requests to the node are allowed. Set using regular expressions. +```jsonc +{ + // List of IPFS ADAMANT nodes interacting between each other + nodes: [ + { + name: "ipfs1", + multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq" + }, + ... + ], + storeFolder: '.adm-ipfs', // File storage directory (the directory is set from the user’s root directory) + logLevel: 'debug', // Logging level: fatal, error, warn, info, debug, trace + peerDiscovery: { + // IPFS network nodes that will be used to search for new nodes + // Details: https://github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap + bootstrap: [ + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + ... + ], + // Addresses that helia will listen to + listen: [ + '/ip4/0.0.0.0/tcp/4001', + ] + }, + serverPort: 4000, // API server deployment port + autoPeeringPeriod: '*/10 * * * * *', + diskUsageScanPeriod: '*/30 * * * * *', // Disk space scanning period. Set in cron format: '* * * * * *' + uploadLimitSizeBytes: 268435456, // Maximum upload file size (in bytes) + maxFileCount: 5, // Maximum upload count of files per request + findFileTimeout: 20000, // Time limit for searching for a file on the IPFS network + cors: { + // Allowed URLs to make request to node. Set using regular expressions + originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] + } +} +``` ## How to use -To upload a file to IPFS, you need to make a POST request `/api/file/upload` +### Upload file Request body type: `form-data` It should contain a "files" field, which can accept an array of files up to 5 pieces at a time. -Answer: -```json + +#### Request +```POST /api/file/upload``` +```bash +curl -i --location 'http://localhost:4000/api/file/upload' --form 'files=@"file.txt"' +``` + +#### Response +``` +HTTP/1.1 200 OK +X-Powered-By: Express +Vary: Origin +Content-Type: application/json; charset=utf-8 +Content-Length: 98 +ETag: W/"62-79lDutTrkZZgROcnYebNgQPnqhQ" +Date: Wed, 10 Jul 2024 12:18:50 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + +{"filesNames":["file.txt"],"cids":["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"]} +``` + +### Get file + +#### Request +```GET /api/file/:cid``` +```bash +curl -i --location 'http://localhost:4000/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm' +``` + +#### Response +``` +HTTP/1.1 200 OK +X-Powered-By: Express +Vary: Origin +Content-Type: application/octet-stream +Content-Length: 17 +Date: Wed, 10 Jul 2024 12:23:39 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + +Hello ipfs-node! +``` + +### Get node info + +#### Request +```GET /api/node/info``` +```bash +curl -i --location 'http://localhost:4000/api/node/info' +``` + +#### Response +``` +HTTP/1.1 200 OK +X-Powered-By: Express +Vary: Origin +Content-Type: application/json; charset=utf-8 +Content-Length: 428 +ETag: W/"1ac-gZEDVXGiPiendFaMWoAHkwBk/cY" +Date: Wed, 10 Jul 2024 12:36:38 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + { - "filesNames": [ - "beach9.jpg" - name of the uploaded file + "version":"0.0.1", + "timestamp":1720614998797, + "heliaStatus":"started", + "peerId":"12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", + "multiAddresses":[ + "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", + "/ip4/62.72.43.99/tcp/4001/p2p/12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9" ], - "cids": [ - "bafkreiaeejc52w4pr6p4znyl77bljssgshvh4n23xpohkflrwxakpa42ra" - CID of the file - ] + "blockstoreSizeMb":0.0009489059448242188, + "datastoreSizeMb":0.006007194519042969, + "availableSizeInMb":2257731 } ``` - -To find a file, you need to perform a GET request `/api/file/:cid` - -The response will be a stream with the Content-Type header: `application/octet-stream` From 8deb29fbdde822093314737e7b74b7342b8cd3b5 Mon Sep 17 00:00:00 2001 From: confleux Date: Wed, 10 Jul 2024 16:21:39 +0300 Subject: [PATCH 067/167] fix: user's root directory -> user's home directory --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae8138e..4f80335 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ For example: }, ... ], - storeFolder: '.adm-ipfs', // File storage directory (the directory is set from the user’s root directory) + storeFolder: '.adm-ipfs', // File storage directory (the directory is set from the user’s home directory) logLevel: 'debug', // Logging level: fatal, error, warn, info, debug, trace peerDiscovery: { // IPFS network nodes that will be used to search for new nodes From 4081738fd42ea7465d798a815941624867e64022 Mon Sep 17 00:00:00 2001 From: confleux Date: Wed, 10 Jul 2024 18:17:04 +0300 Subject: [PATCH 068/167] refactor: remove extra response info --- README.md | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 4f80335..8e860d7 100644 --- a/README.md +++ b/README.md @@ -84,14 +84,8 @@ curl -i --location 'http://localhost:4000/api/file/upload' --form 'files=@"file. #### Response ``` HTTP/1.1 200 OK -X-Powered-By: Express -Vary: Origin Content-Type: application/json; charset=utf-8 -Content-Length: 98 -ETag: W/"62-79lDutTrkZZgROcnYebNgQPnqhQ" -Date: Wed, 10 Jul 2024 12:18:50 GMT -Connection: keep-alive -Keep-Alive: timeout=5 +... {"filesNames":["file.txt"],"cids":["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"]} ``` @@ -107,13 +101,8 @@ curl -i --location 'http://localhost:4000/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpe #### Response ``` HTTP/1.1 200 OK -X-Powered-By: Express -Vary: Origin Content-Type: application/octet-stream -Content-Length: 17 -Date: Wed, 10 Jul 2024 12:23:39 GMT -Connection: keep-alive -Keep-Alive: timeout=5 +... Hello ipfs-node! ``` @@ -129,14 +118,8 @@ curl -i --location 'http://localhost:4000/api/node/info' #### Response ``` HTTP/1.1 200 OK -X-Powered-By: Express -Vary: Origin Content-Type: application/json; charset=utf-8 -Content-Length: 428 -ETag: W/"1ac-gZEDVXGiPiendFaMWoAHkwBk/cY" -Date: Wed, 10 Jul 2024 12:36:38 GMT -Connection: keep-alive -Keep-Alive: timeout=5 +... { "version":"0.0.1", From c0a3565b66df2a3e14f663db21ee7f4e9c9b736f Mon Sep 17 00:00:00 2001 From: confleux Date: Wed, 10 Jul 2024 18:18:00 +0300 Subject: [PATCH 069/167] refactor: format json --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8e860d7..a43b125 100644 --- a/README.md +++ b/README.md @@ -122,16 +122,16 @@ Content-Type: application/json; charset=utf-8 ... { - "version":"0.0.1", - "timestamp":1720614998797, - "heliaStatus":"started", - "peerId":"12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", - "multiAddresses":[ + "version": "0.0.1", + "timestamp": 1720614998797, + "heliaStatus": "started", + "peerId": "12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", + "multiAddresses": [ "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", "/ip4/62.72.43.99/tcp/4001/p2p/12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9" ], - "blockstoreSizeMb":0.0009489059448242188, - "datastoreSizeMb":0.006007194519042969, - "availableSizeInMb":2257731 + "blockstoreSizeMb": 0.0009489059448242188, + "datastoreSizeMb": 0.006007194519042969, + "availableSizeInMb": 2257731 } ``` From 259519197167fa15eb0f25ea0a51ecd3c8a1c2d9 Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 13:05:45 +0100 Subject: [PATCH 070/167] chore: update `package-lock.json` --- package-lock.json | 146 +--------------------------------------------- 1 file changed, 1 insertion(+), 145 deletions(-) diff --git a/package-lock.json b/package-lock.json index e17b0b4..0490236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "@chainsafe/libp2p-yamux": "^6.0.2", "@helia/mfs": "^3.0.6", "@helia/unixfs": "^3.0.6", - "@helia/verified-fetch": "^1.3.14", "@libp2p/autonat": "^1.0.19", "@libp2p/bootstrap": "^10.0.22", "@libp2p/circuit-relay-v2": "^1.0.22", @@ -2780,23 +2779,6 @@ "progress-events": "^1.0.0" } }, - "node_modules/@helia/car": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@helia/car/-/car-3.1.5.tgz", - "integrity": "sha512-3vlkSsIt/QD2CSnuGOjjx2tCEZ9Dxss/PohqXTiXdHeFeTAXoLEgX5f25jyYUHErLt7tdgMrO6ayACVPFpjHVQ==", - "dependencies": { - "@helia/interface": "^4.3.0", - "@ipld/car": "^5.3.0", - "@libp2p/interfaces": "^3.3.2", - "interface-blockstore": "^5.2.10", - "it-drain": "^3.0.5", - "it-map": "^3.0.5", - "multiformats": "^13.1.0", - "p-defer": "^4.0.0", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0" - } - }, "node_modules/@helia/delegated-routing-v1-http-api-client": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.1.tgz", @@ -2818,19 +2800,6 @@ "uint8arrays": "^5.0.1" } }, - "node_modules/@helia/http": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@helia/http/-/http-1.0.7.tgz", - "integrity": "sha512-79mui6ZlcgzDiOl+Nz+cje8NUTOcJE1lMmh4D4Ryxd80gDGlHk2ZY8d4w468LvyJ9mm2ZJJuboSwLvPDHm0UJw==", - "dependencies": { - "@helia/block-brokers": "^3.0.0", - "@helia/interface": "^4.3.0", - "@helia/routers": "^1.1.0", - "@helia/utils": "^0.3.0", - "blockstore-core": "^4.4.0", - "datastore-core": "^9.2.9" - } - }, "node_modules/@helia/interface": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.3.0.tgz", @@ -2845,24 +2814,6 @@ "progress-events": "^1.0.0" } }, - "node_modules/@helia/ipns": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@helia/ipns/-/ipns-7.2.2.tgz", - "integrity": "sha512-9FX0/93nT5ueaFMFzhbS+YWx/YHoqVnr11ZIfDMOIleU7WC0U84W/J7n1k21yNDajFIHqXQJNFm68geJaP42Ig==", - "dependencies": { - "@helia/interface": "^4.3.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/kad-dht": "^12.0.8", - "@libp2p/logger": "^4.0.7", - "@libp2p/peer-id": "^4.0.7", - "@multiformats/dns": "^1.0.1", - "interface-datastore": "^8.2.11", - "ipns": "^9.1.0", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" - } - }, "node_modules/@helia/mfs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.6.tgz", @@ -2955,39 +2906,6 @@ "uint8arrays": "^5.0.2" } }, - "node_modules/@helia/verified-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@helia/verified-fetch/-/verified-fetch-1.4.1.tgz", - "integrity": "sha512-gp2jLQMwJn6WBTQTSAvg78WUkDLPNeUhTvJ6DNMJwsuVP0vE/RAx3H55w1gX4uzUadhPLmf5LqY/ZRvHnQn6gQ==", - "dependencies": { - "@helia/block-brokers": "^3.0.0", - "@helia/car": "^3.1.5", - "@helia/http": "^1.0.7", - "@helia/interface": "^4.3.0", - "@helia/ipns": "^7.2.2", - "@helia/routers": "^1.1.0", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/interface": "^1.1.6", - "@libp2p/kad-dht": "^12.0.11", - "@libp2p/peer-id": "^4.0.9", - "@multiformats/dns": "^1.0.6", - "cborg": "^4.2.0", - "hashlru": "^2.3.0", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "ipfs-unixfs-exporter": "^13.5.0", - "it-map": "^3.0.5", - "it-pipe": "^3.0.1", - "it-tar": "^6.0.5", - "it-to-browser-readablestream": "^2.0.6", - "lru-cache": "^10.2.0", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.3" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3021,21 +2939,6 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, - "node_modules/@ipld/car": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@ipld/car/-/car-5.3.0.tgz", - "integrity": "sha512-OB8LVvJeVAFFGluNIkZeDZ/aGeoekFKsuIvNT9I5sJIb5WekQuW5+lekjQ7Z7mZ7DBKuke/kI4jBT1j0/akU1w==", - "dependencies": { - "@ipld/dag-cbor": "^9.0.7", - "cborg": "^4.0.5", - "multiformats": "^13.0.0", - "varint": "^6.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/@ipld/dag-cbor": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz", @@ -11187,15 +11090,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, - "node_modules/iso-constants": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/iso-constants/-/iso-constants-0.1.2.tgz", - "integrity": "sha512-OTCM5ZCQsHBCI4Wdu4tSxvDIkmDHd5EwJDps5mKqnQnWJSKlnwMs3EDZ4n3Fh1tmkWkDlyd2vCDbEYuPbyrUNQ==", - "hasInstallScript": true, - "engines": { - "node": ">=10" - } - }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -11420,40 +11314,6 @@ "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.5.tgz", "integrity": "sha512-4CzqXzx7FAeXsRYBTH0GhkxerH8Sv0nEGIXrO0ZIpECHth59Dm9ZYZ161VPrCQccWIL/Vu6M9YptlbMiEpCIlQ==" }, - "node_modules/it-tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/it-tar/-/it-tar-6.0.5.tgz", - "integrity": "sha512-GxKBamsX2EZA6jrNh6xoeKmRpbOELgYhCk1ojhN0TFSBdxwkRBqBRc3sI43vTHRpI7i2o6ptrSxK3LLKWNvudg==", - "dependencies": { - "iso-constants": "^0.1.2", - "it-reader": "^6.0.1", - "it-stream-types": "^2.0.1", - "it-to-buffer": "^4.0.5", - "p-defer": "^4.0.0", - "uint8arraylist": "^2.3.2", - "uint8arrays": "^5.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-to-browser-readablestream": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/it-to-browser-readablestream/-/it-to-browser-readablestream-2.0.9.tgz", - "integrity": "sha512-Q/0OXLOMgZ4Fk62Mrqp3x9QqN/UH9fotftr3dEfqr74Bt8ceClLOTmhkMyrk53R/gRcxdM6tBjzHGBeSjhmPeQ==", - "dependencies": { - "get-iterator": "^2.0.1" - } - }, - "node_modules/it-to-buffer": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/it-to-buffer/-/it-to-buffer-4.0.7.tgz", - "integrity": "sha512-c7JXrFg8xntJTPzhg7Dg6WJYm+XW0wBUebvEBrc6zrL/QukGRXclw1OBz6M9Qmqkiorgb3qpsRwKlI/4Q3tmkQ==", - "dependencies": { - "uint8arrays": "^5.0.3" - } - }, "node_modules/it-ws": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.1.tgz", @@ -12661,6 +12521,7 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, "engines": { "node": "14 || >=16.14" } @@ -16995,11 +16856,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", From 35d7b91f8f48c5a7729cbe55fd4af277ba5946ed Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 13:16:18 +0100 Subject: [PATCH 071/167] chore(package.json): remove unused dependencies --- package-lock.json | 556 ---------------------------------------------- package.json | 23 -- 2 files changed, 579 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0490236..c1ec8b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,37 +9,19 @@ "version": "0.0.1", "license": "GPLv3", "dependencies": { - "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-noise": "^15.0.0", - "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/mfs": "^3.0.6", "@helia/unixfs": "^3.0.6", - "@libp2p/autonat": "^1.0.19", "@libp2p/bootstrap": "^10.0.22", - "@libp2p/circuit-relay-v2": "^1.0.22", - "@libp2p/identify": "^2.0.0", - "@libp2p/interface-transport": "^4.0.3", - "@libp2p/kad-dht": "^12.0.15", - "@libp2p/mplex": "^10.0.22", "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-id-factory": "^4.1.1", "@libp2p/tcp": "^9.0.24", - "@libp2p/upnp-nat": "^1.0.20", - "@libp2p/webrtc": "^4.0.31", - "blockstore-core": "^4.4.1", "blockstore-fs": "^1.1.10", - "busboy": "^1.6.0", "cors": "^2.8.5", "cron": "^3.1.6", - "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", "express": "^4.19.2", - "file-type": "^19.0.0", "go-get-folder-size": "^0.5.5", "helia": "^4.2.1", - "it-last": "^3.0.4", "json5": "^2.2.3", - "libp2p": "^1.5.1", "multer": "^1.4.5-lts.1", "multiformats": "^13.1.0", "pino": "^8.20.0", @@ -48,16 +30,11 @@ "devDependencies": { "@commitlint/cli": "^19.2.1", "@commitlint/config-conventional": "^19.1.0", - "@swc/core": "1.3.18", - "@types/busboy": "^1.5.3", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", - "cross-env": "^7.0.3", "eslint": "^8.57.0", - "eslint-config-google": "^0.14.0", - "eslint-plugin-jest": "^27.6.0", "gts": "^5.2.0", "husky": "^9.0.11", "lint-staged": "^15.2.2", @@ -2321,30 +2298,6 @@ "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.0.2.tgz", "integrity": "sha512-ndGqEMG1W5WkGagaqOZHpPU172AGdxr+LD15sv3WIUvT5oCFUrG1Y0CW/v2Egwj4JXEvSibaIIIqImsm98y1nA==" }, - "node_modules/@chainsafe/libp2p-gossipsub": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-gossipsub/-/libp2p-gossipsub-13.0.0.tgz", - "integrity": "sha512-2q+v429uZjMl6N3d+j9QCMj8YO0aiYvLSN1t0aTdpMXQHCHLJaaT9PtNn845B1Li7/uZjYESmikgVt8r7keH0w==", - "dependencies": { - "@libp2p/crypto": "^4.0.1", - "@libp2p/interface": "^1.1.2", - "@libp2p/interface-internal": "^1.0.7", - "@libp2p/peer-id": "^4.0.5", - "@libp2p/pubsub": "^9.0.8", - "@multiformats/multiaddr": "^12.1.14", - "denque": "^2.1.0", - "it-length-prefixed": "^9.0.4", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "multiformats": "^13.0.1", - "protons-runtime": "5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.1" - }, - "engines": { - "npm": ">=8.7.0" - } - }, "node_modules/@chainsafe/libp2p-noise": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-15.0.0.tgz", @@ -2814,22 +2767,6 @@ "progress-events": "^1.0.0" } }, - "node_modules/@helia/mfs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@helia/mfs/-/mfs-3.0.6.tgz", - "integrity": "sha512-ew30vM+oLeoYx6zdzdLZNwJ6vzCHCE1twzNMeccUPQdFdGY2x/P5+izBljPId6W4zuq8XXMD2rYOmtPUeSC5sw==", - "dependencies": { - "@helia/unixfs": "^3.0.6", - "@libp2p/interfaces": "^3.3.2", - "@libp2p/logger": "^4.0.7", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "ipfs-unixfs": "^11.1.3", - "ipfs-unixfs-exporter": "^13.5.0", - "ipfs-unixfs-importer": "^15.2.4", - "multiformats": "^13.1.0" - } - }, "node_modules/@helia/routers": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.1.0.tgz", @@ -3374,22 +3311,6 @@ "uint8arraylist": "^2.4.8" } }, - "node_modules/@libp2p/interface-connection": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-5.1.1.tgz", - "integrity": "sha512-ytknMbuuNW72LYMmTP7wFGP5ZTaUSGBCmV9f+uQ55XPcFHtKXLtKWVU/HE8IqPmwtyU8AO7veGoJ/qStMHNRVA==", - "dependencies": { - "@libp2p/interface-peer-id": "^2.0.0", - "@libp2p/interfaces": "^3.0.0", - "@multiformats/multiaddr": "^12.0.0", - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.4.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/@libp2p/interface-internal": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.2.0.tgz", @@ -3401,96 +3322,6 @@ "uint8arraylist": "^2.4.8" } }, - "node_modules/@libp2p/interface-peer-id": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", - "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", - "dependencies": { - "multiformats": "^11.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@libp2p/interface-peer-id/node_modules/multiformats": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", - "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@libp2p/interface-stream-muxer": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@libp2p/interface-stream-muxer/-/interface-stream-muxer-4.1.2.tgz", - "integrity": "sha512-dQJcn67UaAa8YQFRJDhbo4uT453z/2lCzD/ZwTk1YOqJxATXbXgVcB8dXDQFEUiUX3ZjVQ1IBu+NlQd+IZ++zw==", - "dependencies": { - "@libp2p/interface-connection": "^5.0.0", - "@libp2p/interfaces": "^3.0.0", - "@libp2p/logger": "^2.0.7", - "abortable-iterator": "^5.0.1", - "any-signal": "^4.1.1", - "it-pushable": "^3.1.3", - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.4.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@libp2p/interface-stream-muxer/node_modules/@libp2p/logger": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-2.1.1.tgz", - "integrity": "sha512-2UbzDPctg3cPupF6jrv6abQnAUTrbLybNOj0rmmrdGm1cN2HJ1o/hBu0sXuq4KF9P1h/eVRn1HIRbVIEKnEJrA==", - "dependencies": { - "@libp2p/interface-peer-id": "^2.0.2", - "@multiformats/multiaddr": "^12.1.3", - "debug": "^4.3.4", - "interface-datastore": "^8.2.0", - "multiformats": "^11.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@libp2p/interface-stream-muxer/node_modules/multiformats": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", - "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@libp2p/interface-transport": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface-transport/-/interface-transport-4.0.3.tgz", - "integrity": "sha512-jXFQ3blhFMEyQbFw/U8Glo3F/fUO5LEaX5HIdeqNpCliK+XnwTfpkcaG+WsJrcApWK4FFyUHc+GGqiWR0hAFFg==", - "dependencies": { - "@libp2p/interface-connection": "^5.0.0", - "@libp2p/interface-stream-muxer": "^4.0.0", - "@libp2p/interfaces": "^3.0.0", - "@multiformats/multiaddr": "^12.0.0", - "it-stream-types": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@libp2p/interfaces": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", - "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/@libp2p/kad-dht": { "version": "12.0.15", "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.15.tgz", @@ -3684,26 +3515,6 @@ "uint8arrays": "^5.0.3" } }, - "node_modules/@libp2p/pubsub": { - "version": "9.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-9.0.17.tgz", - "integrity": "sha512-7QOoaQ+DqPcQARUBjKAY2pE81nQ10kqfTi+QWZxhgxGFFjDAWpEnu4mELRXGAyPfo/TZ+P+6kHulMJ/JKjoUJA==", - "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-collections": "^5.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/utils": "^5.4.0", - "it-length-prefixed": "^9.0.4", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "multiformats": "^13.1.0", - "p-queue": "^8.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" - } - }, "node_modules/@libp2p/record": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.1.tgz", @@ -6080,200 +5891,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@swc/core": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.18.tgz", - "integrity": "sha512-VChk3ldLhmVoX3Hd2M3Y4j960T0lo2Zus60iZoWST6P65RVPt8BatFVVPAB9dABy1dB5zn1BCpHlH85yXVysQw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "swcx": "run_swcx.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.18", - "@swc/core-darwin-x64": "1.3.18", - "@swc/core-linux-arm-gnueabihf": "1.3.18", - "@swc/core-linux-arm64-gnu": "1.3.18", - "@swc/core-linux-arm64-musl": "1.3.18", - "@swc/core-linux-x64-gnu": "1.3.18", - "@swc/core-linux-x64-musl": "1.3.18", - "@swc/core-win32-arm64-msvc": "1.3.18", - "@swc/core-win32-ia32-msvc": "1.3.18", - "@swc/core-win32-x64-msvc": "1.3.18" - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.18.tgz", - "integrity": "sha512-4UEQ+LyzDFTszEy4LCU50h4cjVNJcNwD87aVBT/8i6YXj5dyMki/TrkIQ6Bhv7g5beg2GRncB2ndjN66r8I8+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.18.tgz", - "integrity": "sha512-DSCd7eVr+4ukffNnvhrFmUoCF0VLOXPgGmdwm6u0irLWOLtr2VZNZcf7UF+t/Y9jPKmXz3OY6lVgwtjxZhiklQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.18.tgz", - "integrity": "sha512-9dy6qJiWAls9OrBvrWbFDbjEkuOPrEP6OsKyrQWTMqLjCLwgLa3g4yC0YtPdUa/A8uyNVKtRcq+NXoKW+mP/QQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.18.tgz", - "integrity": "sha512-8FZjiUSM4JBQTD4sV7Y6BNMdo0oDlqa8xYVaAimuIBL8ixD/Fb+0GIxKdB59yKRVQyuXJRa6Pwzd7zk3wY5T0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.18.tgz", - "integrity": "sha512-0zNqfFeAHZp37lu+lTVvZKfDM10EIoYJtv9sWz+0EA5mkzwj4NtC3ialTIjcPAyJ9Oq4zBtToW2hv7qEtyBHZw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.18.tgz", - "integrity": "sha512-PA3Cc97Kc6W6RtpBLeJaoXLCRL5dJLYd2dszf+f5hGHHJybh6eXGIU0ZkZr898NUHoL8fT6Mg6I4JCNImq/yBg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.18.tgz", - "integrity": "sha512-RiZXHwED8cfD/zoBG01iY8YZtOF/8t9XHZ1JqCx9PWOMjXD3Vc8F2I7bp1Qg6ahzWEaP+2+/rqGO1kSwaJjJLw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.18.tgz", - "integrity": "sha512-G1Lu/sP+v34lwsGFreklnCdxygMLmobyLY31cNPd0i47ZwgrGowuTV34Mcqfc4AWRkayqVAIlb/WWIZ1+qemcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.18.tgz", - "integrity": "sha512-Uu+m5BPemw5ZiG6LaF+pP0qFQuIXF55wMZNa0Dbl/16hF7ci6q941MT6CqeK5LQQ52FVVqeYO5lDk5CggaA3Mw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.18.tgz", - "integrity": "sha512-9o8uFNsPmWB5FFQSDCsI/KVBSHuAILEwB/hMvbUxKtZeSWAQTm5BqbNPi6X11KJ3MdyoJn7zPejj3grL3dcd/w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -6284,15 +5901,6 @@ "@types/node": "*" } }, - "node_modules/@types/busboy": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", - "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -6775,19 +6383,6 @@ "node": ">=6.5" } }, - "node_modules/abortable-iterator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/abortable-iterator/-/abortable-iterator-5.0.1.tgz", - "integrity": "sha512-hlZ5Z8UwqrKsJcelVPEqDduZowJPBQJ9ZhBC2FXpja3lXy8X6MoI5uMzIgmrA8+3jcVnp8TF/tx+IBBqYJNUrg==", - "dependencies": { - "get-iterator": "^2.0.0", - "it-stream-types": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -8191,24 +7786,6 @@ "luxon": "~3.4.0" } }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -8500,14 +8077,6 @@ "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", "peer": true }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8777,18 +8346,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -8820,31 +8377,6 @@ "eslint": ">=4.19.1" } }, - "node_modules/eslint-plugin-jest": { - "version": "27.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", - "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "^5.10.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", - "eslint": "^7.0.0 || ^8.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, "node_modules/eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -9451,22 +8983,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-type": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.0.0.tgz", - "integrity": "sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==", - "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -14118,18 +13634,6 @@ "node": ">=8" } }, - "node_modules/peek-readable": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", - "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -15129,34 +14633,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -16208,22 +15684,6 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "peer": true }, - "node_modules/strtok3": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", - "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -16534,22 +15994,6 @@ "node": ">=0.6" } }, - "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", diff --git a/package.json b/package.json index 9eab637..3fe82b8 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,11 @@ "devDependencies": { "@commitlint/cli": "^19.2.1", "@commitlint/config-conventional": "^19.1.0", - "@swc/core": "1.3.18", - "@types/busboy": "^1.5.3", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.11.30", - "cross-env": "^7.0.3", "eslint": "^8.57.0", - "eslint-config-google": "^0.14.0", - "eslint-plugin-jest": "^27.6.0", "gts": "^5.2.0", "husky": "^9.0.11", "lint-staged": "^15.2.2", @@ -38,37 +33,19 @@ "typescript": "^5.4.3" }, "dependencies": { - "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-noise": "^15.0.0", - "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/mfs": "^3.0.6", "@helia/unixfs": "^3.0.6", - "@libp2p/autonat": "^1.0.19", "@libp2p/bootstrap": "^10.0.22", - "@libp2p/circuit-relay-v2": "^1.0.22", - "@libp2p/identify": "^2.0.0", - "@libp2p/interface-transport": "^4.0.3", - "@libp2p/kad-dht": "^12.0.15", - "@libp2p/mplex": "^10.0.22", "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-id-factory": "^4.1.1", "@libp2p/tcp": "^9.0.24", - "@libp2p/upnp-nat": "^1.0.20", - "@libp2p/webrtc": "^4.0.31", - "blockstore-core": "^4.4.1", "blockstore-fs": "^1.1.10", - "busboy": "^1.6.0", "cors": "^2.8.5", "cron": "^3.1.6", - "datastore-core": "^9.2.9", "datastore-fs": "^9.1.8", "express": "^4.19.2", - "file-type": "^19.0.0", "go-get-folder-size": "^0.5.5", "helia": "^4.2.1", - "it-last": "^3.0.4", "json5": "^2.2.3", - "libp2p": "^1.5.1", "multer": "^1.4.5-lts.1", "multiformats": "^13.1.0", "pino": "^8.20.0", From 6b56705ea0e0ecaa826366eaf4b7d3ea4493b7c3 Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 13:21:28 +0100 Subject: [PATCH 072/167] chore(package.json): upgrade dependencies --- package-lock.json | 4312 +++++++++++++++++++++------------------------ package.json | 46 +- 2 files changed, 2000 insertions(+), 2358 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1ec8b9..a5a2487 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,40 +9,40 @@ "version": "0.0.1", "license": "GPLv3", "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@helia/unixfs": "^3.0.6", - "@libp2p/bootstrap": "^10.0.22", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/tcp": "^9.0.24", - "blockstore-fs": "^1.1.10", + "@chainsafe/libp2p-noise": "^15.1.0", + "@helia/unixfs": "^3.0.7", + "@libp2p/bootstrap": "^10.1.4", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/tcp": "^9.1.4", + "blockstore-fs": "^2.0.0", "cors": "^2.8.5", - "cron": "^3.1.6", - "datastore-fs": "^9.1.8", + "cron": "^3.1.7", + "datastore-fs": "^10.0.0", "express": "^4.19.2", "go-get-folder-size": "^0.5.5", - "helia": "^4.2.1", + "helia": "^4.2.5", "json5": "^2.2.3", "multer": "^1.4.5-lts.1", - "multiformats": "^13.1.0", - "pino": "^8.20.0", - "pino-http": "^9.0.0" + "multiformats": "^13.2.2", + "pino": "^9.3.2", + "pino-http": "^10.2.0" }, "devDependencies": { - "@commitlint/cli": "^19.2.1", - "@commitlint/config-conventional": "^19.1.0", + "@commitlint/cli": "^19.4.0", + "@commitlint/config-conventional": "^19.2.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", - "@types/node": "^20.11.30", + "@types/node": "^20.14.8", "eslint": "^8.57.0", - "gts": "^5.2.0", - "husky": "^9.0.11", - "lint-staged": "^15.2.2", - "nodemon": "^3.1.0", - "pino-pretty": "^11.0.0", - "prettier": "^3.2.5", - "rimraf": "^5.0.5", - "typescript": "^5.4.3" + "gts": "^5.3.1", + "husky": "^9.1.4", + "lint-staged": "^15.2.8", + "nodemon": "^3.1.4", + "pino-pretty": "^11.2.2", + "prettier": "^3.3.3", + "rimraf": "^6.0.1", + "typescript": "^5.5.4" }, "engines": { "node": ">=20.0.0" @@ -93,11 +93,11 @@ "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -105,30 +105,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -153,12 +153,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "peer": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -168,38 +168,39 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "peer": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "peer": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -207,15 +208,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -226,19 +218,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", "semver": "^6.3.1" }, "engines": { @@ -258,12 +248,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -300,74 +290,53 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "peer": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "peer": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "peer": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -377,35 +346,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -415,14 +384,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -432,101 +401,90 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "peer": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "peer": true, "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "peer": true, - "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "peer": true, "dependencies": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "peer": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -581,10 +539,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "peer": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -593,13 +554,28 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", - "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -609,12 +585,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -624,14 +600,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -641,13 +617,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -693,13 +669,13 @@ } }, "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.1.tgz", - "integrity": "sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.7.tgz", + "integrity": "sha512-CcmFwUJ3tKhLjPdt4NP+SHMshebytF8ZTYOv5ZDpkzq2sin80Wb5vJrGt8fhPrORQCfoSa0LAxC/DW+GAC5+Hw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-export-default-from": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-default-from": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -878,12 +854,12 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.1.tgz", - "integrity": "sha512-cNXSxv9eTkGUtd0PsNMK8Yx5xeScxfpWOUAxE+ZPAXXEcAMOC3fk7LRdXq5fvpra2pLx2p1YtkAhpUbB2SwaRA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.7.tgz", + "integrity": "sha512-bTPz4/635WQ9WhwsyPdxUJDVpsi/X9BMmy/8Rf/UAlOO4jSql4CxUCjWI5PiM+jG+c4LVPTScoTw80geFj9+Bw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -905,12 +881,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz", - "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", + "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -920,12 +896,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -935,12 +911,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -974,12 +950,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1091,12 +1067,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1122,12 +1098,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1137,15 +1113,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", - "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1155,14 +1131,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", - "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1172,12 +1148,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1187,12 +1163,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1202,13 +1178,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1218,13 +1194,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1235,18 +1211,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", + "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.0", "globals": "^11.1.0" }, "engines": { @@ -1266,13 +1240,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1282,12 +1256,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1297,13 +1271,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1313,12 +1287,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1327,13 +1301,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1344,13 +1334,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "peer": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1360,12 +1350,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1376,13 +1366,13 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz", - "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.2.tgz", + "integrity": "sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-flow": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-flow": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1392,13 +1382,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1408,14 +1398,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -1425,12 +1415,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1441,12 +1431,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1456,12 +1446,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1472,12 +1462,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1487,13 +1477,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1503,14 +1493,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1520,15 +1510,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "peer": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1538,13 +1528,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1554,13 +1544,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1570,12 +1560,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1585,12 +1575,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1601,12 +1591,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -1617,15 +1607,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", - "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.5" + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1635,13 +1625,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1651,12 +1641,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -1667,13 +1657,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -1684,12 +1674,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1699,13 +1689,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1715,14 +1705,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", - "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1733,12 +1723,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1748,12 +1738,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", - "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1763,16 +1753,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -1782,12 +1772,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz", - "integrity": "sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1797,12 +1787,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", - "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1812,12 +1802,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1828,12 +1818,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1843,13 +1833,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", - "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.1", "babel-plugin-polyfill-regenerator": "^0.6.1", @@ -1872,12 +1862,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1887,13 +1877,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1903,12 +1893,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1918,12 +1908,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1933,12 +1923,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1948,15 +1938,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz", - "integrity": "sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/plugin-syntax-typescript": "^7.24.1" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1966,12 +1957,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1981,13 +1972,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1997,13 +1988,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2013,13 +2004,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2029,27 +2020,28 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", - "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.1", - "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2061,59 +2053,60 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.1", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.24.1", - "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.5", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-classes": "^7.24.5", - "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.5", - "@babel/plugin-transform-dotall-regex": "^7.24.1", - "@babel/plugin-transform-duplicate-keys": "^7.24.1", - "@babel/plugin-transform-dynamic-import": "^7.24.1", - "@babel/plugin-transform-exponentiation-operator": "^7.24.1", - "@babel/plugin-transform-export-namespace-from": "^7.24.1", - "@babel/plugin-transform-for-of": "^7.24.1", - "@babel/plugin-transform-function-name": "^7.24.1", - "@babel/plugin-transform-json-strings": "^7.24.1", - "@babel/plugin-transform-literals": "^7.24.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-member-expression-literals": "^7.24.1", - "@babel/plugin-transform-modules-amd": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-modules-systemjs": "^7.24.1", - "@babel/plugin-transform-modules-umd": "^7.24.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.24.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.5", - "@babel/plugin-transform-object-super": "^7.24.1", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.5", - "@babel/plugin-transform-parameters": "^7.24.5", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.5", - "@babel/plugin-transform-property-literals": "^7.24.1", - "@babel/plugin-transform-regenerator": "^7.24.1", - "@babel/plugin-transform-reserved-words": "^7.24.1", - "@babel/plugin-transform-shorthand-properties": "^7.24.1", - "@babel/plugin-transform-spread": "^7.24.1", - "@babel/plugin-transform-sticky-regex": "^7.24.1", - "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.5", - "@babel/plugin-transform-unicode-escapes": "^7.24.1", - "@babel/plugin-transform-unicode-property-regex": "^7.24.1", - "@babel/plugin-transform-unicode-regex": "^7.24.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.4", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -2133,14 +2126,14 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.1.tgz", - "integrity": "sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.7.tgz", + "integrity": "sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-transform-flow-strip-types": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-flow-strip-types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2164,16 +2157,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", - "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-syntax-jsx": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-typescript": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2183,9 +2176,9 @@ } }, "node_modules/@babel/register": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", - "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", "peer": true, "dependencies": { "clone-deep": "^4.0.1", @@ -2208,9 +2201,9 @@ "peer": true }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "peer": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2226,33 +2219,30 @@ "peer": true }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "peer": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2270,13 +2260,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "peer": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2299,16 +2289,16 @@ "integrity": "sha512-ndGqEMG1W5WkGagaqOZHpPU172AGdxr+LD15sv3WIUvT5oCFUrG1Y0CW/v2Egwj4JXEvSibaIIIqImsm98y1nA==" }, "node_modules/@chainsafe/libp2p-noise": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-15.0.0.tgz", - "integrity": "sha512-O8Y/WVU4J/qrnG72jwVhbmdXiBzv1dT9B3PMClCRmZ9z/5vVPEGRVXE/SVYeGF3bNuBTLoh+F+GaKG/9UHlMhg==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-15.1.0.tgz", + "integrity": "sha512-84S/Uk7ZZRYpSlE5d1odMmQTl5g5Da8etgcf4QI7arTAHkvBs3il7yGHIPt4wV4EV0qIMG+XjdGIYihRXfI2/w==", "dependencies": { "@chainsafe/as-chacha20poly1305": "^0.1.0", "@chainsafe/as-sha256": "^0.4.1", "@libp2p/crypto": "^4.0.0", - "@libp2p/interface": "^1.0.0", + "@libp2p/interface": "^1.5.0", "@libp2p/peer-id": "^4.0.0", - "@noble/ciphers": "^0.4.0", + "@noble/ciphers": "^0.5.1", "@noble/curves": "^1.1.0", "@noble/hashes": "^1.3.1", "it-length-prefixed": "^9.0.1", @@ -2349,15 +2339,15 @@ } }, "node_modules/@commitlint/cli": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.3.0.tgz", - "integrity": "sha512-LgYWOwuDR7BSTQ9OLZ12m7F/qhNY+NpAyPBgo4YNMkACE7lGuUnuQq1yi9hz1KA4+3VqpOYl8H1rY/LYK43v7g==", + "version": "19.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.4.0.tgz", + "integrity": "sha512-sJX4J9UioVwZHq7JWM9tjT5bgWYaIN3rC4FP7YwfEwBYiIO+wMyRttRvQLNkow0vCdM0D67r9NEWU0Ui03I4Eg==", "dev": true, "dependencies": { "@commitlint/format": "^19.3.0", "@commitlint/lint": "^19.2.2", - "@commitlint/load": "^19.2.0", - "@commitlint/read": "^19.2.1", + "@commitlint/load": "^19.4.0", + "@commitlint/read": "^19.4.0", "@commitlint/types": "^19.0.3", "execa": "^8.0.1", "yargs": "^17.0.0" @@ -2463,9 +2453,9 @@ } }, "node_modules/@commitlint/load": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.2.0.tgz", - "integrity": "sha512-XvxxLJTKqZojCxaBQ7u92qQLFMMZc4+p9qrIq/9kJDy8DOrEa7P1yx7Tjdc2u2JxIalqT4KOGraVgCE7eCYJyQ==", + "version": "19.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.4.0.tgz", + "integrity": "sha512-I4lCWaEZYQJ1y+Y+gdvbGAx9pYPavqZAZ3/7/8BpWh+QjscAn8AjsUpLV2PycBsEx7gupq5gM4BViV9xwTIJuw==", "dev": true, "dependencies": { "@commitlint/config-validator": "^19.0.3", @@ -2507,9 +2497,9 @@ } }, "node_modules/@commitlint/read": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.2.1.tgz", - "integrity": "sha512-qETc4+PL0EUv7Q36lJbPG+NJiBOGg7SSC7B5BsPWOmei+Dyif80ErfWQ0qXoW9oCh7GTpTNRoaVhiI8RbhuaNw==", + "version": "19.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.4.0.tgz", + "integrity": "sha512-r95jLOEZzKDakXtnQub+zR3xjdnrl2XzerPwm7ch1/cc5JGq04tyaNpa6ty0CRCWdVrk4CZHhqHozb8yZwy2+g==", "dev": true, "dependencies": { "@commitlint/top-level": "^19.0.0", @@ -2605,9 +2595,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2683,12 +2673,12 @@ } }, "node_modules/@helia/bitswap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.0.tgz", - "integrity": "sha512-/RF3vxEHM6KWliLYYvukf+z388SWkJU1NJkdXdbzU7W0YjyCmanYAusOXVf5sfHIiOtK+YFO6oJzy+kcp+eySQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.3.tgz", + "integrity": "sha512-nCszNky/kOxaj1CZrZbzzFz6Fn7uNWigudn4KaLV+ZuFx6xyB8+t6aCmjKQ59me0WA3mwkmHkPtUOi9Y75zLSw==", "dependencies": { - "@helia/interface": "^4.3.0", - "@helia/utils": "^0.3.0", + "@helia/interface": "^4.3.1", + "@helia/utils": "^0.3.3", "@libp2p/interface": "^1.1.2", "@libp2p/logger": "^4.0.5", "@libp2p/peer-collections": "^5.1.6", @@ -2714,13 +2704,13 @@ } }, "node_modules/@helia/block-brokers": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.0.tgz", - "integrity": "sha512-f93Lklv+ZyySxIAiZDG6p2kRF+OV3hdhVUAyWNK6TWpyxuFNj45cYQci9nz/ZTmEHqg2ylswwiq4lEGml3WS7Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.3.tgz", + "integrity": "sha512-xSnmWv1jgeBJURcdF/KFOHEa1CQygk3jm1qxYPYqnMWxRXhsFG5Qi13EA+QfslCXyywfxbr4yJWleUUV2Mw4oQ==", "dependencies": { - "@helia/bitswap": "^1.1.0", - "@helia/interface": "^4.3.0", - "@helia/utils": "^0.3.0", + "@helia/bitswap": "^1.1.3", + "@helia/interface": "^4.3.1", + "@helia/utils": "^0.3.3", "@libp2p/interface": "^1.1.4", "@libp2p/utils": "^5.2.6", "@multiformats/multiaddr": "^12.2.1", @@ -2754,9 +2744,9 @@ } }, "node_modules/@helia/interface": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.3.0.tgz", - "integrity": "sha512-gaWQSVGIcOkK0Wx12qUiUMFLPI8AjQCYaf4dpZdciSYPNfKOtPpW54QGzgrQYBcvBgPzg16hnnvDGwLeL2WlAQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.3.1.tgz", + "integrity": "sha512-jvuUUAQbLMhvMt+Lon7mJfJt5xJWiwn8aXtI9fHBpMNMsrKJOtefxAzwSr18xe+C9bMdOyRtocj2eFhs74bQ8w==", "dependencies": { "@libp2p/interface": "^1.1.4", "@multiformats/dns": "^1.0.1", @@ -2768,12 +2758,12 @@ } }, "node_modules/@helia/routers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.1.0.tgz", - "integrity": "sha512-WwHcvN8No6+3alTrxNXtTJdqfyX0Ymm41u5yL+1YFdsvRKKy5RLxNlgla34kFJaHuQGPFdROYP/9X4UaHpvcIQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.1.1.tgz", + "integrity": "sha512-LsEkUAno4PiQ1cmwxFnYWy5xQicpzjXyYeRJEGLjAnAXin8hq0JPkEouZx8vTlmSf1Rde4RSaTu3+3H1qj5ccw==", "dependencies": { "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.3.0", + "@helia/interface": "^4.3.1", "@libp2p/interface": "^1.1.4", "@multiformats/uri-to-multiaddr": "^8.0.0", "ipns": "^9.0.0", @@ -2784,11 +2774,11 @@ } }, "node_modules/@helia/unixfs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.6.tgz", - "integrity": "sha512-x3hoHUjwNk2M9Hg0Z9sQE8O8S2rcj13O9dYexGHckxdpeEPhNOwRWT1cMpY8NTK0vpuIs0TvlQm9gYfHm94Clw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.7.tgz", + "integrity": "sha512-5fBXYk9t6Iu/DnTQZ//sn0MhM00AnK+VYEkfXdReoBUdzVFrOFW/bihT4sgLfYCq86+TShdrU1aDLkbFiN6FnQ==", "dependencies": { - "@helia/interface": "^4.3.0", + "@helia/interface": "^4.3.1", "@ipld/dag-pb": "^4.1.0", "@libp2p/interface": "^1.1.4", "@libp2p/logger": "^4.0.7", @@ -2810,11 +2800,11 @@ } }, "node_modules/@helia/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-lCOd5DsimmUrp9+TyxWDkvhDFvI+Hgn7PevQHGya3AwmJXxsuCY6FtRgd7YkdaoqAm4+q7L710UnUtp5AmHlVA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.3.tgz", + "integrity": "sha512-iCS8dJRULgltfx7aLLoqfjWUpqBWrTyouCToB9RgHpNs4lHcIsZn/VSbHyjC4NUF9nIWsxhH/3UAdvWl23HEgw==", "dependencies": { - "@helia/interface": "^4.3.0", + "@helia/interface": "^4.3.1", "@ipld/dag-cbor": "^9.2.0", "@ipld/dag-json": "^10.2.0", "@ipld/dag-pb": "^4.1.0", @@ -2847,6 +2837,7 @@ "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", @@ -2874,12 +2865,13 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@ipld/dag-cbor": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz", - "integrity": "sha512-N14oMy0q4gM6OuZkIpisKe0JBSjf1Jb39VI+7jMLiWX9124u1Z3Fdj/Tag1NA0cVxxqWDh0CqsjcVfOKtelPDA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.1.tgz", + "integrity": "sha512-nyY48yE7r3dnJVlxrdaimrbloh4RokQaNRdI//btfTkcTEZbpmSrbYcBQ4VKTf8ZxXAOUJy4VsRpkJo+y9RTnA==", "dependencies": { "cborg": "^4.0.0", "multiformats": "^13.1.0" @@ -2890,9 +2882,9 @@ } }, "node_modules/@ipld/dag-json": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.0.tgz", - "integrity": "sha512-O9YLUrl3d3WbVz7v1WkajFkyfOLEe2Fep+wor4fgVe0ywxzrivrj437NiPcVyB+2EDdFn/Q7tCHFf8YVhDf8ZA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.2.tgz", + "integrity": "sha512-NnU8HdHKwAoGyrW3S09NMa8aZw0tImLRyR64hoafpLpDpAbA9g1+fb24JsdlugbL4sXUQVwDVA+qK4Ud8V83lA==", "dependencies": { "cborg": "^4.0.0", "multiformats": "^13.1.0" @@ -2903,9 +2895,9 @@ } }, "node_modules/@ipld/dag-pb": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.0.tgz", - "integrity": "sha512-LJU451Drqs5zjFm7jI4Hs3kHlilOqkjcSfPiQgVsZnWaYb2C7YdfhnclrVn/X+ucKejlU9BL3+gXFCZUXkMuCg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.2.tgz", + "integrity": "sha512-BSztO4l3C+ya9HjCaQot26Y4AVsqIKtnn6+23ubc1usucnf6yoTBme18oCCdM6gKBMxuPqju5ye3lh9WEJsdeQ==", "dependencies": { "multiformats": "^13.1.0" }, @@ -3172,9 +3164,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "peer": true }, "node_modules/@jridgewell/trace-mapping": { @@ -3193,117 +3185,117 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@libp2p/autonat": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.0.19.tgz", - "integrity": "sha512-TyptjlqUvxeRIE8wrr54j3vAteSuJof1U1ryHDLjoM3fXk64a8Ie9Yc1PkcOEMXBJ7oiAEoxe0LmQ+VW89q+hQ==", - "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-id-factory": "^4.1.1", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", - "it-first": "^3.0.4", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.1.4.tgz", + "integrity": "sha512-yt/sUisqBLZPM/0lvpRo0enIEIbdyuSraIu4xRlGkdqqAXSo9T6CJppXxNi9VKTmlmD39WYYNP41Ilt3SvQhRQ==", + "dependencies": { + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", + "it-first": "^3.0.6", "it-length-prefixed": "^9.0.4", - "it-map": "^3.0.5", - "it-parallel": "^3.0.6", + "it-map": "^3.1.0", + "it-parallel": "^3.0.7", "it-pipe": "^3.0.1", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8" } }, "node_modules/@libp2p/bootstrap": { - "version": "10.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.0.22.tgz", - "integrity": "sha512-qAEBvmsksPnuVYMF2Khfvw3Nvatv+VHvrW36A1LQM20nIZ4D25c0t0D9xUyMZd87R4/T+fqCPsW8JKG+6XQH7w==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.1.4.tgz", + "integrity": "sha512-bt4Tz/DMKnlvZhF4d9Nkv/K7qgnoDBM/N0zwjA/aQg1vSq54/Szc2K3cIDAv9/r9dM4ckDRmIZZENr/bwcIIKw==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-id": "^4.1.1", + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-id": "^4.2.3", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.1" + "@multiformats/multiaddr": "^12.2.3" } }, "node_modules/@libp2p/circuit-relay-v2": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.0.22.tgz", - "integrity": "sha512-N913QHAsjIkOtS3cbr1oANG8UbUMYNAxOkgNkTzBNTqTiNbJQxOellK00DpX+quEOP0T+YUc7EnCSrU5wtvL+g==", - "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-collections": "^5.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-record": "^7.0.16", - "@libp2p/utils": "^5.4.0", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.1.4.tgz", + "integrity": "sha512-WLiisIvRWVSJLzb4MO1aqDOD2A7s99OgpfeKKt9nxSaq34WeQS9aGN9YxSczYkum9hBTdnLJZZDQrd6SId6NbQ==", + "dependencies": { + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/peer-collections": "^5.2.8", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/peer-record": "^7.0.24", + "@libp2p/utils": "^5.4.8", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr": "^12.2.3", "any-signal": "^4.1.1", - "it-protobuf-stream": "^1.1.2", + "it-protobuf-stream": "^1.1.3", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", "p-defer": "^4.0.1", - "p-retry": "^6.2.0", + "progress-events": "^1.0.0", "protons-runtime": "^5.4.0", + "race-signal": "^1.0.2", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/crypto": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.1.tgz", - "integrity": "sha512-ju7P18Swd0CAa4ud31PBQdxPN++6dFl49RrH4F4yh5HYA3hXL18nbioH18iJ4WAahzjNQsdRMYzTQLWAUZPAkg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.8.tgz", + "integrity": "sha512-j8d1gOS6ypt6CSYgBTJKSTGsht3SFPkhfOkGND6sUCl1w9pRslDlvFnU9UrNHLMiaNmVHaeS9B2IKzKMbLX92Q==", "dependencies": { - "@libp2p/interface": "^1.3.1", + "@libp2p/interface": "^1.6.3", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "asn1js": "^3.0.5", "multiformats": "^13.1.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/dcutr": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.0.19.tgz", - "integrity": "sha512-EIgeQ5ZFpL6OYqd4PtJyV6Tc8VWu/s2CYeDub1pPZz06UiHfKYO3pDiCEeb9tr740O//VRK127yXaXHdtlV+kA==", - "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.1.4.tgz", + "integrity": "sha512-NjIiAoi5q2Y2q0eON+VCdm8ec2Be1Q9q1vcL6z7RyBWwIkk6PPy8RvBaI6GcGKjk251jLrN6vz2yoL7vWRht2A==", + "dependencies": { + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", + "@multiformats/multiaddr-matcher": "^1.2.1", "delay": "^6.0.0", - "it-protobuf-stream": "^1.1.2", + "it-protobuf-stream": "^1.1.3", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8" } }, "node_modules/@libp2p/identify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.0.0.tgz", - "integrity": "sha512-1vdXRHIkENE+iyN4yXo00ia02I63UCm4FVeM5pQZZxbeZz4+1Eo8dJuDYU9h0SFTghhkIYeyTT6CnkqfvMcy8A==", - "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-record": "^7.0.16", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.1.4.tgz", + "integrity": "sha512-w2JkmoZkXT3OJnTuyBPlEBicj1J1n90pcusZbs+YIiJ+mhsOvNKnQhJgCG8Q6wnTimcBjinqhRRp0hiaHqX7VQ==", + "dependencies": { + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/peer-record": "^7.0.24", + "@multiformats/multiaddr": "^12.2.3", + "@multiformats/multiaddr-matcher": "^1.2.1", "it-drain": "^3.0.7", "it-parallel": "^3.0.7", - "it-protobuf-stream": "^1.1.2", + "it-protobuf-stream": "^1.1.3", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3", + "uint8arrays": "^5.1.0", "wherearewe": "^2.0.1" } }, "node_modules/@libp2p/interface": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.3.1.tgz", - "integrity": "sha512-KJoYP6biAgIHUU3pxaixaaYCvIHZshzXetxfoNigadAZ3hCGuwpdFhk7IABEaI3RgadOOYUwW3MXPbL+cxnXVQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.6.3.tgz", + "integrity": "sha512-Tm8W5Q2FsjcSdeA5BvP/GTUq/lp3SjeW6GPmWbbIasBJdv67UGHahu8YDFTME90IxTijnikkfGNkOPsd/4UuvA==", "dependencies": { - "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr": "^12.2.3", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", @@ -3312,42 +3304,42 @@ } }, "node_modules/@libp2p/interface-internal": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.2.0.tgz", - "integrity": "sha512-7jrVw3I9B7l6TiKIq199sn0y1BlPVfFGrYE8h02RLLEEuq64q4Gx+/UGmCo7aD3E4+wCcrCLP37O4LZxjAhWUQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.3.3.tgz", + "integrity": "sha512-xng1cBGDhSZmCu0kUEv3363oSgpoPmvpDr11wumhKNEgGOwBVbEr0v7serYcrlORxFmyUBW8y98CkPRJxskPLA==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-collections": "^5.2.0", - "@multiformats/multiaddr": "^12.2.1", + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-collections": "^5.2.8", + "@multiformats/multiaddr": "^12.2.3", + "progress-events": "^1.0.0", "uint8arraylist": "^2.4.8" } }, "node_modules/@libp2p/kad-dht": { - "version": "12.0.15", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.0.15.tgz", - "integrity": "sha512-BY4/0hok8PhvaOL31AvX0CkQC3j0yUgahzMNr2gqLI9wbCiuiOqzo4FSuJ0gIkv8I9XcGS6L+CfZ44SpsUZPEA==", - "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-collections": "^5.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/record": "^4.0.1", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.1.4.tgz", + "integrity": "sha512-TWg3WB/Ghqz2vVOBxZz3PD0gDw1j24GlcQiJw5AieXMY1va8ZxkYyJtpHluhPpjLnlI4zxNLbE30A05Tp6XD4w==", + "dependencies": { + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/peer-collections": "^5.2.8", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/record": "^4.0.4", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", "any-signal": "^4.1.1", "hashlru": "^2.3.0", "interface-datastore": "^8.2.11", - "it-drain": "^3.0.5", - "it-length": "^3.0.4", + "it-drain": "^3.0.7", + "it-length": "^3.0.6", "it-length-prefixed": "^9.0.4", - "it-map": "^3.0.5", - "it-merge": "^3.0.3", - "it-parallel": "^3.0.6", + "it-map": "^3.1.0", + "it-merge": "^3.0.5", + "it-parallel": "^3.0.7", "it-pipe": "^3.0.1", - "it-protobuf-stream": "^1.1.2", - "it-pushable": "^3.2.3", - "it-take": "^3.0.4", + "it-protobuf-stream": "^1.1.3", + "it-take": "^3.0.5", "multiformats": "^13.1.0", "p-defer": "^4.0.1", "p-event": "^6.0.1", @@ -3357,213 +3349,215 @@ "race-signal": "^1.0.2", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/keychain": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.0.14.tgz", - "integrity": "sha512-uTQmFHqo5IGMDVQJz6Hb9ubg0pV7LwL/S6tGK1CCz46lanAb5Harb4EEMGcv5p4URrEpQ4TheatGnHyDoX5Mug==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.1.4.tgz", + "integrity": "sha512-nYMJ1RAuKP7WqtVocZByVyro57ZqLoKrDhC2+VDleFtco1FrTFxqrF7I5Hk6vn9lz94eIcG9ZCh9geRKEs5ecg==", "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-id": "^4.1.1", + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-id": "^4.2.3", "interface-datastore": "^8.2.11", "merge-options": "^3.0.4", "multiformats": "^13.1.0", "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/logger": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.12.tgz", - "integrity": "sha512-eSiHY06Zijq6b1rMBob/ZuGBEavFm8IPNPEzeOU3JrBU7v/OV8Da0FesbemtkudZVPRi8mqRoOiIwmWn0mObng==", + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.19.tgz", + "integrity": "sha512-VKpIMbjzs60AaTezh55iEDPJ0W2icbkJkBXSlAMycCT4C+RYxOTRgevasw3mDB6+Lj9etM0nfa4vutoG4fsYCw==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@multiformats/multiaddr": "^12.2.1", - "debug": "^4.3.4", + "@libp2p/interface": "^1.6.3", + "@multiformats/multiaddr": "^12.2.3", "interface-datastore": "^8.2.11", - "multiformats": "^13.1.0" + "multiformats": "^13.1.0", + "weald": "^1.0.2" } }, "node_modules/@libp2p/mdns": { - "version": "10.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.0.22.tgz", - "integrity": "sha512-4ZWQeYhZaa4G4l+f4/TXMQVgGAWoXzklpzkfbFzK17bVwH3ocCKvcLrG+gYMG8hiVIjLvmx5yT3ahUTN1aFmtQ==", - "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.1.4.tgz", + "integrity": "sha512-OlvfOGdNw9jPpTwgS9TYskNNn8kiqVDAIT42XOL9aLIXlX6Uqx1LlKphoWUxL01TsSIBUX5asKdnbGPpV/qqLA==", + "dependencies": { + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", "@types/multicast-dns": "^7.2.4", "dns-packet": "^5.6.1", "multicast-dns": "^7.2.5" } }, "node_modules/@libp2p/mplex": { - "version": "10.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.0.22.tgz", - "integrity": "sha512-1lpREL+EHeE+/WGKZwKHIkGIuWPmgFrwWtlhvYu0xrceVCgjlDlCE3mJuN9hzN0IPr0A3278yahiVCCX9zO33A==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.1.4.tgz", + "integrity": "sha512-DxRAeixdbGVEGV+5ekp0C63NyVkKv0bOYkx/dVozcWDHmfClTD070KOhF3gIplKAVInC3FYaO3ahWzMdrD/J6g==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/utils": "^5.4.0", + "@libp2p/interface": "^1.6.3", + "@libp2p/utils": "^5.4.8", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/multistream-select": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.9.tgz", - "integrity": "sha512-Cs7KgIZXmHnTbtbK0nHVezgghCuo/8IjdioeViXyljTe2/ye4lds86lSkuZTEEbggM/4djT0fBdvqAJ2F5rhVQ==", + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.16.tgz", + "integrity": "sha512-0IUecYYu++gimGRzG324A6+NE9Vk/6Wvmp2xegfLjUZRegpFbHup3UvFEGxTkpHmsoswJMMTPHQyCNJFga+Z7Q==", "dependencies": { - "@libp2p/interface": "^1.3.1", + "@libp2p/interface": "^1.6.3", "it-length-prefixed": "^9.0.4", - "it-length-prefixed-stream": "^1.1.6", + "it-length-prefixed-stream": "^1.1.7", "it-stream-types": "^2.0.1", "p-defer": "^4.0.1", "race-signal": "^1.0.2", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/peer-collections": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.0.tgz", - "integrity": "sha512-I8fXe+3ToYJaXXodddEtj1Ix2gyfz46NzL5i47e776wwCZhtQqjA1kbhTkO8bQogHYSub0SEWxsjm1HL9dtSYw==", + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.8.tgz", + "integrity": "sha512-dIakIFms3GgFYg4lVgl9afAm+OAxGTofz4KdFnVrlNW1z6VFhTbnRtvTweR6woSr2es1+Fu6WutmZ+62Fn0iOQ==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/utils": "^5.4.0" + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/utils": "^5.4.8" } }, "node_modules/@libp2p/peer-id": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.1.1.tgz", - "integrity": "sha512-Fu25drhhdc5JDWb3x33u+5p9I3F2tmYrlx2aiwD5Kv70RLKC9k7B1m4iywBkFE82QKUPtT21IrVYlwsm46znqQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.2.3.tgz", + "integrity": "sha512-hRqPzcYOz/5q6QvHYdmPMGeFZCjC/9qxQ/+jstSDMnY1DuKEXCre2+tCpG9OeRAFyPBbs5isfaqbY3zNZV2pqA==", "dependencies": { - "@libp2p/interface": "^1.3.1", + "@libp2p/interface": "^1.6.3", "multiformats": "^13.1.0", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/peer-id-factory": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.1.1.tgz", - "integrity": "sha512-8/tRsKQLtgXmM8KklE1XB5VFfHytnbhibH/tRMN9CFZWkpkK1KmFgMxN8t9jZ2+zSK1ozZQQi4jyvkYEyLi5zQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.2.3.tgz", + "integrity": "sha512-4ryh3M4UH+nwlM1o3y3iCpW2jTxjj+Hgb6UNI1nFxZGLA6MI+En8kKiroa970xiVvsA+Xmas8B9gWeMFrZKBKg==", "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-id": "^4.1.1", + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-id": "^4.2.3", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/peer-record": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.16.tgz", - "integrity": "sha512-U6oDKvz40CDSgQkbd7FIORJFB8XCPJ+At4i4JFNtyKX/c34nPINTPCH0lL6exsTnus8L2XHhRC9Hh9zfMyXdNg==", - "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.24.tgz", + "integrity": "sha512-2LYvLqr3XDjA5u2LGD/3IvdgdrwOYdMzQnzr612/Q+yVS+36CN0dAIqU74OEWx+XeeUJSBQnQ0ryBQq/arbXuA==", + "dependencies": { + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", "protons-runtime": "^5.4.0", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/peer-store": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.0.17.tgz", - "integrity": "sha512-LHo63U8dbeqrdohc19h4M9qkI7iDTpIHG+kLv2A2lNGwrR22op0mk0ARRPUTi8ZBWvLg1Q3cpXih5JBAdU2n7w==", - "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-collections": "^5.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-record": "^7.0.16", - "@multiformats/multiaddr": "^12.2.1", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.1.4.tgz", + "integrity": "sha512-D/lqEaWZV8xEBQ7b0ZEFYtaa6+LjXjs+PRrFpmnvyuz86o9KQedVoJPufT4g0nKIeQZUtdRfkzWNnwD+fru64g==", + "dependencies": { + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-collections": "^5.2.8", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/peer-record": "^7.0.24", + "@multiformats/multiaddr": "^12.2.3", "interface-datastore": "^8.2.11", - "it-all": "^3.0.4", + "it-all": "^3.0.6", "mortice": "^3.0.4", "multiformats": "^13.1.0", "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/ping": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.0.17.tgz", - "integrity": "sha512-tfbQQOCM+yNSFfdxzOnUBMjXpww47ntT47mgvsfUp/DWHo/3qRaVSmDWHNP1mMSWa30TFcoMUUL2ndZjSf/rMg==", - "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@multiformats/multiaddr": "^12.2.1", - "it-first": "^3.0.4", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.1.4.tgz", + "integrity": "sha512-biwYtLaEHky2hsLzVZnPzrwME2WwaMu2MG+poKepxS36Ib9EM7XPGVUjpoafCQBM+Z67AMiFpKVtk3k9Kb24Gw==", + "dependencies": { + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@multiformats/multiaddr": "^12.2.3", + "it-first": "^3.0.6", "it-pipe": "^3.0.1", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/record": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.1.tgz", - "integrity": "sha512-hflqTiuuMI7GzmTyvycv7y9wvR/xePD/06gcY2ePnWkA1jWlIuebegEbs6+E6MSHQRnmnfecpeZDVRFOQVUSEw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.4.tgz", + "integrity": "sha512-wEEeHXGNIcc8HtGbgGMuSHbboUWMxKG7OxALFwkE+KACgfRJZTESOp6XIdZnyC0r9lfEFsjF01pFKBTzoBmWEQ==", "dependencies": { "protons-runtime": "^5.4.0", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/tcp": { - "version": "9.0.24", - "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.0.24.tgz", - "integrity": "sha512-cv5Kp0z/MOKJNS1OAgYGQNHfXuH1/mdPkUl/3ifjlj1+LMOnWalXm/WM13l2gDCQcwSsEYMRt4wKGcja9J+BTQ==", + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.1.4.tgz", + "integrity": "sha512-0j/aYdMFLhLWUodtyuBt5+smzkmXCiczOxNOwKyTVUuoMMcAPCLxACqCy8213qiwpJ3qP5ZTTTs/JJMgWRC7ag==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/utils": "^5.4.0", + "@libp2p/interface": "^1.6.3", + "@libp2p/utils": "^5.4.8", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr": "^12.2.3", "@types/sinon": "^17.0.3", - "stream-to-it": "^1.0.0" + "progress-events": "^1.0.0", + "stream-to-it": "^1.0.1" } }, "node_modules/@libp2p/upnp-nat": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.0.20.tgz", - "integrity": "sha512-Mzcqsu0Ylg2nHk3aiRafFNE1U09vIBvzszD32+RavK18OdC8Heh7OweCtWQNUpyHehrHZAw7Rn3XT+f1aVVaFQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.2.4.tgz", + "integrity": "sha512-oI0yAg7GKpiPy4u2u/34wYPJ/CLBraIA5dUh21ZGy3TH88eaTOYfyHSfGw9AI0IT/012xxkpqbA+h5+V+qDoBQ==", "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.13", - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", "wherearewe": "^2.0.1" } }, "node_modules/@libp2p/utils": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.0.tgz", - "integrity": "sha512-tEBhg5g06vQbR+x51U2N5xAWFNrQpVIVnJZX8urrKcxTRQnJk57Uka2eTEtBj8AxgQ0DDC7a5r2km2X50ja/EA==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.8.tgz", + "integrity": "sha512-3ysAjrheDvcrUd8RWOCwa6Hm+5FMsYOlPq9IPllfD0jOyHO2RHc/huvAmjBg/PWrTdUyx1nWQf9wmdQFgTDUDQ==", "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/logger": "^4.0.12", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/logger": "^4.0.19", + "@multiformats/multiaddr": "^12.2.3", + "@multiformats/multiaddr-matcher": "^1.2.1", "@sindresorhus/fnv1a": "^3.1.0", "@types/murmurhash3js-revisited": "^3.0.3", + "any-signal": "^4.1.1", "delay": "^6.0.0", "get-iterator": "^2.0.1", "is-loopback-addr": "^2.0.2", @@ -3572,83 +3566,81 @@ "murmurhash3js-revisited": "^3.0.0", "netmask": "^2.0.2", "p-defer": "^4.0.1", - "race-event": "^1.2.0", + "race-event": "^1.3.0", "race-signal": "^1.0.2", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/webrtc": { - "version": "4.0.31", - "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.0.31.tgz", - "integrity": "sha512-+MIQ+qv2j0H2iOOnnyniiYpDbEBq1s7gna+ndElV5A7cimv+4OXdCu8CcmtGdfPvrUbZxFYKmSvbcBF87YLZcw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.1.4.tgz", + "integrity": "sha512-vX6FctslugUzZiZNQ8+uNAYN4ZDunnqcecUog0KOonySI8XAJs6ZA9Y2giCoSbkmaAP80gSRp8gGfuri8puLYw==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/utils": "^5.4.0", + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/utils": "^5.4.8", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", + "@multiformats/multiaddr": "^12.2.3", + "@multiformats/multiaddr-matcher": "^1.2.1", "detect-browser": "^5.3.0", "it-length-prefixed": "^9.0.4", - "it-protobuf-stream": "^1.1.2", + "it-protobuf-stream": "^1.1.3", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", - "multihashes": "^4.0.3", - "node-datachannel": "^0.8.0", + "node-datachannel": "^0.11.0", "p-defer": "^4.0.1", "p-event": "^6.0.1", "p-timeout": "^6.1.2", + "progress-events": "^1.0.0", "protons-runtime": "^5.4.0", "race-signal": "^1.0.2", - "react-native-webrtc": "^118.0.3", + "react-native-webrtc": "^118.0.7", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/websockets": { - "version": "8.0.22", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.0.22.tgz", - "integrity": "sha512-p7vWeQ4Ibg12eeirNatfnTkJWgN+fmoTfp1zqrDLn1j4GE/Ga9z07IPmGjuPBYWr1yiLFhXOzDhWrmaCpOm6zA==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.1.4.tgz", + "integrity": "sha512-VWVgIo8t4VTzTI+orfGQxeHSXNWQL6dF//nH2p3nI5Z0saR6lrv5pNTsXbIhrZukGRFGgSwqGsd8bGhEohKNrQ==", "dependencies": { - "@libp2p/interface": "^1.3.1", - "@libp2p/utils": "^5.4.0", + "@libp2p/interface": "^1.6.3", + "@libp2p/utils": "^5.4.8", "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.1", + "@multiformats/multiaddr": "^12.2.3", "@multiformats/multiaddr-to-uri": "^10.0.1", "@types/ws": "^8.5.10", "it-ws": "^6.1.1", "p-defer": "^4.0.1", + "progress-events": "^1.0.0", + "race-signal": "^1.0.2", "wherearewe": "^2.0.1", - "ws": "^8.16.0" + "ws": "^8.17.0" } }, "node_modules/@libp2p/webtransport": { - "version": "4.0.30", - "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.0.30.tgz", - "integrity": "sha512-d2yhK1Lis/0FcgoPU98MTLK0wpytXKOAP9XFfBuAbQMQ3qknPMk2HvXvA6R/U3DbNzPaJ8iHOfK9tUwzOyFCiA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.1.4.tgz", + "integrity": "sha512-gbQmEZIrDgdHUMVp2NYKrnnK6slMMoB+k9Pd5uTd/mD6kvhgKBKWmWkEX++CUDlv4j78E6tgQEjQ4xNbnl79+A==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.3.1", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/utils": "^5.4.0", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", + "@libp2p/interface": "^1.6.3", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/utils": "^5.4.8", + "@multiformats/multiaddr": "^12.2.3", + "@multiformats/multiaddr-matcher": "^1.2.1", "it-stream-types": "^2.0.1", "multiformats": "^13.1.0", + "progress-events": "^1.0.0", "race-signal": "^1.0.2", "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, - "node_modules/@multiformats/base-x": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", - "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" - }, "node_modules/@multiformats/dns": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", @@ -3672,9 +3664,9 @@ } }, "node_modules/@multiformats/multiaddr": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.2.1.tgz", - "integrity": "sha512-UwjoArBbv64FlaetV4DDwh+PUMfzXUBltxQwdh+uTYnGFzVa8ZfJsn1vt1RJlJ6+Xtrm3RMekF/B+K338i2L5Q==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.3.0.tgz", + "integrity": "sha512-JQ8Gc/jgucqqvEaDTFN/AvxlYDHEE7lgEWLMYW7hKZkWggER+GvG/tVxUgUxIP8M0vFpvEHKKHE0lKzyMsgi8Q==", "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@chainsafe/netmask": "^2.0.0", @@ -3686,9 +3678,9 @@ } }, "node_modules/@multiformats/multiaddr-matcher": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.1.tgz", - "integrity": "sha512-rcf8RSsvOkJcMoNaGgEPXgoCyvorGBOyNfj1TYX2IHcI8FhqDcuzuYwzuHz6wlsTwi4ADDNU2azGcOXftCnfYA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.4.tgz", + "integrity": "sha512-GgpqzQFL4Mj8t7cLNHC5nuYUuSm0kTtSUyYswiyWwTSUY3XwRAMx0UiFWQg+ETk0u+/IvFaHxfnyEoH3tasvwg==", "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@multiformats/multiaddr": "^12.0.0", @@ -3696,11 +3688,11 @@ } }, "node_modules/@multiformats/multiaddr-to-uri": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.0.1.tgz", - "integrity": "sha512-RtOBRJucMCzINPytvt1y7tJ2jr4aSKJmv3DF7/C515RJO9+nu9sZHdsk9vn251OtN8k21rAGlIHESt/BSJWAnQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.1.0.tgz", + "integrity": "sha512-ZNwSAx3ssBWwd4y0LKrOsq9xG7LBHboQxnUdSduNc2fTh/NS1UjA2slgUy6KHxH5k9S2DSus0iU2CoyJyN0/pg==", "dependencies": { - "@multiformats/multiaddr": "^12.0.0" + "@multiformats/multiaddr": "^12.3.0" } }, "node_modules/@multiformats/murmur3": { @@ -3726,17 +3718,17 @@ } }, "node_modules/@noble/ciphers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", - "integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz", + "integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==", "dependencies": { "@noble/hashes": "1.4.0" }, @@ -3810,19 +3802,19 @@ } }, "node_modules/@react-native-community/cli": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.6.tgz", - "integrity": "sha512-IqclB7VQ84ye8Fcs89HOpOscY4284VZg2pojHNl8H0Lzd4DadXJWQoxC7zWm8v2f8eyeX2kdhxp2ETD5tceIgA==", - "peer": true, - "dependencies": { - "@react-native-community/cli-clean": "13.6.6", - "@react-native-community/cli-config": "13.6.6", - "@react-native-community/cli-debugger-ui": "13.6.6", - "@react-native-community/cli-doctor": "13.6.6", - "@react-native-community/cli-hermes": "13.6.6", - "@react-native-community/cli-server-api": "13.6.6", - "@react-native-community/cli-tools": "13.6.6", - "@react-native-community/cli-types": "13.6.6", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.9.tgz", + "integrity": "sha512-hFJL4cgLPxncJJd/epQ4dHnMg5Jy/7Q56jFvA3MHViuKpzzfTCJCB+pGY54maZbtym53UJON9WTGpM3S81UfjQ==", + "peer": true, + "dependencies": { + "@react-native-community/cli-clean": "13.6.9", + "@react-native-community/cli-config": "13.6.9", + "@react-native-community/cli-debugger-ui": "13.6.9", + "@react-native-community/cli-doctor": "13.6.9", + "@react-native-community/cli-hermes": "13.6.9", + "@react-native-community/cli-server-api": "13.6.9", + "@react-native-community/cli-tools": "13.6.9", + "@react-native-community/cli-types": "13.6.9", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", @@ -3834,19 +3826,19 @@ "semver": "^7.5.2" }, "bin": { - "react-native": "build/bin.js" + "rnc-cli": "build/bin.js" }, "engines": { "node": ">=18" } }, "node_modules/@react-native-community/cli-clean": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-13.6.6.tgz", - "integrity": "sha512-cBwJTwl0NyeA4nyMxbhkWZhxtILYkbU3TW3k8AXLg+iGphe0zikYMGB3T+haTvTc6alTyEFwPbimk9bGIqkjAQ==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-13.6.9.tgz", + "integrity": "sha512-7Dj5+4p9JggxuVNOjPbduZBAP1SUgNhLKVw5noBUzT/3ZpUZkDM+RCSwyoyg8xKWoE4OrdUAXwAFlMcFDPKykA==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-tools": "13.6.9", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2" @@ -3997,12 +3989,12 @@ } }, "node_modules/@react-native-community/cli-config": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-13.6.6.tgz", - "integrity": "sha512-mbG425zCKr8JZhv/j11382arezwS/70juWMsn8j2lmrGTrP1cUdW0MF15CCIFtJsqyK3Qs+FTmqttRpq81QfSg==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-13.6.9.tgz", + "integrity": "sha512-rFfVBcNojcMm+KKHE/xqpqXg8HoKl4EC7bFHUrahMJ+y/tZll55+oX/PGG37rzB8QzP2UbMQ19DYQKC1G7kXeg==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-tools": "13.6.9", "chalk": "^4.1.2", "cosmiconfig": "^5.1.0", "deepmerge": "^4.3.0", @@ -4120,25 +4112,25 @@ } }, "node_modules/@react-native-community/cli-debugger-ui": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.6.tgz", - "integrity": "sha512-Vv9u6eS4vKSDAvdhA0OiQHoA7y39fiPIgJ6biT32tN4avHDtxlc6TWZGiqv7g98SBvDWvoVAmdPLcRf3kU+c8g==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.9.tgz", + "integrity": "sha512-TkN7IdFmGPPvTpAo3nCAH9uwGCPxWBEAwpqEZDrq0NWllI7Tdie8vDpGdrcuCcKalmhq6OYnkXzeBah7O1Ztpw==", "peer": true, "dependencies": { "serve-static": "^1.13.1" } }, "node_modules/@react-native-community/cli-doctor": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-13.6.6.tgz", - "integrity": "sha512-TWZb5g6EmQe2Ua2TEWNmyaEayvlWH4GmdD9ZC+p8EpKFpB1NpDGMK6sXbpb42TDvwZg5s4TDRplK0PBEA/SVDg==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-13.6.9.tgz", + "integrity": "sha512-5quFaLdWFQB+677GXh5dGU9I5eg2z6Vg4jOX9vKnc9IffwyIFAyJfCZHrxLSRPDGNXD7biDQUdoezXYGwb6P/A==", "peer": true, "dependencies": { - "@react-native-community/cli-config": "13.6.6", - "@react-native-community/cli-platform-android": "13.6.6", - "@react-native-community/cli-platform-apple": "13.6.6", - "@react-native-community/cli-platform-ios": "13.6.6", - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-config": "13.6.9", + "@react-native-community/cli-platform-android": "13.6.9", + "@react-native-community/cli-platform-apple": "13.6.9", + "@react-native-community/cli-platform-ios": "13.6.9", + "@react-native-community/cli-tools": "13.6.9", "chalk": "^4.1.2", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", @@ -4319,13 +4311,13 @@ } }, "node_modules/@react-native-community/cli-hermes": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-13.6.6.tgz", - "integrity": "sha512-La5Ie+NGaRl3klei6WxKoOxmCUSGGxpOk6vU5pEGf0/O7ky+Ay0io+zXYUZqlNMi/cGpO7ZUijakBYOB/uyuFg==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-13.6.9.tgz", + "integrity": "sha512-GvwiwgvFw4Ws+krg2+gYj8sR3g05evmNjAHkKIKMkDTJjZ8EdyxbkifRUs1ZCq3TMZy2oeblZBXCJVOH4W7ZbA==", "peer": true, "dependencies": { - "@react-native-community/cli-platform-android": "13.6.6", - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-platform-android": "13.6.9", + "@react-native-community/cli-tools": "13.6.9", "chalk": "^4.1.2", "hermes-profile-transformer": "^0.0.6" } @@ -4368,12 +4360,12 @@ } }, "node_modules/@react-native-community/cli-platform-android": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.6.tgz", - "integrity": "sha512-/tMwkBeNxh84syiSwNlYtmUz/Ppc+HfKtdopL/5RB+fd3SV1/5/NPNjMlyLNgFKnpxvKCInQ7dnl6jGHJjeHjg==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.9.tgz", + "integrity": "sha512-9KsYGdr08QhdvT3Ht7e8phQB3gDX9Fs427NJe0xnoBh+PDPTI2BD5ks5ttsH8CzEw8/P6H8tJCHq6hf2nxd9cw==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-tools": "13.6.9", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2", @@ -4526,12 +4518,12 @@ } }, "node_modules/@react-native-community/cli-platform-apple": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.6.tgz", - "integrity": "sha512-bOmSSwoqNNT3AmCRZXEMYKz1Jf1l2F86Nhs7qBcXdY/sGiJ+Flng564LOqvdAlVLTbkgz47KjNKCS2pP4Jg0Mg==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.9.tgz", + "integrity": "sha512-KoeIHfhxMhKXZPXmhQdl6EE+jGKWwoO9jUVWgBvibpVmsNjo7woaG/tfJMEWfWF3najX1EkQAoJWpCDBMYWtlA==", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-tools": "13.6.9", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2", @@ -4684,22 +4676,22 @@ } }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.6.tgz", - "integrity": "sha512-vjDnRwhlSN5ryqKTas6/DPkxuouuyFBAqAROH4FR1cspTbn6v78JTZKDmtQy9JMMo7N5vZj1kASU5vbFep9IOQ==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.9.tgz", + "integrity": "sha512-CiUcHlGs8vE0CAB4oi1f+dzniqfGuhWPNrDvae2nm8dewlahTBwIcK5CawyGezjcJoeQhjBflh9vloska+nlnw==", "peer": true, "dependencies": { - "@react-native-community/cli-platform-apple": "13.6.6" + "@react-native-community/cli-platform-apple": "13.6.9" } }, "node_modules/@react-native-community/cli-server-api": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-13.6.6.tgz", - "integrity": "sha512-ZtCXxoFlM7oDv3iZ3wsrT3SamhtUJuIkX2WePLPlN5bcbq7zimbPm2lHyicNJtpcGQ5ymsgpUWPCNZsWQhXBqQ==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-13.6.9.tgz", + "integrity": "sha512-W8FSlCPWymO+tlQfM3E0JmM8Oei5HZsIk5S0COOl0MRi8h0NmHI4WSTF2GCfbFZkcr2VI/fRsocoN8Au4EZAug==", "peer": true, "dependencies": { - "@react-native-community/cli-debugger-ui": "13.6.6", - "@react-native-community/cli-tools": "13.6.6", + "@react-native-community/cli-debugger-ui": "13.6.9", + "@react-native-community/cli-tools": "13.6.9", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", @@ -4710,18 +4702,18 @@ } }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "peer": true, "dependencies": { "async-limiter": "~1.0.0" } }, "node_modules/@react-native-community/cli-tools": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-13.6.6.tgz", - "integrity": "sha512-ptOnn4AJczY5njvbdK91k4hcYazDnGtEPrqIwEI+k/CTBHNdb27Rsm2OZ7ye6f7otLBqF8gj/hK6QzJs8CEMgw==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-13.6.9.tgz", + "integrity": "sha512-OXaSjoN0mZVw3nrAwcY1PC0uMfyTd9fz7Cy06dh+EJc+h0wikABsVRzV8cIOPrVV+PPEEXE0DBrH20T2puZzgQ==", "peer": true, "dependencies": { "appdirsjs": "^1.2.4", @@ -4964,9 +4956,9 @@ } }, "node_modules/@react-native-community/cli-types": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-13.6.6.tgz", - "integrity": "sha512-733iaYzlmvNK7XYbnWlMjdE+2k0hlTBJW071af/xb6Bs+hbJqBP9c03FZuYH2hFFwDDntwj05bkri/P7VgSxug==", + "version": "13.6.9", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-13.6.9.tgz", + "integrity": "sha512-RLxDppvRxXfs3hxceW/mShi+6o5yS+kFPnPqZTaMKKR5aSg7LwDpLQW4K2D22irEG8e6RKDkZUeH9aL3vO2O0w==", "peer": true, "dependencies": { "joi": "^17.2.1" @@ -5187,30 +5179,30 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.83.tgz", - "integrity": "sha512-2vkLMVnp+YTZYTNSDIBZojSsjz8sl5PscP3j4GcV6idD8V978SZfwFlk8K0ti0BzRs11mzL0Pj17km597S/eTQ==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", + "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.83.tgz", - "integrity": "sha512-+S0st3t4Ro00bi9gjT1jnK8qTFOU+CwmziA7U9odKyWrCoRJrgmrvogq/Dr1YXlpFxexiGIupGut1VHxr+fxJA==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.87.tgz", + "integrity": "sha512-+vJYpMnENFrwtgvDfUj+CtVJRJuUnzAUYT0/Pb68Sq9RfcZ5xdcCuUgyf7JO+akW2VTBoJY427wkcxU30qrWWw==", "peer": true, "dependencies": { - "@react-native/codegen": "0.74.83" + "@react-native/codegen": "0.74.87" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.83.tgz", - "integrity": "sha512-KJuu3XyVh3qgyUer+rEqh9a/JoUxsDOzkJNfRpDyXiAyjDRoVch60X/Xa/NcEQ93iCVHAWs0yQ+XGNGIBCYE6g==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.87.tgz", + "integrity": "sha512-hyKpfqzN2nxZmYYJ0tQIHG99FQO0OWXp/gVggAfEUgiT+yNKas1C60LuofUsK7cd+2o9jrpqgqW4WzEDZoBlTg==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", @@ -5253,7 +5245,7 @@ "@babel/plugin-transform-typescript": "^7.5.0", "@babel/plugin-transform-unicode-regex": "^7.0.0", "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.74.83", + "@react-native/babel-plugin-codegen": "0.74.87", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -5265,9 +5257,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.83.tgz", - "integrity": "sha512-GgvgHS3Aa2J8/mp1uC/zU8HuTh8ZT5jz7a4mVMWPw7+rGyv70Ba8uOVBq6UH2Q08o617IATYc+0HfyzAfm4n0w==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.87.tgz", + "integrity": "sha512-GMSYDiD+86zLKgMMgz9z0k6FxmRn+z6cimYZKkucW4soGbxWsbjUAZoZ56sJwt2FJ3XVRgXCrnOCgXoH/Bkhcg==", "peer": true, "dependencies": { "@babel/parser": "^7.20.0", @@ -5286,15 +5278,15 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.83.tgz", - "integrity": "sha512-7GAFjFOg1mFSj8bnFNQS4u8u7+QtrEeflUIDVZGEfBZQ3wMNI5ycBzbBGycsZYiq00Xvoc6eKFC7kvIaqeJpUQ==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.87.tgz", + "integrity": "sha512-EgJG9lSr8x3X67dHQKQvU6EkO+3ksVlJHYIVv6U/AmW9dN80BEFxgYbSJ7icXS4wri7m4kHdgeq2PQ7/3vvrTQ==", "peer": true, "dependencies": { - "@react-native-community/cli-server-api": "13.6.6", - "@react-native-community/cli-tools": "13.6.6", - "@react-native/dev-middleware": "0.74.83", - "@react-native/metro-babel-transformer": "0.74.83", + "@react-native-community/cli-server-api": "13.6.9", + "@react-native-community/cli-tools": "13.6.9", + "@react-native/dev-middleware": "0.74.87", + "@react-native/metro-babel-transformer": "0.74.87", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", @@ -5453,22 +5445,22 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.83.tgz", - "integrity": "sha512-RGQlVUegBRxAUF9c1ss1ssaHZh6CO+7awgtI9sDeU0PzDZY/40ImoPD5m0o0SI6nXoVzbPtcMGzU+VO590pRfA==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.87.tgz", + "integrity": "sha512-MN95DJLYTv4EqJc+9JajA3AJZSBYJz2QEJ3uWlHrOky2vKrbbRVaW1ityTmaZa2OXIvNc6CZwSRSE7xCoHbXhQ==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.83.tgz", - "integrity": "sha512-UH8iriqnf7N4Hpi20D7M2FdvSANwTVStwFCSD7VMU9agJX88Yk0D1T6Meh2RMhUu4kY2bv8sTkNRm7LmxvZqgA==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.87.tgz", + "integrity": "sha512-7TmZ3hTHwooYgIHqc/z87BMe1ryrIqAUi+AF7vsD+EHCGxHFdMjSpf1BZ2SUPXuLnF2cTiTfV2RwhbPzx0tYIA==", "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.83", + "@react-native/debugger-frontend": "0.74.87", "@rnx-kit/chromium-edge-launcher": "^1.0.0", "chrome-launcher": "^0.15.2", "connect": "^3.6.5", @@ -5517,40 +5509,40 @@ } }, "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "peer": true, "dependencies": { "async-limiter": "~1.0.0" } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.83.tgz", - "integrity": "sha512-Pw2BWVyOHoBuJVKxGVYF6/GSZRf6+v1Ygc+ULGz5t20N8qzRWPa2fRZWqoxsN7TkNLPsECYY8gooOl7okOcPAQ==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.87.tgz", + "integrity": "sha512-T+VX0N1qP+U9V4oAtn7FTX7pfsoVkd1ocyw9swYXgJqU2fK7hC9famW7b3s3ZiufPGPr1VPJe2TVGtSopBjL6A==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.83.tgz", - "integrity": "sha512-/t74n8r6wFhw4JEoOj3bN71N1NDLqaawB75uKAsSjeCwIR9AfCxlzZG0etsXtOexkY9KMeZIQ7YwRPqUdNXuqw==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.87.tgz", + "integrity": "sha512-M5Evdn76CuVEF0GsaXiGi95CBZ4IWubHqwXxV9vG9CC9kq0PSkoM2Pn7Lx7dgyp4vT7ccJ8a3IwHbe+5KJRnpw==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.83.tgz", - "integrity": "sha512-hGdx5N8diu8y+GW/ED39vTZa9Jx1di2ZZ0aapbhH4egN1agIAusj5jXTccfNBwwWF93aJ5oVbRzfteZgjbutKg==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.87.tgz", + "integrity": "sha512-UsJCO24sNax2NSPBmV1zLEVVNkS88kcgAiYrZHtYSwSjpl4WZ656tIeedBfiySdJ94Hr3kQmBYLipV5zk0NI1A==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.74.83", + "@react-native/babel-preset": "0.74.87", "hermes-parser": "0.19.1", "nullthrows": "^1.1.1" }, @@ -5562,15 +5554,15 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.83.tgz", - "integrity": "sha512-jhCY95gRDE44qYawWVvhTjTplW1g+JtKTKM3f8xYT1dJtJ8QWv+gqEtKcfmOHfDkSDaMKG0AGBaDTSK8GXLH8Q==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", + "integrity": "sha512-Xh7Nyk/MPefkb0Itl5Z+3oOobeG9lfLb7ZOY2DKpFnoCE1TzBmib9vMNdFaLdSxLIP+Ec6icgKtdzYg8QUPYzA==", "peer": true }, "node_modules/@react-native/virtualized-lists": { - "version": "0.74.83", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.83.tgz", - "integrity": "sha512-rmaLeE34rj7py4FxTod7iMTC7BAsm+HrGA8WxYmEJeyTV7WSaxAkosKoYBz8038mOiwnG9VwA/7FrB6bEQvn1A==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.87.tgz", + "integrity": "sha512-lsGxoFMb0lyK/MiplNKJpD+A1EoEUumkLrCjH4Ht+ZlG8S0BfCxmskLZ6qXn3BiDSkLjfjI/qyZ3pnxNBvkXpQ==", "peer": true, "dependencies": { "invariant": "^2.2.4", @@ -5608,9 +5600,9 @@ } }, "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "version": "18.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", + "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", "peer": true, "dependencies": { "undici-types": "~5.26.4" @@ -5632,6 +5624,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "peer": true, "dependencies": { "glob": "^7.1.3" @@ -5643,253 +5636,61 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", - "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ] + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", - "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ] + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "peer": true }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", - "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "peer": true }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", - "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "peer": true }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", - "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] + "node_modules/@sindresorhus/fnv1a": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", + "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", - "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", - "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", - "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", - "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", - "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", - "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", - "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", - "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", - "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", - "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", - "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "peer": true - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "peer": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "peer": true - }, - "node_modules/@sindresorhus/fnv1a": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", - "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } }, "node_modules/@types/body-parser": { "version": "1.19.5", @@ -5936,11 +5737,6 @@ "@types/node": "*" } }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" - }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -5954,9 +5750,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", - "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", "dev": true, "dependencies": { "@types/node": "*", @@ -6042,9 +5838,9 @@ "integrity": "sha512-QvlqvYtGBYIDeO8dFdY4djkRubcrc+yTJtBc7n8VZPlJDUS/00A+PssbvERM8f9bYRmcaSEHPZgZojeQj7kzAA==" }, "node_modules/@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "version": "20.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", + "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", "dependencies": { "undici-types": "~5.26.4" } @@ -6076,11 +5872,6 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -6128,17 +5919,17 @@ "peer": true }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "peer": true, "dependencies": { "@types/yargs-parser": "*" @@ -6150,22 +5941,14 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "peer": true }, - "node_modules/@typescript-eslint/eslint-plugin": { + "node_modules/@typescript-eslint/scope-manager": { "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6173,27 +5956,18 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/parser": { + "node_modules/@typescript-eslint/type-utils": { "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6203,7 +5977,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "*" }, "peerDependenciesMeta": { "typescript": { @@ -6211,14 +5985,20 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6226,33 +6006,31 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/@typescript-eslint/types": { @@ -6295,54 +6073,6 @@ } } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", @@ -6366,12 +6096,6 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -6396,9 +6120,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "bin": { "acorn": "bin/acorn" }, @@ -6416,15 +6140,15 @@ } }, "node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -6728,13 +6452,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6837,49 +6561,23 @@ } }, "node_modules/blockstore-fs": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-1.1.10.tgz", - "integrity": "sha512-Dg0mbdma0OY4NEk78efcAAiG5ZrMcIVrM7s+0e2p4uavnvrcBT6vDj5ITfnRfid3idKHOoCYShGEi9ENNgJg1A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-2.0.0.tgz", + "integrity": "sha512-PrGM39gdo/NhYLA7+gUN+9tSD33Y38mCwqW3s/PsbGpPRsSZiNjscCclwAoZRHb8UbpRtQT2KwXo23gCzagxJg==", "dependencies": { - "blockstore-core": "^4.0.0", "fast-write-atomic": "^0.2.1", "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.0", - "it-glob": "^2.0.6", + "interface-store": "6.0.0", + "it-glob": "^3.0.1", "it-map": "^3.0.5", "it-parallel-batch": "^3.0.4", "multiformats": "^13.0.1" } }, - "node_modules/blockstore-fs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/blockstore-fs/node_modules/it-glob": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.7.tgz", - "integrity": "sha512-FjL2rAsGu566sMVKexFG5ciDoKoi1lgwXlSE6DW0yVbGRyvrlzi0OQ3fbfrw89dpIAzJFHnLNQwZS4yRkt3d/Q==", - "dependencies": { - "minimatch": "^9.0.4" - } - }, - "node_modules/blockstore-fs/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/blockstore-fs/node_modules/interface-store": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", + "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" }, "node_modules/body-parser": { "version": "1.20.2", @@ -6927,11 +6625,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6943,9 +6641,9 @@ "integrity": "sha512-g1Aznml3HmqTLSXylZhGwdfnAa67+vlNAYhT9ROJZkAxY7yYmWusND10olvCMPe4sVhZyVwn5tPkRzOg85kBEg==" }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -6962,10 +6660,10 @@ ], "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -7011,6 +6709,15 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -7129,9 +6836,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001617", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", - "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "funding": [ { "type": "opencollective", @@ -7149,9 +6856,9 @@ "peer": true }, "node_modules/cborg": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.2.0.tgz", - "integrity": "sha512-q6cFW5m3KxfP/9xGI3yGLaC1l5DP6DWM9IvjiJojnIwohL5CQDl02EXViPV852mOfQo+7PJGPN01MI87vFGzyA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.2.3.tgz", + "integrity": "sha512-XBFbEJ6WMfn9L7woc2t+EzOxF8vGqddoopKBbrhIvZBt2WIUgSlT8xLmM6Aq1xv8eWt4yOSjwxWjYeuHU3CpJA==", "bin": { "cborg": "lib/bin.js" } @@ -7306,9 +7013,9 @@ "dev": true }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { "emoji-regex": "^10.3.0", @@ -7441,12 +7148,12 @@ "peer": true }, "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/commondir": { @@ -7705,12 +7412,12 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-js-compat": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", - "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz", + "integrity": "sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==", "peer": true, "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -7831,48 +7538,41 @@ } }, "node_modules/datastore-fs": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-9.1.8.tgz", - "integrity": "sha512-NM/f7/yHkgnVSiYJFPbKlEIJZ49sWz2XkvYY+Kl3bl/esQILzDt4uXUhNMRF4xfLTpQKcr+ZOfrArxVYsfnj7g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-10.0.0.tgz", + "integrity": "sha512-XeTImTVXjSApJzqn/iDfoWCLWqTdHO/lHT9+fgghlBtbfe9qAm2YImlSlnN8ryfwmKtkgLQm9tI4gSL9vsTZyA==", "dependencies": { - "datastore-core": "^9.0.0", + "datastore-core": "10.0.0", "fast-write-atomic": "^0.2.1", "interface-datastore": "^8.0.0", - "interface-store": "^5.0.0", - "it-glob": "^2.0.6", + "interface-store": "6.0.0", + "it-glob": "^3.0.1", "it-map": "^3.0.5", "it-parallel-batch": "^3.0.4" } }, - "node_modules/datastore-fs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/datastore-fs/node_modules/datastore-core": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-10.0.0.tgz", + "integrity": "sha512-/b5KDmSAIFizS/ZzTTU+ZdfOcQc6mRmbc4/EK/WOzbEO5c0KWyjRLsZ3DWwh9wiPu93V7pX1oC+Or61k8UPPsg==", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/datastore-fs/node_modules/it-glob": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.7.tgz", - "integrity": "sha512-FjL2rAsGu566sMVKexFG5ciDoKoi1lgwXlSE6DW0yVbGRyvrlzi0OQ3fbfrw89dpIAzJFHnLNQwZS4yRkt3d/Q==", - "dependencies": { - "minimatch": "^9.0.4" + "@libp2p/logger": "^4.0.6", + "interface-datastore": "^8.0.0", + "interface-store": "6.0.0", + "it-drain": "^3.0.5", + "it-filter": "^3.0.4", + "it-map": "^3.0.5", + "it-merge": "^3.0.3", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-sort": "^3.0.4", + "it-take": "^3.0.4" } }, - "node_modules/datastore-fs/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/datastore-fs/node_modules/interface-store": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", + "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" }, "node_modules/dateformat": { "version": "4.6.3", @@ -7884,15 +7584,15 @@ } }, "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", + "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==", "peer": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -8166,9 +7866,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.761", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.761.tgz", - "integrity": "sha512-PIbxpiJGx6Bb8dQaonNc6CGTRlVntdLg/2nMa1YhnrwYOORY9a3ZgGN0UQYE6lAcj/lkyduJN7BPt/JiY+jAQQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", "peer": true }, "node_modules/emoji-regex": { @@ -8213,6 +7913,18 @@ "node": ">=4" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/err-code": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", @@ -8359,9 +8071,9 @@ } }, "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", "dev": true, "dependencies": { "eslint-utils": "^2.0.0", @@ -8377,33 +8089,53 @@ "eslint": ">=4.19.1" } }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=6" }, - "peerDependencies": { - "eslint": ">=5.16.0" + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { @@ -8453,27 +8185,30 @@ } }, "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { @@ -8660,9 +8395,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -8765,6 +8500,12 @@ "node": ">=6" } }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "peer": true + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -8903,15 +8644,21 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true + }, "node_modules/fast-write-atomic": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz", "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" }, "node_modules/fast-xml-parser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", - "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -8984,9 +8731,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -9073,6 +8820,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -9097,18 +8845,18 @@ "peer": true }, "node_modules/flow-parser": { - "version": "0.236.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.236.0.tgz", - "integrity": "sha512-0OEk9Gr+Yj7wjDW2KgaNYUypKau71jAfFyeLQF5iVtxqc6uJHag/MT7pmaEApf4qM7u86DkBcd4ualddYMfbLw==", + "version": "0.243.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.243.0.tgz", + "integrity": "sha512-HCDBfH+kZcY5etWYeAqatjW78gkIryzb9XixRsA8lGI1uyYc7aCpElkkO4H+KIpoyQMiY0VAZPI4cyac3wQe8w==", "peer": true, "engines": { "node": ">=0.4.0" } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -9292,6 +9040,7 @@ "version": "7.2.3", "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", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9405,24 +9154,24 @@ "dev": true }, "node_modules/gts": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gts/-/gts-5.3.0.tgz", - "integrity": "sha512-/V0nbaWLBv8g0v2kol5M7Vf+kHXk19Ew5sa3wQJXeUaccesXg7AFo7eEInoIpR+aIJ3QDjoYt4zHYeFr8w8rng==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gts/-/gts-5.3.1.tgz", + "integrity": "sha512-P9F+krJkGOkisUX+P9pfUas1Xy+U+CxBFZT62uInkJbgvZpnW1ug/pIcMJJmLOthMq1J88lpQUGhXDC9UTvVcw==", "dev": true, "dependencies": { "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "chalk": "^4.1.2", - "eslint": "8.53.0", + "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-node": "11.1.0", + "eslint-plugin-n": "15.7.0", "eslint-plugin-prettier": "5.1.3", "execa": "^5.0.0", "inquirer": "^7.3.3", "json5": "^2.1.3", "meow": "^9.0.0", "ncp": "^2.0.0", - "prettier": "3.1.1", + "prettier": "3.2.5", "rimraf": "3.0.2", "write-file-atomic": "^4.0.0" }, @@ -9430,35 +9179,97 @@ "gts": "build/src/cli.js" }, "engines": { - "node": ">=14" + "node": ">=14" + }, + "peerDependencies": { + "typescript": ">=3" + } + }, + "node_modules/gts/node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/gts/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=3" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/gts/node_modules/@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "node_modules/gts/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/gts/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/gts/node_modules/chalk": { @@ -9477,59 +9288,26 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/gts/node_modules/eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "node_modules/gts/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8.0.0" + } + }, + "node_modules/gts/node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/gts/node_modules/execa": { @@ -9555,22 +9333,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/gts/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gts/node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9613,27 +9375,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gts/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/gts/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gts/node_modules/meow": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", @@ -9696,49 +9437,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gts/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/gts/node_modules/prettier": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", - "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -9754,6 +9456,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -9813,18 +9516,6 @@ "node": ">=10" } }, - "node_modules/gts/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hamt-sharding": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.6.tgz", @@ -9901,17 +9592,17 @@ } }, "node_modules/helia": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.1.tgz", - "integrity": "sha512-b3ZXDeddNtSiUbz8mJumL0chTRYjGGjmPb5qQhilXOG5PKFwffffYaR0Py4eaZ5eTlBlCfKCqjiBNeS4oerAWA==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.5.tgz", + "integrity": "sha512-0X4Kb1jIwGGROcIcfS5xSjQHH8uisBbMGvktnWjziGkmlcgb8JXzilLXTtH+jDpkTkpo0s1VTKZkHo9rozjWkw==", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/block-brokers": "^3.0.0", + "@helia/block-brokers": "^3.0.3", "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.3.0", - "@helia/routers": "^1.1.0", - "@helia/utils": "^0.3.0", + "@helia/interface": "^4.3.1", + "@helia/routers": "^1.1.1", + "@helia/utils": "^0.3.3", "@libp2p/autonat": "^1.0.13", "@libp2p/bootstrap": "^10.0.16", "@libp2p/circuit-relay-v2": "^1.0.16", @@ -10027,12 +9718,12 @@ } }, "node_modules/husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", + "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", "dev": true, "bin": { - "husky": "bin.mjs" + "husky": "bin.js" }, "engines": { "node": ">=18" @@ -10157,6 +9848,7 @@ "version": "1.0.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.", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -10238,23 +9930,33 @@ } }, "node_modules/interface-blockstore": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.2.10.tgz", - "integrity": "sha512-9K48hTvBCGsKVD3pF4ILgDcf+W2P/gq0oxLcsHGB6E6W6nDutYkzR+7k7bCs9REHrBEfKzcVDEKieiuNM9WRZg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.3.0.tgz", + "integrity": "sha512-MsOE2H9duZdpxXk7KVOQLaQnX4LfqHegshKxSr4b/YD44t13koT5Lg4lphZ0FwB5uvtvqQPm7LNcS0T/5Z2YUQ==", "dependencies": { - "interface-store": "^5.0.0", + "interface-store": "6.0.0", "multiformats": "^13.0.1" } }, + "node_modules/interface-blockstore/node_modules/interface-store": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", + "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" + }, "node_modules/interface-datastore": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.2.11.tgz", - "integrity": "sha512-9E0iXehfp/j0UbZ2mvlYB4K9pP7uQBCppfuy8WHs1EHF6wLQrM9+zwyX+8Qt6HnH4GKZRyXX/CNXm6oD4+QYgA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.0.tgz", + "integrity": "sha512-RM/rTSmRcnoCwGZIHrPm+nlGYVoT4R0lcFvNnDyhdFT4R6BuHHhfFP47UldVEjs98SfxLuMhaNMsyjI918saHw==", "dependencies": { - "interface-store": "^5.0.0", + "interface-store": "6.0.0", "uint8arrays": "^5.0.2" } }, + "node_modules/interface-datastore/node_modules/interface-store": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", + "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" + }, "node_modules/interface-store": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.8.tgz", @@ -10382,11 +10084,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10481,17 +10186,6 @@ "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==" }, - "node_modules/is-network-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", - "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10626,13 +10320,12 @@ "integrity": "sha512-pQAAlSvJ4aV6xM/6LRvkPdKSKXxS4my2fGzNUxJyAQ8ccFdxPmK1bUuF5OoeUDkcdrbs8jtsmc4DypCMrGY6sg==" }, "node_modules/it-byte-stream": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-1.0.10.tgz", - "integrity": "sha512-wjEADMuCS7PtnAGDjLGZ9n2+J+c6F/3a64ZfLVw2DCSKJWxwEQv+kQ2GUqArQVwkF+cKL6p7ka5cfbm+rwkwzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-1.1.0.tgz", + "integrity": "sha512-WWponBWdKEa6o2U3NX+wGMY8X1EkWXcQvpC+3CUqKb4ZzK30q3EPqiTjFxLf9tNVgdF/MNAtx/XclpVfgaz9KQ==", "dependencies": { + "it-queueless-pushable": "^1.0.0", "it-stream-types": "^2.0.1", - "p-defer": "^4.0.1", - "race-signal": "^1.0.2", "uint8arraylist": "^2.4.8" } }, @@ -10642,9 +10335,9 @@ "integrity": "sha512-vy6S1JKjjHSIFHgBpLpD1zhkCRl3z1zYWUxE14+kAYf+BL9ssWSFImJfhl361IIcwr0ofw8etzg11VqqB+ntUA==" }, "node_modules/it-filter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.0.tgz", - "integrity": "sha512-FiYuzdsUhmMZJTJQ8YLdgX3ArjQmAtCG1lyrtZd+92/2eC6YO9UoybdrwVj/yyZkuXAPykrSipLuZ+KSKpt29A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.1.tgz", + "integrity": "sha512-TOXmVuaSkxlLp2hXKoMTra0WMZMKVFxE3vSsbIA+PbADNCBAHhjJ/lM31vBOUTddHMO34Ku++vU8T9PLlBxQtg==", "dependencies": { "it-peekable": "^3.0.0" } @@ -10655,9 +10348,9 @@ "integrity": "sha512-ExIewyK9kXKNAplg2GMeWfgjUcfC1FnUXz/RPfAvIXby+w7U4b3//5Lic0NV03gXT8O/isj5Nmp6KiY0d45pIQ==" }, "node_modules/it-foreach": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.0.tgz", - "integrity": "sha512-nobWUecq9E2ED1kcXz2o27yN6KePauSdmxJNMwCduWByrF4WNB2UgBHjr9QV2jPXpEWPDuzxZas9fVhQj1Vovg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.1.tgz", + "integrity": "sha512-ID4Gxnavk/LVQLQESAQ9hR6dR63Ih6X+8VdxEktX8rpz2dCGAbZpey/eljTNbMfV2UKXHiu6UsneoNBZuac97g==", "dependencies": { "it-peekable": "^3.0.0" } @@ -10681,11 +10374,10 @@ "integrity": "sha512-R7bxHAzpRzYz7vghc2DDH7x4KXvEkeLfN/h316++jzbkEHIRXbEPLbE20p5yrqqBdOeK6/FRUDuHlTJ0H1hysw==" }, "node_modules/it-length-prefixed": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.0.4.tgz", - "integrity": "sha512-lz28fykbG0jq7s5XtvlzGxO5BeSOw6ikymkRllxjL21V5VKLcvB4pHr9wPvEnsAJ2et1xpOk3BRTMq9XrhgKsg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.1.0.tgz", + "integrity": "sha512-kx2UTJuy7/lsT3QUzf50NjfxU1Z4P4wlvYp6YnR5Nc61P8XKfy+QtiJi1VLojA+Kea7vMbB4002rIij1Ol9hcw==", "dependencies": { - "err-code": "^3.0.1", "it-reader": "^6.0.1", "it-stream-types": "^2.0.1", "uint8-varint": "^2.0.1", @@ -10698,9 +10390,9 @@ } }, "node_modules/it-length-prefixed-stream": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-1.1.7.tgz", - "integrity": "sha512-tH38h/wChpR6As/PD6yWZlpoMuB4wDW2Rxf3QbSt4+O1HTsLYbyZasNhTyIuvQqhebQ30OYrdM0yr9ig5qUvYQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-1.2.0.tgz", + "integrity": "sha512-vX7dzSl/2UMYYsAr0FQdPNVR5xYEETaeboZ+eXxNBjgARuvxnWA6OedW8lC5/J3ebMTC98JhA3eH76eTijUOsA==", "dependencies": { "it-byte-stream": "^1.0.0", "it-stream-types": "^2.0.1", @@ -10709,9 +10401,9 @@ } }, "node_modules/it-map": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.0.tgz", - "integrity": "sha512-B7zNmHYRE0qes8oTiNYU7jXEF5WvKZNAUosskCks1JT9Z4DNwRClrQyd+C/hgITG8ewDbVZMGx9VXAx3KMY2kA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.1.tgz", + "integrity": "sha512-9bCSwKD1yN1wCOgJ9UOl+46NQtdatosPWzxxUk2NdTLwRPXLh+L7iwCC9QKsbgM60RQxT/nH8bKMqm3H/o8IHQ==", "dependencies": { "it-peekable": "^3.0.0" } @@ -10725,9 +10417,9 @@ } }, "node_modules/it-ndjson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.6.tgz", - "integrity": "sha512-44QB+rmfB2C8L2WDv03+J3kcOQYuIPBP3KBN+szZTEsvvNyoQEQSnOXoGouNpRfgMA1rEqqoK6roenCJaBeYSQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.7.tgz", + "integrity": "sha512-V3IskT5RCVtov1u6sC9gkg0uD02qe8yPoVzBZVaRx+YkuMdpFd6opiAwfYovNd/NEbqo9mBN8wJLtw6vD0Xiqg==" }, "node_modules/it-pair": { "version": "2.0.6", @@ -10743,25 +10435,25 @@ } }, "node_modules/it-parallel": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.7.tgz", - "integrity": "sha512-aIIc2t8knfER/mQu4uEHaAYZrnj/2Tdp+Vj6BA94Gi7xghx1kblvpyrLkCYO9K+eDyPS1cE3Vfhh9a20MEmzXA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.8.tgz", + "integrity": "sha512-URLhs6eG4Hdr4OdvgBBPDzOjBeSSmI+Kqex2rv/aAyYClME26RYHirLVhZsZP5M+ZP6M34iRlXk8Wlqtezuqpg==", "dependencies": { "p-defer": "^4.0.1" } }, "node_modules/it-parallel-batch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.5.tgz", - "integrity": "sha512-WxoFCMj61zV7rxaDj8Uox2aU+GI/JQzYQwcLi4QT1wN5cjAV+kA0GjaF9I95YG2JB6LVHb2qSbpfZEM7YVtnfQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.6.tgz", + "integrity": "sha512-3wgiQGvMMHy65OXScrtrtmY+bJSF7P6St1AP+BU+SK83fEr8NNk/MrmJKrtB1+MahYX2a8I+pOGKDj8qVtuV0Q==", "dependencies": { "it-batch": "^3.0.0" } }, "node_modules/it-peekable": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.4.tgz", - "integrity": "sha512-Bb4xyMX5xAveFyh9ySbCrHMCpIF0+fIbl+0ZkcxP94JVofLe5j/mSBK0gjrrISsSVURVyey8X4L/IqrekOxjiA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.5.tgz", + "integrity": "sha512-JWQOGMt6rKiPcY30zUVMR4g6YxkpueTwHVE7CMs/aGqCf4OydM6w+7ZM3PvmO1e0TocjuR4aL8xyZWR46cTqCQ==" }, "node_modules/it-pipe": { "version": "3.0.1", @@ -10778,9 +10470,9 @@ } }, "node_modules/it-protobuf-stream": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-1.1.3.tgz", - "integrity": "sha512-96n+e6X8CXL0JerxTJuEnfivmfLzGKpIGAlJLoH7HEGo2nPRrMe+HxeWGwDF4Un3FphI/Z62JNxSvq/5DxfiQw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-1.1.5.tgz", + "integrity": "sha512-H70idW45As3cEbU4uSoZ9IYHUIV3YM69/2mmXYR7gOlPabWjuyNi3/abK11geiiq3la27Sos/mXr68JljjKtEQ==", "dependencies": { "it-length-prefixed-stream": "^1.0.0", "it-stream-types": "^2.0.1", @@ -10795,6 +10487,15 @@ "p-defer": "^4.0.0" } }, + "node_modules/it-queueless-pushable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-1.0.0.tgz", + "integrity": "sha512-HbcAbcuQj7a9EBxiRCZ+77FxWutgs/pY5ZvEyQnylWPGNFojCLAUwhcZjf5OuEQ9+y+vSa7w1GQBe8xJdmIn5A==", + "dependencies": { + "p-defer": "^4.0.1", + "race-signal": "^1.0.2" + } + }, "node_modules/it-reader": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-6.0.4.tgz", @@ -10809,9 +10510,9 @@ } }, "node_modules/it-sort": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.5.tgz", - "integrity": "sha512-vFo3wYR+aRDwklp8iH8LKeePmWqXGQrS8JqEdZmbJ58DIGj67n0RT/t5BR8iYps/C/v5IdWsbow1bOCEUfY+hA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.6.tgz", + "integrity": "sha512-aNrlZAXB8vWBd42tCpaXGL6CJVJNDW3OLczmdt6g0k/s9Z6evkTdgU2LjwW5SNNeX41sF+C8MjV+OcVf93PsPw==", "dependencies": { "it-all": "^3.0.0" } @@ -10826,14 +10527,14 @@ } }, "node_modules/it-take": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.5.tgz", - "integrity": "sha512-4CzqXzx7FAeXsRYBTH0GhkxerH8Sv0nEGIXrO0ZIpECHth59Dm9ZYZ161VPrCQccWIL/Vu6M9YptlbMiEpCIlQ==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.6.tgz", + "integrity": "sha512-uqw3MRzf9to1SOLxaureGa73lK8k8ZB/asOApTAkvrzUqCznGtKNgPFH7uYIWlt4UuWq/hU6I+U4Fm5xpjN8Vg==" }, "node_modules/it-ws": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.1.tgz", - "integrity": "sha512-oyk4eCeZto2lzWDnJOa3j1S2M+VOGKUh8isEf94ySoaL6IFlyie0T4P9E0ZUaIvX8LyJxYFHFKCt8Zk7Sm/XPQ==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.5.tgz", + "integrity": "sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw==", "dependencies": { "@types/ws": "^8.2.2", "event-iterator": "^2.0.0", @@ -10847,15 +10548,15 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11173,18 +10874,18 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, "bin": { "jiti": "bin/jiti.js" } }, "node_modules/joi": { - "version": "17.13.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", - "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "peer": true, "dependencies": { "@hapi/hoek": "^9.3.0", @@ -11455,34 +11156,35 @@ } }, "node_modules/libp2p": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.5.1.tgz", - "integrity": "sha512-RzfOGjFRpSUDsv5c1ZA2TsWbPLaS86AVbIqhCrh273F9x3N/CkCXmRfWFmaRFmezWhP3SHbjsFWyQRJGF9U5mw==", - "dependencies": { - "@libp2p/crypto": "^4.1.1", - "@libp2p/interface": "^1.3.1", - "@libp2p/interface-internal": "^1.2.0", - "@libp2p/logger": "^4.0.12", - "@libp2p/multistream-select": "^5.1.9", - "@libp2p/peer-collections": "^5.2.0", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/peer-id-factory": "^4.1.1", - "@libp2p/peer-store": "^10.0.17", - "@libp2p/utils": "^5.4.0", - "@multiformats/dns": "^1.0.5", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.8.3.tgz", + "integrity": "sha512-IpHKhR/gjiRcm3UqaHC3bEFsnBS62hTwXy60L8Uie8NZvvliVL6skmIBAODfma0+C7zvJhNKA8453SOmTeVTwQ==", + "dependencies": { + "@libp2p/crypto": "^4.1.8", + "@libp2p/interface": "^1.6.3", + "@libp2p/interface-internal": "^1.3.3", + "@libp2p/logger": "^4.0.19", + "@libp2p/multistream-select": "^5.1.16", + "@libp2p/peer-collections": "^5.2.8", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/peer-id-factory": "^4.2.3", + "@libp2p/peer-store": "^10.1.4", + "@libp2p/utils": "^5.4.8", + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^12.2.3", + "@multiformats/multiaddr-matcher": "^1.2.1", "any-signal": "^4.1.1", "datastore-core": "^9.2.9", "interface-datastore": "^8.2.11", - "it-merge": "^3.0.3", - "it-parallel": "^3.0.6", + "it-merge": "^3.0.5", + "it-parallel": "^3.0.7", "merge-options": "^3.0.4", "multiformats": "^13.1.0", "p-defer": "^4.0.1", + "progress-events": "^1.0.0", "race-event": "^1.3.0", "race-signal": "^1.0.2", - "uint8arrays": "^5.0.3" + "uint8arrays": "^5.1.0" } }, "node_modules/lighthouse-logger": { @@ -11511,12 +11213,15 @@ "peer": true }, "node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -11526,21 +11231,21 @@ "dev": true }, "node_modules/lint-staged": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", - "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", - "dev": true, - "dependencies": { - "chalk": "5.3.0", - "commander": "11.1.0", - "debug": "4.3.4", - "execa": "8.0.1", - "lilconfig": "3.0.0", - "listr2": "8.0.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.4" + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", + "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -11553,16 +11258,16 @@ } }, "node_modules/listr2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", - "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", "dev": true, "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", - "log-update": "^6.0.0", - "rfdc": "^1.3.0", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" }, "engines": { @@ -11710,14 +11415,14 @@ } }, "node_modules/log-update": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", - "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "dependencies": { - "ansi-escapes": "^6.2.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^7.0.0", + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" }, @@ -11729,12 +11434,15 @@ } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11765,15 +11473,15 @@ } }, "node_modules/log-update/node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11794,52 +11502,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/log-update/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log-update/node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/log-update/node_modules/slice-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", @@ -12034,12 +11727,12 @@ } }, "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/luxon": { @@ -12163,9 +11856,9 @@ } }, "node_modules/metro": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.9.tgz", - "integrity": "sha512-Bc57Xf3GO2Xe4UWQsBj/oW6YfLPABEu8jfDVDiNmJvoQW4CO34oDPuYKe4KlXzXhcuNsqOtSxpbjCRRVjhhREg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.10.tgz", + "integrity": "sha512-FDPi0X7wpafmDREXe1lgg3WzETxtXh6Kpq8+IwsG35R2tMyp2kFIqDdshdohuvDt1J/qDARcEPq7V/jElTb1kA==", "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", @@ -12182,34 +11875,34 @@ "debug": "^2.2.0", "denodeify": "^1.2.1", "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.20.1", + "hermes-parser": "0.23.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.9", - "metro-cache": "0.80.9", - "metro-cache-key": "0.80.9", - "metro-config": "0.80.9", - "metro-core": "0.80.9", - "metro-file-map": "0.80.9", - "metro-resolver": "0.80.9", - "metro-runtime": "0.80.9", - "metro-source-map": "0.80.9", - "metro-symbolicate": "0.80.9", - "metro-transform-plugins": "0.80.9", - "metro-transform-worker": "0.80.9", + "metro-babel-transformer": "0.80.10", + "metro-cache": "0.80.10", + "metro-cache-key": "0.80.10", + "metro-config": "0.80.10", + "metro-core": "0.80.10", + "metro-file-map": "0.80.10", + "metro-resolver": "0.80.10", + "metro-runtime": "0.80.10", + "metro-source-map": "0.80.10", + "metro-symbolicate": "0.80.10", + "metro-transform-plugins": "0.80.10", + "metro-transform-worker": "0.80.10", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", - "rimraf": "^3.0.2", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "strip-ansi": "^6.0.0", "throat": "^5.0.0", - "ws": "^7.5.1", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { @@ -12220,13 +11913,14 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.9.tgz", - "integrity": "sha512-d76BSm64KZam1nifRZlNJmtwIgAeZhZG3fi3K+EmPOlrR8rDtBxQHDSN3fSGeNB9CirdTyabTMQCkCup6BXFSQ==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.10.tgz", + "integrity": "sha512-GXHueUzgzcazfzORDxDzWS9jVVRV6u+cR6TGvHOfGdfLzJCj7/D0PretLfyq+MwN20twHxLW+BUXkoaB8sCQBg==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "hermes-parser": "0.20.1", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.23.0", "nullthrows": "^1.1.1" }, "engines": { @@ -12234,70 +11928,60 @@ } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", - "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", + "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", "peer": true }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", - "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", + "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", "peer": true, "dependencies": { - "hermes-estree": "0.20.1" + "hermes-estree": "0.23.0" } }, "node_modules/metro-cache": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.9.tgz", - "integrity": "sha512-ujEdSI43QwI+Dj2xuNax8LMo8UgKuXJEdxJkzGPU6iIx42nYa1byQ+aADv/iPh5sh5a//h5FopraW5voXSgm2w==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.10.tgz", + "integrity": "sha512-8CBtDJwMguIE5RvV3PU1QtxUG8oSSX54mIuAbRZmcQ0MYiOl9JdrMd4JCBvIyhiZLoSStph425SMyCSnjtJsdA==", "peer": true, "dependencies": { - "metro-core": "0.80.9", - "rimraf": "^3.0.2" + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.80.10" }, "engines": { "node": ">=18" } }, "node_modules/metro-cache-key": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.9.tgz", - "integrity": "sha512-hRcYGhEiWIdM87hU0fBlcGr+tHDEAT+7LYNCW89p5JhErFt/QaAkVx4fb5bW3YtXGv5BTV7AspWPERoIb99CXg==", - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/metro-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.10.tgz", + "integrity": "sha512-57qBhO3zQfoU/hP4ZlLW5hVej2jVfBX6B4NcSfMj4LgDPL3YknWg80IJBxzQfjQY/m+fmMLmPy8aUMHzUp/guA==", "peer": true, "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "flow-enums-runtime": "^0.0.6" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18" } }, "node_modules/metro-config": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.9.tgz", - "integrity": "sha512-28wW7CqS3eJrunRGnsibWldqgwRP9ywBEf7kg+uzUHkSFJNKPM1K3UNSngHmH0EZjomizqQA2Zi6/y6VdZMolg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.10.tgz", + "integrity": "sha512-0GYAw0LkmGbmA81FepKQepL1KU/85Cyv7sAiWm6QWeV6AcVCpsKg6jGLqGHJ0LLPL60rWzA4TV1DQAlzdJAEtA==", "peer": true, "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.6.3", - "metro": "0.80.9", - "metro-cache": "0.80.9", - "metro-core": "0.80.9", - "metro-runtime": "0.80.9" + "metro": "0.80.10", + "metro-cache": "0.80.10", + "metro-core": "0.80.10", + "metro-runtime": "0.80.10" }, "engines": { "node": ">=18" @@ -12376,27 +12060,29 @@ } }, "node_modules/metro-core": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.9.tgz", - "integrity": "sha512-tbltWQn+XTdULkGdzHIxlxk4SdnKxttvQQV3wpqqFbHDteR4gwCyTR2RyYJvxgU7HELfHtrVbqgqAdlPByUSbg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.10.tgz", + "integrity": "sha512-nwBB6HbpGlNsZMuzxVqxqGIOsn5F3JKpsp8PziS7Z4mV8a/jA1d44mVOgYmDa2q5WlH5iJfRIIhdz24XRNDlLA==", "peer": true, "dependencies": { + "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.9" + "metro-resolver": "0.80.10" }, "engines": { "node": ">=18" } }, "node_modules/metro-file-map": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.9.tgz", - "integrity": "sha512-sBUjVtQMHagItJH/wGU9sn3k2u0nrCl0CdR4SFMO1tksXLKbkigyQx4cbpcyPVOAmGTVuy3jyvBlELaGCAhplQ==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.10.tgz", + "integrity": "sha512-ytsUq8coneaN7ZCVk1IogojcGhLIbzWyiI2dNmw2nnBgV/0A+M5WaTTgZ6dJEz3dzjObPryDnkqWPvIGLCPtiw==", "peer": true, "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.6.3", @@ -12428,11 +12114,12 @@ "peer": true }, "node_modules/metro-minify-terser": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.9.tgz", - "integrity": "sha512-FEeCeFbkvvPuhjixZ1FYrXtO0araTpV6UbcnGgDUpH7s7eR5FG/PiJz3TsuuPP/HwCK19cZtQydcA2QrCw446A==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.10.tgz", + "integrity": "sha512-Xyv9pEYpOsAerrld7cSLIcnCCpv8ItwysOmTA+AKf1q4KyE9cxrH2O2SA0FzMCkPzwxzBWmXwHUr+A89BpEM6g==", "peer": true, "dependencies": { + "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { @@ -12440,38 +12127,43 @@ } }, "node_modules/metro-resolver": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.9.tgz", - "integrity": "sha512-wAPIjkN59BQN6gocVsAvvpZ1+LQkkqUaswlT++cJafE/e54GoVkMNCmrR4BsgQHr9DknZ5Um/nKueeN7kaEz9w==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.10.tgz", + "integrity": "sha512-EYC5CL7f+bSzrqdk1bylKqFNGabfiI5PDctxoPx70jFt89Jz+ThcOscENog8Jb4LEQFG6GkOYlwmPpsi7kx3QA==", "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, "engines": { "node": ">=18" } }, "node_modules/metro-runtime": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.9.tgz", - "integrity": "sha512-8PTVIgrVcyU+X/rVCy/9yxNlvXsBCk5JwwkbAm/Dm+Abo6NBGtNjWF0M1Xo/NWCb4phamNWcD7cHdR91HhbJvg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.10.tgz", + "integrity": "sha512-Xh0N589ZmSIgJYAM+oYwlzTXEHfASZac9TYPCNbvjNTn0EHKqpoJ/+Im5G3MZT4oZzYv4YnvzRtjqS5k0tK94A==", "peer": true, "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.0.0", + "flow-enums-runtime": "^0.0.6" }, "engines": { "node": ">=18" } }, "node_modules/metro-source-map": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.9.tgz", - "integrity": "sha512-RMn+XS4VTJIwMPOUSj61xlxgBvPeY4G6s5uIn6kt6HB6A/k9ekhr65UkkDD7WzHYs3a9o869qU8tvOZvqeQzgw==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.10.tgz", + "integrity": "sha512-EyZswqJW8Uukv/HcQr6K19vkMXW1nzHAZPWJSEyJFKIbgp708QfRZ6vnZGmrtFxeJEaFdNup4bGnu8/mIOYlyA==", "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", + "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.9", + "metro-symbolicate": "0.80.10", "nullthrows": "^1.1.1", - "ob1": "0.80.9", + "ob1": "0.80.10", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -12489,13 +12181,14 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.9.tgz", - "integrity": "sha512-Ykae12rdqSs98hg41RKEToojuIW85wNdmSe/eHUgMkzbvCFNVgcC0w3dKZEhSsqQOXapXRlLtHkaHLil0UD/EA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.10.tgz", + "integrity": "sha512-qAoVUoSxpfZ2DwZV7IdnQGXCSsf2cAUExUcZyuCqGlY5kaWBb0mx2BL/xbMFDJ4wBp3sVvSBPtK/rt4J7a0xBA==", "peer": true, "dependencies": { + "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.80.9", + "metro-source-map": "0.80.10", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -12518,15 +12211,16 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.9.tgz", - "integrity": "sha512-UlDk/uc8UdfLNJhPbF3tvwajyuuygBcyp+yBuS/q0z3QSuN/EbLllY3rK8OTD9n4h00qZ/qgxGv/lMFJkwP4vg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.10.tgz", + "integrity": "sha512-leAx9gtA+2MHLsCeWK6XTLBbv2fBnNFu/QiYhWzMq8HsOAP4u1xQAU0tSgPs8+1vYO34Plyn79xTLUtQCRSSUQ==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/template": "^7.0.0", "@babel/traverse": "^7.20.0", + "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, "engines": { @@ -12534,22 +12228,23 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.9.tgz", - "integrity": "sha512-c/IrzMUVnI0hSVVit4TXzt3A1GiUltGVlzCmLJWxNrBGHGrJhvgePj38+GXl1Xf4Fd4vx6qLUkKMQ3ux73bFLQ==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.10.tgz", + "integrity": "sha512-zNfNLD8Rz99U+JdOTqtF2o7iTjcDMMYdVS90z6+81Tzd2D0lDWVpls7R1hadS6xwM+ymgXFQTjM6V6wFoZaC0g==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "metro": "0.80.9", - "metro-babel-transformer": "0.80.9", - "metro-cache": "0.80.9", - "metro-cache-key": "0.80.9", - "metro-minify-terser": "0.80.9", - "metro-source-map": "0.80.9", - "metro-transform-plugins": "0.80.9", + "flow-enums-runtime": "^0.0.6", + "metro": "0.80.10", + "metro-babel-transformer": "0.80.10", + "metro-cache": "0.80.10", + "metro-cache-key": "0.80.10", + "metro-minify-terser": "0.80.10", + "metro-source-map": "0.80.10", + "metro-transform-plugins": "0.80.10", "nullthrows": "^1.1.1" }, "engines": { @@ -12597,18 +12292,18 @@ } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", - "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", + "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", "peer": true }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", - "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", + "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", "peer": true, "dependencies": { - "hermes-estree": "0.20.1" + "hermes-estree": "0.23.0" } }, "node_modules/metro/node_modules/ms": { @@ -12617,21 +12312,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "peer": true }, - "node_modules/metro/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/metro/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -12654,9 +12334,9 @@ } }, "node_modules/metro/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "peer": true, "engines": { "node": ">=8.3.0" @@ -12675,11 +12355,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -12728,6 +12408,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -12791,9 +12483,9 @@ } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -12847,19 +12539,6 @@ "node": ">= 6.0.0" } }, - "node_modules/multibase": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.6.tgz", - "integrity": "sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "@multiformats/base-x": "^4.0.1" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=6.0.0" - } - }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -12873,41 +12552,9 @@ } }, "node_modules/multiformats": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.1.0.tgz", - "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==" - }, - "node_modules/multihashes": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.3.tgz", - "integrity": "sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==", - "dependencies": { - "multibase": "^4.0.1", - "uint8arrays": "^3.0.0", - "varint": "^5.0.2" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/multihashes/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" - }, - "node_modules/multihashes/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "dependencies": { - "multiformats": "^9.4.2" - } - }, - "node_modules/multihashes/node_modules/varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", + "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" }, "node_modules/murmurhash3js-revisited": { "version": "3.0.0", @@ -12981,9 +12628,9 @@ } }, "node_modules/node-abi": { - "version": "3.62.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", - "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", "dependencies": { "semver": "^7.3.5" }, @@ -12998,14 +12645,13 @@ "peer": true }, "node_modules/node-datachannel": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.8.0.tgz", - "integrity": "sha512-05vYthhjESfO5qumWRbqHI1PN71rc19DJjc14ofOLapJrTCnmhx0JYw0G5wcL56b57uHQP9WjEak3+XklrB+Og==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.11.0.tgz", + "integrity": "sha512-8/vAMms32XxgJ9FIRDXbfmmH1ROm0HBdsa/XteIcUWN4VTQN38UITTkuu6YsfQzN/CQp8YhhnfAEzEadQJ2c6Q==", "hasInstallScript": true, "dependencies": { "node-domexception": "^2.0.1", - "prebuild-install": "^7.0.1", - "rollup": "^4.14.1" + "prebuild-install": "^7.0.1" }, "engines": { "node": ">=16.0.0" @@ -13076,9 +12722,9 @@ "peer": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "peer": true }, "node_modules/node-stream-zip": { @@ -13095,9 +12741,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", - "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -13122,21 +12768,6 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -13192,10 +12823,13 @@ "peer": true }, "node_modules/ob1": { - "version": "0.80.9", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.9.tgz", - "integrity": "sha512-v9yOxowkZbxWhKOaaTyLjIm1aLy4ebMNcSn4NYJKOAI/Qv+SkfEfszpLr2GIxsccmb2Y2HA9qtsqiIJ80ucpVA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.10.tgz", + "integrity": "sha512-dJHyB0S6JkMorUSfSGcYGkkg9kmq3qDUu3ygZUKIfkr47XOPuG35r2Sk6tbwtHXbdKIXmcMvM8DF2CwgdyaHfQ==", "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, "engines": { "node": ">=18" } @@ -13209,9 +12843,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13501,22 +13138,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", - "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-timeout": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", @@ -13536,6 +13157,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -13605,21 +13232,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz", - "integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -13635,9 +13271,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -13672,21 +13308,21 @@ } }, "node_modules/pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.3.2.tgz", + "integrity": "sha512-WtARBjgZ7LNEkrGWxMBN/jvlFiE17LTbBoH0konmBU684Kd0uIiDwBXlcTCW7iJnA6HfIKwUssS/2AC6cDEanw==", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" @@ -13725,25 +13361,25 @@ } }, "node_modules/pino-http": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-9.0.0.tgz", - "integrity": "sha512-Q9QDNEz0vQmbJtMFjOVr2c9yL92vHudjmr3s3m6J1hbw3DBGFZJm3TIj9TWyynZ4GEsEA9SOtni4heRUr6lNOg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.2.0.tgz", + "integrity": "sha512-am03BxnV3Ckx68OkbH0iZs3indsrH78wncQ6w1w51KroIbvJZNImBKX2X1wjdY8lSyaJ0UrX/dnO2DY3cTeCRw==", "dependencies": { "get-caller-file": "^2.0.5", - "pino": "^8.17.1", - "pino-std-serializers": "^6.2.2", + "pino": "^9.0.0", + "pino-std-serializers": "^7.0.0", "process-warning": "^3.0.0" } }, "node_modules/pino-pretty": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", - "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.2.tgz", + "integrity": "sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A==", "dev": true, "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", + "fast-copy": "^3.0.2", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", @@ -13753,7 +13389,7 @@ "pump": "^3.0.0", "readable-stream": "^4.0.0", "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", + "sonic-boom": "^4.0.1", "strip-json-comments": "^3.1.1" }, "bin": { @@ -13786,9 +13422,14 @@ } }, "node_modules/pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==" }, "node_modules/pirates": { "version": "4.0.6", @@ -13907,9 +13548,9 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -14029,13 +13670,9 @@ "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" }, "node_modules/progress-events": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.0.tgz", - "integrity": "sha512-zIB6QDrSbPfRg+33FZalluFIowkbV5Xh1xSuetjG+rlC5he6u2dc6VQJ0TbMdlN3R1RHdpOqxEFMKTnQ+itUwA==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", + "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==" }, "node_modules/promise": { "version": "8.3.0", @@ -14060,9 +13697,9 @@ } }, "node_modules/protons-runtime": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.4.0.tgz", - "integrity": "sha512-XfA++W/WlQOSyjUyuF5lgYBfXZUEMP01Oh1C2dSwZAlF2e/ZrMRPfWonXj6BGM+o8Xciv7w0tsRMKYwYEuQvaw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.5.0.tgz", + "integrity": "sha512-EsALjF9QsrEk6gbCx3lmfHxVN0ah7nG3cY7GySD4xf4g8cr7g543zB88Foh897Sr1RQJ9yDCUsoT1i1H/cVUFA==", "dependencies": { "uint8-varint": "^2.0.2", "uint8arraylist": "^2.4.3", @@ -14222,9 +13859,9 @@ "integrity": "sha512-kaLm7axfOnahIqD3jQ4l1e471FIFcEGebXEnhxyLscuUzV8C94xVHtWEqDDXxll7+yu/6lW0w1Ff4HbtvHvOHg==" }, "node_modules/race-signal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.0.2.tgz", - "integrity": "sha512-o3xNv0iTcIDQCXFlF6fPAMEBRjFxssgGoRqLbg06m+AdzEXXLUmoNOoUHTVz2NoBI8hHwKFKoC6IqyNtWr2bww==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.1.0.tgz", + "integrity": "sha512-VqsW1uzCXfKBd2DhA3K3NhQlqQr04+5WQ7+kHpf1HzT01Q+ePSFWZdQHXKZPuLmm2eXTZM1XLO76cq15ZRAaEA==" }, "node_modules/range-parser": { "version": "1.2.1", @@ -14288,9 +13925,9 @@ } }, "node_modules/react-devtools-core": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.2.0.tgz", - "integrity": "sha512-vZK+/gvxxsieAoAyYaiRIVFxlajb7KXhgBDV7OsoMzaAE+IqGpoxusBjIgq5ibqA2IloKu0p9n7tE68z1xs18A==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.3.1.tgz", + "integrity": "sha512-7FSb9meX0btdBQLwdFOwt6bGqvRPabmVMMslv8fgoSPqXyuGpgQe36kx8gR86XPw7aV1yVouTp6fyZ0EH+NfUw==", "peer": true, "dependencies": { "shell-quote": "^1.6.1", @@ -14298,9 +13935,9 @@ } }, "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "peer": true, "engines": { "node": ">=8.3.0" @@ -14325,22 +13962,22 @@ "peer": true }, "node_modules/react-native": { - "version": "0.74.1", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.1.tgz", - "integrity": "sha512-0H2XpmghwOtfPpM2LKqHIN7gxy+7G/r1hwJHKLV6uoyXGC/gCojRtoo5NqyKrWpFC8cqyT6wTYCLuG7CxEKilg==", + "version": "0.74.5", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.5.tgz", + "integrity": "sha512-Bgg2WvxaGODukJMTZFTZBNMKVaROHLwSb8VAGEdrlvKwfb1hHg/3aXTUICYk7dwgAnb+INbGMwnF8yeAgIUmqw==", "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "13.6.6", - "@react-native-community/cli-platform-android": "13.6.6", - "@react-native-community/cli-platform-ios": "13.6.6", - "@react-native/assets-registry": "0.74.83", - "@react-native/codegen": "0.74.83", - "@react-native/community-cli-plugin": "0.74.83", - "@react-native/gradle-plugin": "0.74.83", - "@react-native/js-polyfills": "0.74.83", - "@react-native/normalize-colors": "0.74.83", - "@react-native/virtualized-lists": "0.74.83", + "@react-native-community/cli": "13.6.9", + "@react-native-community/cli-platform-android": "13.6.9", + "@react-native-community/cli-platform-ios": "13.6.9", + "@react-native/assets-registry": "0.74.87", + "@react-native/codegen": "0.74.87", + "@react-native/community-cli-plugin": "0.74.87", + "@react-native/gradle-plugin": "0.74.87", + "@react-native/js-polyfills": "0.74.87", + "@react-native/normalize-colors": "0.74.87", + "@react-native/virtualized-lists": "0.74.87", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -14397,6 +14034,22 @@ "react-native": ">=0.60.0" } }, + "node_modules/react-native-webrtc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/react-native-webrtc/node_modules/event-target-shim": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", @@ -14446,9 +14099,9 @@ } }, "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "peer": true, "dependencies": { "async-limiter": "~1.0.0" @@ -14866,14 +14519,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14884,24 +14529,25 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "node_modules/rimraf": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", - "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14917,76 +14563,43 @@ } }, "node_modules/rimraf/node_modules/glob": { - "version": "10.3.14", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz", - "integrity": "sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rollup": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", - "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.17.2", - "@rollup/rollup-android-arm64": "4.17.2", - "@rollup/rollup-darwin-arm64": "4.17.2", - "@rollup/rollup-darwin-x64": "4.17.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", - "@rollup/rollup-linux-arm-musleabihf": "4.17.2", - "@rollup/rollup-linux-arm64-gnu": "4.17.2", - "@rollup/rollup-linux-arm64-musl": "4.17.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", - "@rollup/rollup-linux-riscv64-gnu": "4.17.2", - "@rollup/rollup-linux-s390x-gnu": "4.17.2", - "@rollup/rollup-linux-x64-gnu": "4.17.2", - "@rollup/rollup-linux-x64-musl": "4.17.2", - "@rollup/rollup-win32-arm64-msvc": "4.17.2", - "@rollup/rollup-win32-ia32-msvc": "4.17.2", - "@rollup/rollup-win32-x64-msvc": "4.17.2", - "fsevents": "~2.3.2" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -15077,9 +14690,9 @@ } }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { "version": "0.24.0-canary-efb381bbf-20230505", @@ -15110,9 +14723,9 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -15389,9 +15002,9 @@ } }, "node_modules/sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -15456,9 +15069,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "node_modules/split2": { @@ -15841,6 +15454,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "peer": true, "dependencies": { "glob": "^7.1.3" @@ -15850,9 +15464,9 @@ } }, "node_modules/terser": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", - "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "version": "5.31.5", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.5.tgz", + "integrity": "sha512-YPmas0L0rE1UyLL/llTWA0SiDOqIcAQYLeUj7cJYzXHlRTAnMSg9pPe4VJ5PlKvTrPQsdVFuiRiwyeNlYgwh2Q==", "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -15892,9 +15506,9 @@ "dev": true }, "node_modules/thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", "dependencies": { "real-require": "^0.2.0" } @@ -15995,13 +15609,10 @@ } }, "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, "bin": { "nodetouch": "bin/nodetouch.js" } @@ -16029,9 +15640,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -16116,9 +15727,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -16146,9 +15757,9 @@ } }, "node_modules/uint8arrays": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.0.3.tgz", - "integrity": "sha512-6LBuKji28kHjgPJMkQ6GDaBb1lRwIhyOYq6pDGwYMoDPfImE9SkuYENVmR0yu9yGgs2clHUSY9fKDukR+AXfqQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", "dependencies": { "multiformats": "^13.0.0" } @@ -16234,9 +15845,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", @@ -16254,7 +15865,7 @@ "peer": true, "dependencies": { "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16273,9 +15884,9 @@ } }, "node_modules/utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==" }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -16332,6 +15943,34 @@ "defaults": "^1.0.3" } }, + "node_modules/weald": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/weald/-/weald-1.0.2.tgz", + "integrity": "sha512-iG5cIuBwsPe1ZcoGGd4X6QYlepU1vLr4l4oWpzQWqeJPSo9B8bxxyE6xlnj3TCmThtha7gyVL+uuZgUFkPyfDg==", + "dependencies": { + "ms": "^3.0.0-canary.1", + "supports-color": "^9.4.0" + } + }, + "node_modules/weald/node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "engines": { + "node": ">=12.13" + } + }, + "node_modules/weald/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -16459,9 +16098,9 @@ "dev": true }, "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { "emoji-regex": "^10.3.0", @@ -16515,9 +16154,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -16577,9 +16216,12 @@ "peer": true }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -16610,9 +16252,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, "engines": { "node": ">=12.20" diff --git a/package.json b/package.json index 3fe82b8..3348bf8 100644 --- a/package.json +++ b/package.json @@ -16,40 +16,40 @@ "author": "ADAMANT Foundation ", "license": "GPLv3", "devDependencies": { - "@commitlint/cli": "^19.2.1", - "@commitlint/config-conventional": "^19.1.0", + "@commitlint/cli": "^19.4.0", + "@commitlint/config-conventional": "^19.2.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^1.4.11", - "@types/node": "^20.11.30", + "@types/node": "^20.14.8", "eslint": "^8.57.0", - "gts": "^5.2.0", - "husky": "^9.0.11", - "lint-staged": "^15.2.2", - "nodemon": "^3.1.0", - "pino-pretty": "^11.0.0", - "prettier": "^3.2.5", - "rimraf": "^5.0.5", - "typescript": "^5.4.3" + "gts": "^5.3.1", + "husky": "^9.1.4", + "lint-staged": "^15.2.8", + "nodemon": "^3.1.4", + "pino-pretty": "^11.2.2", + "prettier": "^3.3.3", + "rimraf": "^6.0.1", + "typescript": "^5.5.4" }, "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@helia/unixfs": "^3.0.6", - "@libp2p/bootstrap": "^10.0.22", - "@libp2p/peer-id": "^4.1.1", - "@libp2p/tcp": "^9.0.24", - "blockstore-fs": "^1.1.10", + "@chainsafe/libp2p-noise": "^15.1.0", + "@helia/unixfs": "^3.0.7", + "@libp2p/bootstrap": "^10.1.4", + "@libp2p/peer-id": "^4.2.3", + "@libp2p/tcp": "^9.1.4", + "blockstore-fs": "^2.0.0", "cors": "^2.8.5", - "cron": "^3.1.6", - "datastore-fs": "^9.1.8", + "cron": "^3.1.7", + "datastore-fs": "^10.0.0", "express": "^4.19.2", "go-get-folder-size": "^0.5.5", - "helia": "^4.2.1", + "helia": "^4.2.5", "json5": "^2.2.3", "multer": "^1.4.5-lts.1", - "multiformats": "^13.1.0", - "pino": "^8.20.0", - "pino-http": "^9.0.0" + "multiformats": "^13.2.2", + "pino": "^9.3.2", + "pino-http": "^10.2.0" }, "lint-staged": { "*.(ts|js)": [ From 4890d43874a32348f283af50f8906693ca92e9fe Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 13:22:58 +0100 Subject: [PATCH 073/167] chore(package.json): fix `main` entry point --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3348bf8..55e731d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "ipfs-node", "version": "0.0.1", "description": "IPFS decentralized file storage and transfers", - "main": "index.js", + "main": "dist/index.js", "type": "module", "scripts": { "build": "npx rimraf dist; tsc", From f22655db4963f5032a53b9a37c00c99d32d9a7df Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 13:23:15 +0100 Subject: [PATCH 074/167] chore(package.json): mark as private --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 55e731d..262a2f3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "description": "IPFS decentralized file storage and transfers", "main": "dist/index.js", + "private": true, "type": "module", "scripts": { "build": "npx rimraf dist; tsc", From b9a873b621797ff1028c5d06ae52fd0ea19f114a Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 13:44:49 +0100 Subject: [PATCH 075/167] chore: use one config --- .gitignore | 1 + config.default.json5 | 18 ++++++++---------- src/config.ts | 5 ++--- src/index.ts | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index a3bc1f4..b137b65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .idea dist/ +config.json5 diff --git a/config.default.json5 b/config.default.json5 index 2258bc9..c2d8767 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -1,16 +1,16 @@ { nodes: [ { - name: "ipfs1", - multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq" + name: 'ipfs1', + multiAddr: '/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq' }, { - name: "ipfs2", - multiAddr: "/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWJw99nqrQ2L2joFuGCF9EN9EyF8ZrvGr1odQ61HoPrbXd", + name: 'ipfs2', + multiAddr: '/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWJw99nqrQ2L2joFuGCF9EN9EyF8ZrvGr1odQ61HoPrbXd' }, { - name: "ipfs3", - multiAddr: "/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWHTvrWSTCXZFR2vS8Cbw17b5peK2aSBr8CmkyA96CDqiU" + name: 'ipfs3', + multiAddr: '/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWHTvrWSTCXZFR2vS8Cbw17b5peK2aSBr8CmkyA96CDqiU' } ], storeFolder: '.adm-ipfs', @@ -22,9 +22,7 @@ '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' ], - listen: [ - '/ip4/0.0.0.0/tcp/4001', - ] + listen: ['/ip4/0.0.0.0/tcp/4001'] }, serverPort: 4000, autoPeeringPeriod: '*/10 * * * * *', @@ -33,6 +31,6 @@ maxFileCount: 5, findFileTimeout: 20000, cors: { - originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] + originRegexps: ['.*.adamant.im', 'adm.im', '.*.vercel.app', '.*.surge.sh', 'localhost:8080'] } } diff --git a/src/config.ts b/src/config.ts index a73bfd2..8a09a90 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,10 +6,9 @@ import JSON5 from 'json5' const currDir = dirname(fileURLToPath(import.meta.url)) const rootDir = join(currDir, '..') -const configName = process.argv.slice(2)[0] || 'default' -export const configFileName = `config.${configName}.json5` +export const CONFIG_FILE_NAME = 'config.json5' -const configPath = join(rootDir, configFileName) +const configPath = join(rootDir, CONFIG_FILE_NAME) export const config = JSON5.parse(fs.readFileSync(configPath, 'utf8')) diff --git a/src/index.ts b/src/index.ts index 40802fe..60f0086 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,12 @@ import express, { NextFunction } from 'express' import { Request, Response } from 'express' import { pino } from './utils/logger.js' -import { config, configFileName } from './config.js' +import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' import cors from 'cors' import * as routers from './api/index.js' -pino.logger.info(`Using config file: ${configFileName}`) +pino.logger.info(`Using config file: ${CONFIG_FILE_NAME}`) diskUsageCron.start() From ea2235fb25020b80ebe7969709f89276fc356acb Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 15:16:40 +0100 Subject: [PATCH 076/167] fix(`api/file/:cid`): not timing out when using `ifs.stat()` --- src/api/file.ts | 27 +++++++--------------- src/utils/file.ts | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 src/utils/file.ts diff --git a/src/api/file.ts b/src/api/file.ts index c1df237..323d606 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -1,12 +1,12 @@ import { Router } from 'express' import { CID } from 'multiformats/cid' -import { Readable } from 'node:stream' import { multerStorage } from '../multer.js' import { config } from '../config.js' -import { helia, ifs } from '../helia.js' +import { helia } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' +import { downloadFile, getFileStats } from '../utils/file.js' const router = Router() @@ -57,20 +57,10 @@ router.post('/upload', multerStorage.array('files', config.maxFileCount), async router.get('/:cid', async (req, res) => { try { const cid = CID.parse(req.params.cid) - const fileStats = await ifs.stat(cid) - let streamStarted = false - const abortController = new AbortController() - const timeout = setTimeout(() => { - if (!streamStarted) { - abortController.abort(new Error('Cannot find requested CID. Request timed out.')) - } - }, config.findFileTimeout) + const fileStats = await getFileStats(cid) - const stream = Readable.from( - ifs.cat(cid, { - signal: abortController.signal - }) - ) + let streamStarted = false + const stream = downloadFile(cid) stream.on('data', () => { if (!streamStarted) { @@ -79,20 +69,19 @@ router.get('/:cid', async (req, res) => { res.set('Content-Length', fileStats.fileSize.toString()) } }) + stream.on('error', (err) => { pino.logger.error(err) res.status(408).send({ error: err.message }) }) - stream.on('end', () => { - clearTimeout(timeout) - }) + stream.pipe(res) } catch (error) { pino.logger.error(error) res.status(500).send({ - error: 'Internal Server Error. Check the logs for details.' + error: error.message }) } }) diff --git a/src/utils/file.ts b/src/utils/file.ts new file mode 100644 index 0000000..b3a5c40 --- /dev/null +++ b/src/utils/file.ts @@ -0,0 +1,58 @@ +import { CID } from 'multiformats/cid' +import { Readable } from 'node:stream' +import { clearTimeout } from 'node:timers' +import { config } from '../config.js' +import { ifs } from '../helia.js' + +/** + * Return file statistics by CID. + * Throws a timeout error if the file is not found. + */ +export async function getFileStats(cid: CID) { + let timeout: NodeJS.Timeout | undefined + try { + const abortController = new AbortController() + timeout = setTimeout(() => { + abortController.abort(new Error('Cannot find requested CID. Request timed out.')) + }, config.findFileTimeout) + + const stats = await ifs.stat(cid, { signal: abortController.signal }) + return stats + } catch (err) { + console.log(err) + clearTimeout(timeout) + + throw new Error('Cannot find requested CID. Request timed out.') + } +} + +/** + * Return a file stream by CID. + * Throws a timeout error if the file is not found. + */ +export function downloadFile(cid: CID) { + const abortController = new AbortController() + + let aborted = false + const abort = () => { + if (aborted) return + + aborted = true + abortController.abort(new Error('Unable to retrieve the file. Request timed out.')) + } + const abortTimer = setTimeout(abort, config.findFileTimeout) + + const stream = Readable.from( + ifs.cat(cid, { + signal: abortController.signal + }) + ) + stream.on('data', () => { + clearTimeout(abortTimer) + }) + stream.on('end', () => { + clearTimeout(abortTimer) + }) + + return stream +} From b8cec357dc8d5dc2ee285ae04488243ff0dc975a Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 16:28:30 +0100 Subject: [PATCH 077/167] chore: rollback blockstore/datastore versions due breaking changes --- package-lock.json | 51 +++++++++++------------------------------------ package.json | 4 ++-- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5a2487..57c1c61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@libp2p/bootstrap": "^10.1.4", "@libp2p/peer-id": "^4.2.3", "@libp2p/tcp": "^9.1.4", - "blockstore-fs": "^2.0.0", + "blockstore-fs": "^1.1.11", "cors": "^2.8.5", "cron": "^3.1.7", - "datastore-fs": "^10.0.0", + "datastore-fs": "^9.1.9", "express": "^4.19.2", "go-get-folder-size": "^0.5.5", "helia": "^4.2.5", @@ -6561,24 +6561,20 @@ } }, "node_modules/blockstore-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-2.0.0.tgz", - "integrity": "sha512-PrGM39gdo/NhYLA7+gUN+9tSD33Y38mCwqW3s/PsbGpPRsSZiNjscCclwAoZRHb8UbpRtQT2KwXo23gCzagxJg==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-1.1.11.tgz", + "integrity": "sha512-tySmmketfD9CsAeJCdsHTUdy6I2AxLdAlbcOx+KdnA8J7UJ7uKfXxQYNzmx6ZRpXVp/7ccNUWSugwoUstoW83A==", "dependencies": { + "blockstore-core": "^4.0.0", "fast-write-atomic": "^0.2.1", "interface-blockstore": "^5.0.0", - "interface-store": "6.0.0", + "interface-store": "^5.0.0", "it-glob": "^3.0.1", "it-map": "^3.0.5", "it-parallel-batch": "^3.0.4", "multiformats": "^13.0.1" } }, - "node_modules/blockstore-fs/node_modules/interface-store": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", - "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" - }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -7538,42 +7534,19 @@ } }, "node_modules/datastore-fs": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-10.0.0.tgz", - "integrity": "sha512-XeTImTVXjSApJzqn/iDfoWCLWqTdHO/lHT9+fgghlBtbfe9qAm2YImlSlnN8ryfwmKtkgLQm9tI4gSL9vsTZyA==", + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-9.1.9.tgz", + "integrity": "sha512-uCU1BaNENmpjduYaDHQFC+u9YgQG7piPaWaFIvEtoXIYvmnvLmHJFDLsgTEB04TPdvlNLD/YlO/9zEDklE5tYg==", "dependencies": { - "datastore-core": "10.0.0", + "datastore-core": "^9.0.0", "fast-write-atomic": "^0.2.1", "interface-datastore": "^8.0.0", - "interface-store": "6.0.0", + "interface-store": "^5.0.0", "it-glob": "^3.0.1", "it-map": "^3.0.5", "it-parallel-batch": "^3.0.4" } }, - "node_modules/datastore-fs/node_modules/datastore-core": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-10.0.0.tgz", - "integrity": "sha512-/b5KDmSAIFizS/ZzTTU+ZdfOcQc6mRmbc4/EK/WOzbEO5c0KWyjRLsZ3DWwh9wiPu93V7pX1oC+Or61k8UPPsg==", - "dependencies": { - "@libp2p/logger": "^4.0.6", - "interface-datastore": "^8.0.0", - "interface-store": "6.0.0", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-map": "^3.0.5", - "it-merge": "^3.0.3", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-sort": "^3.0.4", - "it-take": "^3.0.4" - } - }, - "node_modules/datastore-fs/node_modules/interface-store": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", - "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" - }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", diff --git a/package.json b/package.json index 262a2f3..174a17e 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,10 @@ "@libp2p/bootstrap": "^10.1.4", "@libp2p/peer-id": "^4.2.3", "@libp2p/tcp": "^9.1.4", - "blockstore-fs": "^2.0.0", + "blockstore-fs": "^1.1.11", "cors": "^2.8.5", "cron": "^3.1.7", - "datastore-fs": "^10.0.0", + "datastore-fs": "^9.1.9", "express": "^4.19.2", "go-get-folder-size": "^0.5.5", "helia": "^4.2.5", From e627dcb9fd8b3219413f324d44a9203fee2e969d Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 16:41:11 +0100 Subject: [PATCH 078/167] feat: bootstrap only with known nodes --- config.default.json5 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index c2d8767..4dba1be 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -17,10 +17,9 @@ logLevel: 'debug', peerDiscovery: { bootstrap: [ - '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' + '/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq', // ipfs1 + '/ip4/154.26.159.245/tcp/4001/p2p/12D3KooWJw99nqrQ2L2joFuGCF9EN9EyF8ZrvGr1odQ61HoPrbXd', // ipfs2 + '/ip4/109.123.240.102/tcp/4001/p2p/12D3KooWHTvrWSTCXZFR2vS8Cbw17b5peK2aSBr8CmkyA96CDqiU' // ipfs3 ], listen: ['/ip4/0.0.0.0/tcp/4001'] }, From 85f4ec2b2e8cb86a152ce6ab433dcd38d03ee994 Mon Sep 17 00:00:00 2001 From: bludnic Date: Fri, 9 Aug 2024 17:14:42 +0100 Subject: [PATCH 079/167] feat(file): return 408 when file not found --- src/api/file.ts | 16 +++++++++++----- src/utils/file.ts | 11 +++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/api/file.ts b/src/api/file.ts index 323d606..9f8defc 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -6,7 +6,7 @@ import { helia } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' -import { downloadFile, getFileStats } from '../utils/file.js' +import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js' const router = Router() @@ -79,10 +79,16 @@ router.get('/:cid', async (req, res) => { stream.pipe(res) } catch (error) { - pino.logger.error(error) - res.status(500).send({ - error: error.message - }) + if (error instanceof FileNotFoundError) { + res.status(408).send({ + error: error.message + }) + } else { + pino.logger.error(error) + res.status(500).send({ + error: error.message + }) + } } }) diff --git a/src/utils/file.ts b/src/utils/file.ts index b3a5c40..64b971e 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -22,7 +22,7 @@ export async function getFileStats(cid: CID) { console.log(err) clearTimeout(timeout) - throw new Error('Cannot find requested CID. Request timed out.') + throw new FileNotFoundError('Cannot find requested CID. Request timed out.') } } @@ -38,7 +38,7 @@ export function downloadFile(cid: CID) { if (aborted) return aborted = true - abortController.abort(new Error('Unable to retrieve the file. Request timed out.')) + abortController.abort(new FileNotFoundError('Unable to retrieve the file. Request timed out.')) } const abortTimer = setTimeout(abort, config.findFileTimeout) @@ -56,3 +56,10 @@ export function downloadFile(cid: CID) { return stream } + +export class FileNotFoundError extends Error { + constructor(message: string) { + super(message) + this.name = 'FileNotFoundError' + } +} From 63e19ec302fd78226bd362dd831fb7a92a4d2bd0 Mon Sep 17 00:00:00 2001 From: bludnic Date: Tue, 26 Nov 2024 00:25:31 +0000 Subject: [PATCH 080/167] fix(api/file): validate max file count --- config.default.json5 | 2 +- src/api/file.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 4dba1be..7945e01 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -27,7 +27,7 @@ autoPeeringPeriod: '*/10 * * * * *', diskUsageScanPeriod: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. - maxFileCount: 5, + maxFileCount: 10, findFileTimeout: 20000, cors: { originRegexps: ['.*.adamant.im', 'adm.im', '.*.vercel.app', '.*.surge.sh', 'localhost:8080'] diff --git a/src/api/file.ts b/src/api/file.ts index 9f8defc..3640148 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -10,7 +10,7 @@ import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js' const router = Router() -router.post('/upload', multerStorage.array('files', config.maxFileCount), async (req, res) => { +router.post('/upload', multerStorage.array('files'), async (req, res) => { if (!req.files) { res.statusCode = 400 return res.send({ @@ -18,6 +18,13 @@ router.post('/upload', multerStorage.array('files', config.maxFileCount), async }) } + if (req.files.length > config.maxFileCount) { + res.status(400).send({ + error: `File limit exceeded. Max ${config.maxFileCount} allowed.` + }) + return + } + try { const files = flatFiles(req.files as UnixFsMulterFile[]) pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) From 906156be6658581cb02c8b7e3b9ee74f21821fa5 Mon Sep 17 00:00:00 2001 From: bludnic Date: Wed, 22 Jan 2025 00:08:28 +0000 Subject: [PATCH 081/167] chore: simplify default cors config --- config.default.json5 | 3 ++- src/index.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 7945e01..ebfa126 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -30,6 +30,7 @@ maxFileCount: 10, findFileTimeout: 20000, cors: { - originRegexps: ['.*.adamant.im', 'adm.im', '.*.vercel.app', '.*.surge.sh', 'localhost:8080'] + origin: '*', + credentials: true } } diff --git a/src/index.ts b/src/index.ts index 60f0086..dd520c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,8 @@ app.use(pino) app.use( cors({ - origin: config.cors.originRegexps.map((item: string) => new RegExp(`^https?:\\/\\/${item}`)), + origin: config.cors.origin, + credentials: config.cors.credentials, methods: ['GET', 'POST'] }) ) From 6ac0164ab1b262326621ecd82bf851baad717083 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Tue, 28 Apr 2026 00:03:04 +0300 Subject: [PATCH 082/167] chore: add static analysis rules and local security check scripts - Add custom Semgrep rules (.semgrep/rules.yml): express error message leak detection (WARNING + ERROR variants), multer originalname sanitization reminder, console.log enforcement in favour of structured logger - Add npm scripts: security:audit, security:audit:full, security:semgrep - Add security scan artifacts to .gitignore --- .github/workflows/security.yml | 76 ++++++++++++++++++++++++++ .gitignore | 2 + .semgrep/rules.yml | 98 ++++++++++++++++++++++++++++++++++ package.json | 5 +- 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/security.yml create mode 100644 .semgrep/rules.yml diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..0dc4395 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,76 @@ +name: Security Audit + +on: + push: + branches: [master, dev] + pull_request: + branches: [master, dev] + schedule: + # Weekly full scan every Sunday at 02:00 UTC + - cron: '0 2 * * 0' + +jobs: + # ----------------------------------------------------------------------- + # 1. Semgrep SAST — static application security testing + # ----------------------------------------------------------------------- + semgrep: + name: Semgrep SAST + runs-on: ubuntu-latest + container: + image: semgrep/semgrep + steps: + - uses: actions/checkout@v4 + + - name: Run Semgrep — OWASP Top 10 + run: | + semgrep \ + --config p/owasp-top-ten \ + --config p/nodejs \ + --config p/expressjs \ + --config p/typescript \ + --config .semgrep/rules.yml \ + --no-strict \ + --sarif \ + --output semgrep-results.sarif \ + --error \ + src/ + + - name: Upload Semgrep SARIF to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: semgrep-results.sarif + category: semgrep + + # ----------------------------------------------------------------------- + # 2. npm audit — vulnerable dependency check (SCA) + # ----------------------------------------------------------------------- + npm-audit: + name: npm Dependency Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run npm audit (fail on critical) + run: npm audit --audit-level=critical + + - name: Generate full audit report (artifact) + if: always() + run: npm audit --json > npm-audit-report.json || true + + - name: Upload audit report + uses: actions/upload-artifact@v4 + if: always() + with: + name: npm-audit-report + path: npm-audit-report.json + retention-days: 30 diff --git a/.gitignore b/.gitignore index b137b65..878503f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules .idea dist/ config.json5 +semgrep-results.sarif +npm-audit-report.json diff --git a/.semgrep/rules.yml b/.semgrep/rules.yml new file mode 100644 index 0000000..de4b97e --- /dev/null +++ b/.semgrep/rules.yml @@ -0,0 +1,98 @@ +rules: + # ----------------------------------------------------------------------- + # Rule: express-error-message-leak + # + # Detects Express response handlers that pass err.message directly into + # the response body. Error messages from third-party libraries often + # contain internal file paths, stack frames, or database schema details + # that should never be exposed to API consumers. + # + # Safe pattern: return a static string and log the full error server-side. + # ----------------------------------------------------------------------- + - id: express-error-message-leak + patterns: + - pattern: "res.$METHOD(..., { ..., error: $ERR.message, ... })" + - pattern-not: "res.$METHOD(..., { ..., error: \"...\", ... })" + message: > + Potential internal error details exposed to the client via err.message. + Return a safe static message (e.g. "Internal Server Error") and log the + full error server-side with a correlation ID. + languages: [typescript, javascript] + severity: WARNING + metadata: + category: security + owasp: "A09:2021" + cwe: "CWE-209" + references: + - https://cwe.mitre.org/data/definitions/209.html + + # ----------------------------------------------------------------------- + # Rule: express-500-error-message-leak + # + # Stricter variant of express-error-message-leak. Targets the specific + # pattern of res.status(500).send({ error: err.message }) which is an + # unambiguous information leak — a 500 always means an unhandled + # exception whose message is internal by definition. + # ----------------------------------------------------------------------- + - id: express-500-error-message-leak + patterns: + - pattern: | + res.status(500).send({ error: $ERR.message }) + message: > + res.status(500) with err.message leaks internal exception details + (file paths, stack frames, library internals) to the client. + Replace with a safe static message. + languages: [typescript, javascript] + severity: ERROR + metadata: + category: security + owasp: "A09:2021" + cwe: "CWE-209" + + # ----------------------------------------------------------------------- + # Rule: multer-unsafe-originalname + # + # Flags every use of file.originalname from a Multer-parsed request. + # This value is taken verbatim from the Content-Disposition header and + # is fully attacker-controlled. Common attacks include path traversal + # (../../etc/passwd), null-byte injection, and Unicode homoglyph abuse. + # + # Safe pattern: sanitize before any use — strip path separators, + # enforce a maximum length, normalize Unicode (NFKC). + # ----------------------------------------------------------------------- + - id: multer-unsafe-originalname + pattern: $FILE.originalname + message: > + file.originalname is attacker-controlled (from the Content-Disposition + header). Sanitize before use: strip path separators, enforce max length, + normalize Unicode. + languages: [typescript, javascript] + severity: WARNING + metadata: + category: security + owasp: "A03:2021" + cwe: "CWE-22" + references: + - https://cwe.mitre.org/data/definitions/22.html + + # ----------------------------------------------------------------------- + # Rule: no-console-log + # + # Enforces use of a structured logger (pino, winston, etc.) instead of + # console.log. console.log has no severity level, no structured fields, + # and no output filtering — making it easy to inadvertently log sensitive + # values (tokens, CIDs, PII) in a format that cannot be suppressed + # in production without code changes. + # ----------------------------------------------------------------------- + - id: no-console-log + pattern: console.log(...) + message: > + Avoid console.log in production code. Use a structured logger with + severity levels (e.g. pino, winston) so sensitive data can be filtered + and log verbosity can be controlled without code changes. + languages: [typescript, javascript] + severity: INFO + metadata: + category: best-practice + references: + - https://getpino.io diff --git a/package.json b/package.json index 174a17e..2487559 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "prepare": "husky", "bump-patch": "npm version patch", "bump-minor": "npm version minor", - "bump-major": "npm version major" + "bump-major": "npm version major", + "security:audit": "npm audit --audit-level=high", + "security:audit:full": "npm audit --json > npm-audit-report.json", + "security:semgrep": "semgrep --config p/owasp-top-ten --config p/nodejs --config p/expressjs --config p/typescript --config .semgrep/rules.yml --no-strict src/" }, "author": "ADAMANT Foundation ", "license": "GPLv3", From c43c370688dbf4c4858ff60d05937d2907a32363 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Wed, 29 Apr 2026 00:29:09 +0300 Subject: [PATCH 083/167] fix(security): update vulnerable dependencies via npm audit fix --- package-lock.json | 1309 +++++++++++++++++++++++---------------------- 1 file changed, 664 insertions(+), 645 deletions(-) diff --git a/package-lock.json b/package-lock.json index 57c1c61..e288868 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,12 +93,14 @@ "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -427,18 +429,20 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -467,84 +471,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -2201,32 +2148,25 @@ "peer": true }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", "peer": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "peer": true - }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2260,14 +2200,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2627,10 +2567,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2907,99 +2848,13 @@ } }, "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, "node_modules/@isaacs/ttlcache": { @@ -3779,16 +3634,6 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -4065,9 +3910,10 @@ } }, "node_modules/@react-native-community/cli-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", "peer": true, "dependencies": { "argparse": "^1.0.7", @@ -6140,10 +5986,11 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6576,22 +6423,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -6602,6 +6450,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6609,12 +6458,29 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6729,20 +6595,32 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "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==", + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -6860,10 +6738,11 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -6979,6 +6858,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" @@ -6991,10 +6871,11 @@ } }, "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -7003,16 +6884,18 @@ } }, "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" }, "node_modules/cli-truncate/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -7026,12 +6909,13 @@ } }, "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -7144,10 +7028,11 @@ "peer": true }, "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } @@ -7181,32 +7066,24 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", "peer": true, "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -7222,11 +7099,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "peer": true }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "peer": true + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -7331,6 +7212,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7395,9 +7277,10 @@ "peer": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7490,9 +7373,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7717,22 +7601,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-data-property": { - "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==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delay": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", @@ -7762,6 +7630,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -7827,11 +7696,19 @@ "node": ">=8" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/ee-first": { "version": "1.1.1", @@ -7853,6 +7730,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "peer": true, "engines": { "node": ">= 0.8" } @@ -7891,6 +7769,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -7934,12 +7813,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -7948,6 +7825,19 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, "engines": { "node": ">= 0.4" } @@ -8197,10 +8087,11 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8480,44 +8371,49 @@ "peer": true }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -8528,6 +8424,15 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -8629,19 +8534,16 @@ "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" }, "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", + "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "peer": true, "dependencies": { "strnum": "^1.0.5" @@ -8715,16 +8617,17 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -8735,14 +8638,25 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-cache-dir": { "version": "2.1.0", @@ -8806,10 +8720,11 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/flow-enums-runtime": { "version": "0.0.6", @@ -8827,12 +8742,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -8941,10 +8857,11 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -8953,15 +8870,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8975,6 +8898,19 @@ "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -9104,11 +9040,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9511,36 +9448,16 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9667,18 +9584,23 @@ "dev": true }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/human-signals": { @@ -9751,9 +9673,10 @@ "dev": true }, "node_modules/image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", "peer": true, "dependencies": { "queue": "6.0.2" @@ -10112,6 +10035,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -10521,21 +10445,19 @@ } }, "node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@isaacs/cliui": "^9.0.0" }, "engines": { "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest-environment-node": { @@ -10883,10 +10805,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -11186,10 +11109,11 @@ "peer": true }, "node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -11204,21 +11128,22 @@ "dev": true }, "node_modules/lint-staged": { - "version": "15.2.8", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", - "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", - "dev": true, - "dependencies": { - "chalk": "~5.3.0", - "commander": "~12.1.0", - "debug": "~4.3.6", - "execa": "~8.0.1", - "lilconfig": "~3.1.2", - "listr2": "~8.2.4", - "micromatch": "~4.0.7", - "pidtree": "~0.6.0", - "string-argv": "~0.3.2", - "yaml": "~2.5.0" + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -11230,11 +11155,37 @@ "url": "https://opencollective.com/lint-staged" } }, + "node_modules/lint-staged/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lint-staged/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/listr2": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", - "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", @@ -11263,10 +11214,11 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -11392,6 +11344,7 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", @@ -11407,10 +11360,11 @@ } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, + "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -11422,10 +11376,11 @@ } }, "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -11434,10 +11389,11 @@ } }, "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -11450,6 +11406,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" }, @@ -11461,12 +11418,13 @@ } }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, + "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.0.0" + "get-east-asian-width": "^1.3.1" }, "engines": { "node": ">=18" @@ -11480,6 +11438,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" }, @@ -11495,6 +11454,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" @@ -11507,10 +11467,11 @@ } }, "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -11523,12 +11484,13 @@ } }, "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -11765,6 +11727,15 @@ "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", "peer": true }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -11792,9 +11763,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-options": { "version": "3.0.4", @@ -11998,9 +11973,10 @@ } }, "node_modules/metro-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", "peer": true, "dependencies": { "argparse": "^1.0.7", @@ -12328,9 +12304,10 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -12386,6 +12363,7 @@ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -12414,9 +12392,10 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12680,9 +12659,10 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", "peer": true, "engines": { "node": ">= 6.13.0" @@ -12816,9 +12796,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -12855,9 +12836,10 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "peer": true, "engines": { "node": ">= 0.8" @@ -13230,9 +13212,10 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -13244,14 +13227,16 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -13732,11 +13717,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -13845,14 +13831,15 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -14505,7 +14492,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/rimraf": { "version": "6.0.1", @@ -14526,24 +14514,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -14559,15 +14563,16 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14707,23 +14712,24 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -14733,6 +14739,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -14740,12 +14747,23 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -14756,7 +14774,8 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serialize-error": { "version": "2.1.0", @@ -14768,41 +14787,35 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "peer": true }, - "node_modules/set-function-length": { - "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==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -14849,14 +14862,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -14951,6 +15019,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -14963,10 +15032,11 @@ } }, "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -15110,9 +15180,10 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -15173,30 +15244,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -15216,19 +15263,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -15296,6 +15330,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -15331,9 +15366,10 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -15553,15 +15589,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "peer": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -16006,10 +16033,11 @@ } }, "node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -16022,29 +16050,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -16053,10 +16064,11 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -16065,16 +16077,18 @@ } }, "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -16088,12 +16102,13 @@ } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -16189,14 +16204,18 @@ "peer": true }, "node_modules/yaml": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", - "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { From 1ddc0a0d64445b1ea0ba2369888f946b7e508e93 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Wed, 29 Apr 2026 01:31:35 +0300 Subject: [PATCH 084/167] fix(security): centralize error handling, prevent internal detail leakage --- src/api/debug.ts | 6 +- src/api/file.ts | 23 +++---- src/api/helia.ts | 23 ++----- src/api/libp2p.ts | 148 +++++++++++++++++++++++----------------------- src/index.ts | 6 +- 5 files changed, 92 insertions(+), 114 deletions(-) diff --git a/src/api/debug.ts b/src/api/debug.ts index b84d4c5..efcd3e6 100644 --- a/src/api/debug.ts +++ b/src/api/debug.ts @@ -5,7 +5,7 @@ import { helia } from '../helia.js' const router = Router() -router.get('/autopeering', async (req, res) => { +router.get('/autopeering', async (req, res, next) => { try { const nodes = getNodesList([helia.libp2p.peerId.toString()]) pino.logger.info(`Peering nodes list: ${nodes.map((node) => node.name)}`) @@ -28,9 +28,7 @@ router.get('/autopeering', async (req, res) => { }) } catch (err) { pino.logger.error(err) - res.send({ - error: err.message - }) + next(err) } }) diff --git a/src/api/file.ts b/src/api/file.ts index 3640148..f34f822 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -10,7 +10,7 @@ import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js' const router = Router() -router.post('/upload', multerStorage.array('files'), async (req, res) => { +router.post('/upload', multerStorage.array('files'), async (req, res, next) => { if (!req.files) { res.statusCode = 400 return res.send({ @@ -54,14 +54,11 @@ router.post('/upload', multerStorage.array('files'), async (req, res) => { } catch (err) { pino.logger.error(err) - res.status(400) - res.send({ - error: err.message - }) + next(err) } }) -router.get('/:cid', async (req, res) => { +router.get('/:cid', async (req, res, next) => { try { const cid = CID.parse(req.params.cid) const fileStats = await getFileStats(cid) @@ -79,22 +76,16 @@ router.get('/:cid', async (req, res) => { stream.on('error', (err) => { pino.logger.error(err) - res.status(408).send({ - error: err.message - }) + next(err) }) stream.pipe(res) } catch (error) { if (error instanceof FileNotFoundError) { - res.status(408).send({ - error: error.message - }) + res.status(404).send({ error: 'File not found' }) + return } else { - pino.logger.error(error) - res.status(500).send({ - error: error.message - }) + next(error) } } }) diff --git a/src/api/helia.ts b/src/api/helia.ts index 8a11b43..885937a 100644 --- a/src/api/helia.ts +++ b/src/api/helia.ts @@ -19,25 +19,16 @@ router.get('/pins', async (req, res) => { }) }) -router.post('/pin/:cid', async (req, res) => { - const cid = CID.parse(req.params.cid) - +router.post('/pin/:cid', async (req, res, next) => { try { + const cid = CID.parse(req.params.cid) for await (const pin of helia.pins.add(cid)) { pino.logger.info('PINNED', pin) } + res.send({ pinned: true, cid: cid.toString() }) } catch (err) { - pino.logger.error(`Error: ${err.message}`) - res.statusCode = 500 - return res.send({ - error: err.message - }) + next(err) } - - res.send({ - pinned: true, - cid: cid.toString() - }) }) router.get('/pins/isPinned/:cid', async (req, res) => { @@ -51,7 +42,7 @@ router.get('/pins/isPinned/:cid', async (req, res) => { }) }) -router.get('/routing/findProviders/:cid', async (req, res) => { +router.get('/routing/findProviders/:cid', async (req, res, next) => { try { const cid = CID.parse(req.params.cid) @@ -65,10 +56,8 @@ router.get('/routing/findProviders/:cid', async (req, res) => { providers }) } catch (err) { - res.send({ - error: err.message - }) pino.logger.error(err) + next(err) } }) diff --git a/src/api/libp2p.ts b/src/api/libp2p.ts index 8c40fb9..9233e26 100644 --- a/src/api/libp2p.ts +++ b/src/api/libp2p.ts @@ -11,7 +11,7 @@ const router = Router() router.get( '/services/ping', - async (req: Request, res: Response) => { + async (req: Request, res: Response, next: Function) => { try { const peerId = peerIdFromString(req.query.peerId || '') const pingService = helia.libp2p.services.ping as PingService @@ -21,68 +21,72 @@ router.get( pong }) } catch (err) { - res.send({ - error: err.message - }) + next(err) } } ) -router.get('/peerStore', async (req: Request, res: Response) => { - const peerId = req.query.peerId +router.get( + '/peerStore', + async (req: Request, res: Response, next: Function) => { + const peerId = req.query.peerId - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [ - (peer) => { - if (!peerId) { - return true + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [ + (peer) => { + if (!peerId) { + return true + } + + return peer.id.toString() === peerId } + ], + limit: 10 + }) - return peer.id.toString() === peerId - } - ], - limit: 10 - }) - - res.send({ - length: peers.length, - peers: peers.map((peer) => { - return { - id: peer.id.toString() - } + res.send({ + length: peers.length, + peers: peers.map((peer) => { + return { + id: peer.id.toString() + } + }) }) - }) - } catch (err) { - res.send({ - error: err.message - }) + } catch (err) { + next(err) + } } -}) +) -router.get('/peerInfo', async (req: Request, res: Response) => { - const peerId = req.query.peerId +router.get( + '/peerInfo', + async (req: Request, res: Response, next: Function) => { + const peerId = req.query.peerId - try { - const peers = await helia.libp2p.peerStore.all({ - filters: [(peer) => peer.id.toString() === peerId], - limit: 10 - }) - - res.send({ - length: peers.length, - peer: peers - }) - } catch (err) { - res.send({ - error: err.message - }) + try { + const peers = await helia.libp2p.peerStore.all({ + filters: [(peer) => peer.id.toString() === peerId], + limit: 10 + }) + + res.send({ + length: peers.length, + peer: peers + }) + } catch (err) { + next(err) + } } -}) +) router.get( '/dial', - async (req: Request, res: Response) => { + async ( + req: Request, + res: Response, + next: Function + ) => { let peerId: PeerId | undefined let multiAddr: Multiaddr | undefined try { @@ -118,50 +122,46 @@ router.get( } catch (err) { pino.logger.warn(`Cannot dial peer: ${err.message}`) + pino.logger.error(err) + next(err) + } + } +) + +router.get( + '/connections', + async (req: Request, res: Response, next: Function) => { + try { + const peerId = req.query.peerId?.toString() || '' + const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) + res.send({ - success: false, - error: err.message + length: connections.length, + connections }) + } catch (err) { pino.logger.error(err) + next(err) } } ) -router.get('/connections', async (req: Request, res: Response) => { +router.get('/status', async (req, res, next) => { try { - const peerId = req.query.peerId?.toString() || '' - const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) - - res.send({ - length: connections.length, - connections - }) + res.send({ status: helia.libp2p.status }) } catch (err) { - pino.logger.error(err) - res.status(400) - res.send({ - error: err.message - }) + next(err) } }) -router.get('/status', async (req, res) => { - res.send({ - status: helia.libp2p.status - }) -}) - -router.get('/peers', (req, res) => { +router.get('/peers', (req, res, next) => { try { const peers = helia.libp2p.getPeers() res.send({ peers }) } catch (err) { pino.logger.error(err) - res.status(400) - res.send({ - error: err.message - }) + next(err) } }) diff --git a/src/index.ts b/src/index.ts index dd520c6..1eda332 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import express, { NextFunction } from 'express' +import express from 'express' import { Request, Response } from 'express' import { pino } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' @@ -33,9 +33,9 @@ app.use('/api/helia', routers.helia) app.use('/api/libp2p', routers.libp2p) app.use('/api/debug', routers.debug) -app.use((err: Error, req: Request, res: Response, next: NextFunction) => { +app.use((err: Error, req: Request, res: Response) => { pino.logger.error(`${err.message}\n${err.stack}`) - res.status(500).send({ error: 'Internal Server Error. See logs.' }) + res.status(500).send({ error: 'Internal Server Error' }) }) app.listen(PORT, () => { From e90da18c47ab5cbbfcfd5094abd94ca402883948 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Wed, 29 Apr 2026 01:32:54 +0300 Subject: [PATCH 085/167] chore(eslint): chage eslint config --- .eslintrc.json | 5 +- .eslintrc.override.json | 6 + package-lock.json | 693 +++++++++++++++++++++++++++++++++++++--- package.json | 1 + 4 files changed, 665 insertions(+), 40 deletions(-) create mode 100644 .eslintrc.override.json diff --git a/.eslintrc.json b/.eslintrc.json index f95bb33..3474edf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "./node_modules/gts/" + "extends": [ + "./node_modules/gts/", + "./.eslintrc.override.json" + ] } diff --git a/.eslintrc.override.json b/.eslintrc.override.json new file mode 100644 index 0000000..d83d127 --- /dev/null +++ b/.eslintrc.override.json @@ -0,0 +1,6 @@ +{ + "rules": { + "n/no-extraneous-import": "off", + "@typescript-eslint/no-unsafe-function-type": "off" + } +} diff --git a/package-lock.json b/package-lock.json index e288868..6cd2e81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.14.8", + "@typescript-eslint/eslint-plugin": "^8.59.1", "eslint": "^8.57.0", "gts": "^5.3.1", "husky": "^9.1.4", @@ -2520,25 +2521,30 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -5787,6 +5793,400 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "peer": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", @@ -5804,6 +6204,23 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/type-utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", @@ -5919,6 +6336,160 @@ } } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", @@ -7447,11 +8018,12 @@ "peer": true }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7462,6 +8034,12 @@ } } }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -11155,31 +11733,6 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/lint-staged/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/listr2": { "version": "8.3.3", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", @@ -14701,9 +15254,10 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -15571,6 +16125,54 @@ "node": ">= 4.5.0" } }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -15639,6 +16241,19 @@ "utf8-byte-length": "^1.0.1" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", diff --git a/package.json b/package.json index 2487559..c16a53c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/express": "^4.17.21", "@types/multer": "^1.4.11", "@types/node": "^20.14.8", + "@typescript-eslint/eslint-plugin": "^8.59.1", "eslint": "^8.57.0", "gts": "^5.3.1", "husky": "^9.1.4", From 42f5fe6ad6dfc046b112208757fcdf10ee20afac Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Wed, 29 Apr 2026 21:41:45 +0300 Subject: [PATCH 086/167] fix(security): add cors.originRegexps support and remove wildcard origin --- config.default.json5 | 9 +++++++-- src/index.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index ebfa126..20c1d0d 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -30,7 +30,12 @@ maxFileCount: 10, findFileTimeout: 20000, cors: { - origin: '*', - credentials: true + originRegexps: [ + '^https://.*\\.adamant\\.im$', + '^https://adm\\.im$', + '^https://.*\\.vercel\\.app$', + '^https://.*\\.surge\\.sh$', + '^http://localhost:8080$' + ] } } diff --git a/src/index.ts b/src/index.ts index 1eda332..31defe9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,10 +15,14 @@ const app = express() app.use(pino) +const allowedOrigins: RegExp[] = (config.cors?.originRegexps ?? []).map( + (pattern: string) => new RegExp(pattern) +) + app.use( cors({ - origin: config.cors.origin, - credentials: config.cors.credentials, + origin: allowedOrigins, + credentials: true, methods: ['GET', 'POST'] }) ) From aa5418ce3f8d8f9b83ed99cd346f311d75ae0ebf Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Fri, 1 May 2026 01:41:48 +0300 Subject: [PATCH 087/167] fix(security): add rate limit on upload and pin endpoints --- package-lock.json | 28 ++++++++++++++++++++++++++++ package.json | 1 + src/api/file.ts | 14 ++++++-------- src/api/helia.ts | 9 +++++---- src/middleware/rateLimiter.ts | 29 +++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 src/middleware/rateLimiter.ts diff --git a/package-lock.json b/package-lock.json index 6cd2e81..4a2efb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "cron": "^3.1.7", "datastore-fs": "^9.1.9", "express": "^4.19.2", + "express-rate-limit": "^8.4.1", "go-get-folder-size": "^0.5.5", "helia": "^4.2.5", "json5": "^2.2.3", @@ -8994,6 +8995,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -10445,6 +10464,15 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ip-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", diff --git a/package.json b/package.json index c16a53c..7c9c8c1 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "cron": "^3.1.7", "datastore-fs": "^9.1.9", "express": "^4.19.2", + "express-rate-limit": "^8.4.1", "go-get-folder-size": "^0.5.5", "helia": "^4.2.5", "json5": "^2.2.3", diff --git a/src/api/file.ts b/src/api/file.ts index f34f822..f559950 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -7,15 +7,14 @@ import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js' +import { writeLimiter, readLimiter } from '../middleware/rateLimiter.js' const router = Router() -router.post('/upload', multerStorage.array('files'), async (req, res, next) => { +router.post('/upload', writeLimiter, multerStorage.array('files'), async (req, res, next) => { if (!req.files) { res.statusCode = 400 - return res.send({ - error: 'No file uploaded' - }) + return res.send({ error: 'No file uploaded' }) } if (req.files.length > config.maxFileCount) { @@ -27,7 +26,7 @@ router.post('/upload', multerStorage.array('files'), async (req, res, next) => { try { const files = flatFiles(req.files as UnixFsMulterFile[]) - pino.logger.info(`req.files: : ${JSON.stringify(files.map((item) => item.originalname))}`) + pino.logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) const cids: CID[] = [] for (const file of files) { @@ -58,7 +57,7 @@ router.post('/upload', multerStorage.array('files'), async (req, res, next) => { } }) -router.get('/:cid', async (req, res, next) => { +router.get('/:cid', readLimiter, async (req, res, next) => { try { const cid = CID.parse(req.params.cid) const fileStats = await getFileStats(cid) @@ -84,9 +83,8 @@ router.get('/:cid', async (req, res, next) => { if (error instanceof FileNotFoundError) { res.status(404).send({ error: 'File not found' }) return - } else { - next(error) } + next(error) } }) diff --git a/src/api/helia.ts b/src/api/helia.ts index 885937a..87da582 100644 --- a/src/api/helia.ts +++ b/src/api/helia.ts @@ -3,10 +3,11 @@ import { Pin } from 'helia' import { CID } from 'multiformats/cid' import { helia } from '../helia.js' import { pino } from '../utils/logger.js' +import { writeLimiter, readLimiter } from '../middleware/rateLimiter.js' const router = Router() -router.get('/pins', async (req, res) => { +router.get('/pins', readLimiter, async (req, res) => { const pins: Pin[] = [] for await (const pin of helia.pins.ls()) { @@ -19,7 +20,7 @@ router.get('/pins', async (req, res) => { }) }) -router.post('/pin/:cid', async (req, res, next) => { +router.post('/pin/:cid', writeLimiter, async (req, res, next) => { try { const cid = CID.parse(req.params.cid) for await (const pin of helia.pins.add(cid)) { @@ -31,7 +32,7 @@ router.post('/pin/:cid', async (req, res, next) => { } }) -router.get('/pins/isPinned/:cid', async (req, res) => { +router.get('/pins/isPinned/:cid', readLimiter, async (req, res) => { const cid = CID.parse(req.params.cid) const isPinned = await helia.pins.isPinned(cid) @@ -42,7 +43,7 @@ router.get('/pins/isPinned/:cid', async (req, res) => { }) }) -router.get('/routing/findProviders/:cid', async (req, res, next) => { +router.get('/routing/findProviders/:cid', readLimiter, async (req, res, next) => { try { const cid = CID.parse(req.params.cid) diff --git a/src/middleware/rateLimiter.ts b/src/middleware/rateLimiter.ts new file mode 100644 index 0000000..d07338c --- /dev/null +++ b/src/middleware/rateLimiter.ts @@ -0,0 +1,29 @@ +import { rateLimit } from 'express-rate-limit' + +/** + * Strict limiter for write operations (upload, pin). + * 10 requests per 15 minutes per IP. + * Prevents disk exhaustion via rapid uploads or forced pinning. + */ +export const writeLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + limit: 10, + standardHeaders: 'draft-8', + legacyHeaders: false, + message: { error: 'Too many requests. Please try again later.' }, + // Skip rate limiting for localhost — internal cron jobs and tooling + skip: (req) => req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1' +}) + +/** + * Lenient limiter for read operations (file download, pin status checks). + * 100 requests per minute per IP. + */ +export const readLimiter = rateLimit({ + windowMs: 60 * 1000, + limit: 100, + standardHeaders: 'draft-8', + legacyHeaders: false, + message: { error: 'Too many requests. Please try again later.' }, + skip: (req) => req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1' +}) From b4341244f7a80d5c8bc5d61145a3e864ff52dda8 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Fri, 1 May 2026 02:05:21 +0300 Subject: [PATCH 088/167] fix(security): fix unsanitized filename from multipart request --- src/utils/sanitizeFilename.ts | 34 ++++++++++++++++++++++++++++++ src/utils/unixfs-multer.storage.ts | 2 ++ 2 files changed, 36 insertions(+) create mode 100644 src/utils/sanitizeFilename.ts diff --git a/src/utils/sanitizeFilename.ts b/src/utils/sanitizeFilename.ts new file mode 100644 index 0000000..57c2679 --- /dev/null +++ b/src/utils/sanitizeFilename.ts @@ -0,0 +1,34 @@ +/** + * Sanitizes a filename from multipart Content-Disposition header. + */ +export function sanitizeFilename(raw: string): string { + let name = raw + + // 1. Normalize Unicode — collapse homoglyphs, remove zero-width chars + name = name.normalize('NFKC') + + // 2. Strip null bytes — terminate strings in C libs prematurely + // eslint-disable-next-line no-control-regex + name = name.replace(/\x00/g, '') + + // 3. Strip control characters including \n, \r, \t — prevent log injection + // eslint-disable-next-line no-control-regex + name = name.replace(/[\x00-\x1f\x7f]/g, '') + + // 4. Strip path separators and traversal sequences + name = name.replace(/[/\\]/g, '_') + name = name.replace(/\.\.+/g, '') + + // 5. Enforce max length — 255 bytes is POSIX limit for filename + if (Buffer.byteLength(name, 'utf8') > 255) { + const buf = Buffer.from(name, 'utf8').subarray(0, 255) + name = new TextDecoder('utf-8', { fatal: false }).decode(buf) + } + + // 6. Fallback for empty result after sanitization + if (!name.trim()) { + name = 'unnamed' + } + + return name +} diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index 503d8ea..a24f16a 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -3,6 +3,7 @@ import * as e from 'express' import { UnixFS } from '@helia/unixfs' import { pino } from './logger.js' import { UnixFsMulterFile } from './types.js' +import { sanitizeFilename } from './sanitizeFilename.js' export interface UnixfsStorageOptions { unixfs: UnixFS @@ -18,6 +19,7 @@ export class UnixfsMulterStorage implements StorageEngine { file: Express.Multer.File, callback: (error?: Error, info?: Partial) => void ): void { + file.originalname = sanitizeFilename(file.originalname) const folder = this.options.destination(req, file) const filename = this.options.filename(req, file) this.options.unixfs From dc044ae19c34a44dde26a7eeea4926f21fdc406e Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Fri, 1 May 2026 23:52:19 +0300 Subject: [PATCH 089/167] chore(security): protect node topology endpoint with API key auth + changes in README --- README.md | 84 +++++++++++++++++++++++++++++++++--- config.default.json5 | 3 +- src/api/node.ts | 46 ++++++++++++-------- src/middleware/apiKeyAuth.ts | 56 ++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 src/middleware/apiKeyAuth.ts diff --git a/README.md b/README.md index a43b125..2d2f241 100644 --- a/README.md +++ b/README.md @@ -62,12 +62,63 @@ For example: maxFileCount: 5, // Maximum upload count of files per request findFileTimeout: 20000, // Time limit for searching for a file on the IPFS network cors: { - // Allowed URLs to make request to node. Set using regular expressions - originRegexps: ['.*\.adamant\.im', 'adm.im', '.*\.vercel\.app', '.*\.surge\.sh', 'localhost:8080'] - } + // Allowed URLs to make requests to node. Set using properly anchored regular expressions. + // Dots in domain names must be escaped (\\.), and patterns must be anchored (^ and $) + // to prevent unintended matches (e.g. 'adm.im' without anchors would also match 'admXim'). + originRegexps: [ + '^https://.*\\.adamant\\.im$', + '^https://adm\\.im$', + '^https://.*\\.vercel\\.app$', + '^https://.*\\.surge\\.sh$', + '^http://localhost:8080$' + ] + }, + + // API key for the protected admin endpoint GET /api/node/info. + // This endpoint exposes sensitive node topology data (peerId, IP addresses, disk usage) + // and must not be publicly accessible. + // + // Generate a strong random key before starting the node: + // openssl rand -hex 32 + // + // If this field is missing or empty, GET /api/node/info returns 503. + // GET /api/node/health remains public and does not require this key. + adminApiKey: 'replace-with-output-of-openssl-rand-hex-32' } ``` +## Security + +### Protected endpoints + +`GET /api/node/info` requires authentication via the `x-api-key` HTTP header. This endpoint exposes sensitive node topology data — `peerId`, `multiAddresses`, disk usage statistics — which could be used to enumerate and map ADAMANT IPFS infrastructure. + +Before starting the node, generate a strong API key and add it to your config: + +```bash +openssl rand -hex 32 +``` + +Then set it in `config.json5`: + +```jsonc +adminApiKey: 'your-generated-key-here' +``` + +To call the protected endpoint: + +```bash +curl -H "x-api-key: your-generated-key-here" http://localhost:4000/api/node/info +``` + +If `adminApiKey` is not set in config, the endpoint returns `503 Service not configured`. + +`GET /api/node/health` is **public** and does not require authentication. It is safe to use for uptime monitoring and client availability checks. + +### CORS + +Allowed browser origins are configured via `cors.originRegexps` in your config file. Patterns must be properly anchored and use escaped dots to avoid unintended matches. See `config.default.json5` for examples. + ## How to use ### Upload file @@ -107,12 +158,35 @@ Content-Type: application/octet-stream Hello ipfs-node! ``` -### Get node info +### Get node health + +#### Request +```GET /api/node/health``` +```bash +curl -i --location 'http://localhost:4000/api/node/health' +``` + +#### Response +``` +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 +... + +{ + "timestamp": 1720614998797, + "heliaStatus": "started" +} +``` + +### Get node info (protected) + +Requires `x-api-key` header matching `adminApiKey` from config. #### Request ```GET /api/node/info``` ```bash -curl -i --location 'http://localhost:4000/api/node/info' +curl -i --location 'http://localhost:4000/api/node/info' \ + --header 'x-api-key: your-generated-key-here' ``` #### Response diff --git a/config.default.json5 b/config.default.json5 index 20c1d0d..3f1158d 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -37,5 +37,6 @@ '^https://.*\\.surge\\.sh$', '^http://localhost:8080$' ] - } + }, + adminApiKey: 'change-me-use-openssl-rand-hex-32' } diff --git a/src/api/node.ts b/src/api/node.ts index 0802312..9815870 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -2,29 +2,39 @@ import { Router } from 'express' import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' +import { apiKeyAuth } from '../middleware/apiKeyAuth.js' const router = Router() -router.get('/health', async (req, res) => { - res.send({ - timestamp: Date.now(), - heliaStatus: helia.libp2p.status - }) +router.get('/health', async (req, res, next) => { + try { + res.send({ + timestamp: Date.now(), + heliaStatus: helia.libp2p.status + }) + } catch (err) { + next(err) + } }) -router.get('/info', async (req, res) => { - const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() - - res.send({ - version: packageJson.version, - timestamp: Date.now(), - heliaStatus: helia.libp2p.status, - peerId: helia.libp2p.peerId, - multiAddresses: helia.libp2p.getMultiaddrs(), - blockstoreSizeMb, - datastoreSizeMb, - availableSizeInMb - }) +// Protected — full node topology for operators only +// Requires x-api-key header matching config.adminApiKey +router.get('/info', apiKeyAuth, async (req, res, next) => { + try { + const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() + res.send({ + version: packageJson.version, + timestamp: Date.now(), + heliaStatus: helia.libp2p.status, + peerId: helia.libp2p.peerId, + multiAddresses: helia.libp2p.getMultiaddrs(), + blockstoreSizeMb, + datastoreSizeMb, + availableSizeInMb + }) + } catch (err) { + next(err) + } }) export default router diff --git a/src/middleware/apiKeyAuth.ts b/src/middleware/apiKeyAuth.ts new file mode 100644 index 0000000..6a7a6e3 --- /dev/null +++ b/src/middleware/apiKeyAuth.ts @@ -0,0 +1,56 @@ +import { createHmac, timingSafeEqual } from 'crypto' +import { Request, Response, NextFunction } from 'express' +import { config } from '../config.js' + +/** + * API key authentication middleware. + * Protects endpoints that expose sensitive node topology information + * (peerId, multiAddresses, disk usage). + * + * Key is read from config.adminApiKey. + * Fail-secure: if no key is configured, access is blocked entirely. + * + * Uses crypto.timingSafeEqual to prevent timing-based key enumeration attacks. + * + * Usage in route: router.get('/info', apiKeyAuth, handler) + * Client usage: curl -H "x-api-key: " http://node/api/node/info + */ +export function apiKeyAuth(req: Request, res: Response, next: NextFunction): void { + const configuredKey = config.adminApiKey as string | undefined + + // Fail-secure: block access if key is not configured + // Prevents accidental exposure after config migration + if (!configuredKey) { + res.status(503).send({ error: 'Service not configured' }) + return + } + + const providedKey = req.headers['x-api-key'] + + if (!providedKey || typeof providedKey !== 'string') { + res.status(401).send({ error: 'Unauthorized' }) + return + } + + // Use timing-safe comparison to prevent key enumeration via response timing. + // timingSafeEqual requires equal-length buffers — compare HMAC digests + // (always 32 bytes for SHA-256) so length differences leak no information. + // Use Uint8Array explicitly to satisfy TypeScript strict buffer typing. + const encoder = new TextEncoder() + const keyBytes = encoder.encode(configuredKey) + + const hmac = (value: string): Uint8Array => { + const result = createHmac('sha256', keyBytes).update(encoder.encode(value)).digest() + return new Uint8Array(result) + } + + const providedDigest = hmac(providedKey) + const configuredDigest = hmac(configuredKey) + + if (!timingSafeEqual(providedDigest, configuredDigest)) { + res.status(401).send({ error: 'Unauthorized' }) + return + } + + next() +} From e2a25871d255d0942202896b0a6d956b58753cdc Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sat, 2 May 2026 00:00:04 +0300 Subject: [PATCH 090/167] chore(security): add startup warning and readme notice for tls reverse proxy requirement --- README.md | 2 ++ src/index.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 2d2f241..3bc7c3e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ pm2 start dist/index.js --name="IPFS node" ``` +> **Security notice:** `ipfs-node` does not handle TLS itself. Always deploy it behind a TLS-terminating reverse proxy (nginx, Caddy, etc.). Never expose port 4000 directly to the internet without HTTPS. + ## How to configure Using `config.default.json5` as a template, you can create various configuration files. diff --git a/src/index.ts b/src/index.ts index 31defe9..24cf4c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,4 +44,8 @@ app.use((err: Error, req: Request, res: Response) => { app.listen(PORT, () => { pino.logger.info(`Server is running on http://localhost:${PORT}`) + pino.logger.warn( + 'TLS is not handled at the application level. ' + + 'Ensure this service is deployed behind a TLS-terminating reverse proxy (e.g. nginx, Caddy).' + ) }) From 9e543a85069f1eb5ed304337a55edb10943b5efe Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sat, 2 May 2026 00:04:02 +0300 Subject: [PATCH 091/167] chore(security): disable x-powered-by header to prevent framework disclosure --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 24cf4c8..81c6463 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ diskUsageCron.start() const PORT = config.serverPort const app = express() +app.disable('x-powered-by') app.use(pino) From c1d0430283e870196066eab83c6a487d1658ccca Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sat, 2 May 2026 00:11:52 +0300 Subject: [PATCH 092/167] chore(security): query blockstore filesystem for available storage size --- src/disk-usage.cron.ts | 2 +- src/utils/utils.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index 404f8e6..0f18a5d 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -34,7 +34,7 @@ async function scan() { datastoreSizeMb = datastoreSize / oneMb } - availableSizeInMb = Number((await availableStorageSize()) / BigInt(oneMb)) + availableSizeInMb = Number((await availableStorageSize(blockstorePath)) / BigInt(oneMb)) pino.logger.info(`Check folder size took ${Date.now() - start} ms.`) } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ad9de8d..cdc39d7 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -72,8 +72,8 @@ export function flatFiles( } } -export async function availableStorageSize() { - const statistics = await statfs('/', { bigint: true }) +export async function availableStorageSize(path: string) { + const statistics = await statfs(path, { bigint: true }) return statistics.bsize * statistics.bavail } From 62ffdc761a0bcac6967a5d8354128f701e5ed1bd Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sat, 2 May 2026 00:25:36 +0300 Subject: [PATCH 093/167] chore(security): add pre-push hook to block push on high severity vulnerabilities --- .husky/pre-push | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .husky/pre-push diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..3134abd --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" +npm audit --audit-level=high From 933684511a0760d83de7c15ceb485c0e251aca79 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sat, 2 May 2026 01:07:29 +0300 Subject: [PATCH 094/167] fix(utils): replace console.log with pino in file.ts --- src/utils/file.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/file.ts b/src/utils/file.ts index 64b971e..a0f72c2 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -3,6 +3,7 @@ import { Readable } from 'node:stream' import { clearTimeout } from 'node:timers' import { config } from '../config.js' import { ifs } from '../helia.js' +import { pino } from './logger.js' /** * Return file statistics by CID. @@ -19,7 +20,7 @@ export async function getFileStats(cid: CID) { const stats = await ifs.stat(cid, { signal: abortController.signal }) return stats } catch (err) { - console.log(err) + pino.logger.error(err) clearTimeout(timeout) throw new FileNotFoundError('Cannot find requested CID. Request timed out.') From 6502890b1f551e486946f352b2590e5dd43bed2e Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sat, 2 May 2026 01:08:33 +0300 Subject: [PATCH 095/167] fix(semgrep): tighten multer-unsafe-originalname rule to target dangerous sinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous rule flagged every read of file.originalname, including post-sanitization uses and log calls. This caused excessive false positives that could only be suppressed with nosemgrep — defeating the purpose of the rule. Rewrite to target dangerous sinks only: - path.join / path.resolve / path.basename / path.dirname - fs.* / fsPromises.* - res.send / res.json response bodies (XSS/injection vector) Logging calls and the sanitization assignment itself are no longer flagged. All real dangerous uses remain covered. Also remove file.originalname from _removeFile log (UnixfsMulterStorage) as _removeFile may be called without prior sanitization in _handleFile. --- .semgrep/rules.yml | 26 +++++++++++++++++++------- src/utils/unixfs-multer.storage.ts | 5 +++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.semgrep/rules.yml b/.semgrep/rules.yml index de4b97e..2168b5c 100644 --- a/.semgrep/rules.yml +++ b/.semgrep/rules.yml @@ -49,19 +49,31 @@ rules: owasp: "A09:2021" cwe: "CWE-209" + # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # Rule: multer-unsafe-originalname # - # Flags every use of file.originalname from a Multer-parsed request. - # This value is taken verbatim from the Content-Disposition header and - # is fully attacker-controlled. Common attacks include path traversal - # (../../etc/passwd), null-byte injection, and Unicode homoglyph abuse. + # Flags uses of file.originalname in DANGEROUS contexts only: + # filesystem paths (path.join/resolve/dirname/basename), direct fs calls, + # and HTTP response bodies. Logging and sanitization calls are excluded. # - # Safe pattern: sanitize before any use — strip path separators, - # enforce a maximum length, normalize Unicode (NFKC). + # Why dangerous contexts only: Semgrep OSS cannot do cross-function + # data-flow analysis. A catch-all pattern on every .originalname read + # produces too many false positives (post-sanitization uses, log calls) + # and gets suppressed with nosemgrep, which defeats the purpose. + # Targeting dangerous sinks gives actionable findings with low FP rate. # ----------------------------------------------------------------------- - id: multer-unsafe-originalname - pattern: $FILE.originalname + patterns: + - pattern-either: + - pattern: path.join(..., $FILE.originalname, ...) + - pattern: path.resolve(..., $FILE.originalname, ...) + - pattern: path.basename($FILE.originalname, ...) + - pattern: path.dirname($FILE.originalname, ...) + - pattern: fs.$METHOD($FILE.originalname, ...) + - pattern: fsPromises.$METHOD($FILE.originalname, ...) + - pattern: "res.send({..., $KEY: $FILE.originalname, ...})" + - pattern: "res.json({..., $KEY: $FILE.originalname, ...})" message: > file.originalname is attacker-controlled (from the Content-Disposition header). Sanitize before use: strip path separators, enforce max length, diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index a24f16a..b9be662 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -19,7 +19,8 @@ export class UnixfsMulterStorage implements StorageEngine { file: Express.Multer.File, callback: (error?: Error, info?: Partial) => void ): void { - file.originalname = sanitizeFilename(file.originalname) + const safeFilename = sanitizeFilename(file.originalname) + file.originalname = safeFilename const folder = this.options.destination(req, file) const filename = this.options.filename(req, file) this.options.unixfs @@ -40,7 +41,7 @@ export class UnixfsMulterStorage implements StorageEngine { file: Express.Multer.File, callback: (error: Error | null) => void ): void { - pino.logger.info(`Need remove file ${file.originalname}`) + pino.logger.info('Remove file requested') callback(null) } } From 81a07b8fb1402bf42a8d23cfb21a0b8da5ba9cc9 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 19 Aug 2026 14:37:05 +0300 Subject: [PATCH 096/167] chore: update .gitignore and add markdownlint configuration --- .gitignore | 11 +++++++++++ .markdownlint.jsonc | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .markdownlint.jsonc diff --git a/.gitignore b/.gitignore index b137b65..c4d05f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,15 @@ node_modules .idea +.vscode +.DS_Store + +logs dist/ config.json5 + +.playwright-cli/ + +# ai +.ai-ignored/ +.ai-tasks/ +.claude/ diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..3b8e3f9 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,10 @@ +{ + "MD013": false, + "MD029": false, + "MD024": { "siblings_only": true }, + "MD041": false, + "MD060": false, + "MD040": false, + "MD036": false, + "MD034": false +} From 66e93665ab0afa3cdbd88c857cc13dd5a7cb7df2 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 19 Aug 2026 14:42:01 +0300 Subject: [PATCH 097/167] docs: add AI agent instructions --- AGENTS.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f1b4b31 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,115 @@ +# IPFS Node: AI Agent Operating Manual + +This document defines how AI agents must work in this repository. It intentionally contains only general guidance; add project-specific technical instructions only after verifying them against the current codebase. + +## Priorities + +Optimize agent work for: + +1. Reliability and correctness +2. Security and privacy +3. Simplicity and maintainability +4. Clear collaboration with contributors + +Prefer the smallest safe change that fully addresses the task. Avoid unrelated rewrites and speculative abstractions. + +## Language Policy + +- Developers may communicate with AI in any language +- All repository artifacts must be in English +- Write code, comments, documentation, commit messages, issue text, and PR text in English + +## Writing and Documentation + +- Use concise, operational wording +- Keep one blank line before and after Markdown lists +- In lists, omit the trailing period when an item contains one sentence; if an item contains multiple sentences, punctuate every sentence +- Use fenced code blocks with matching fences and a language tag when applicable +- Write JSDoc for public modules, exported functions, reusable helpers, and functions materially changed by the task +- In JSDoc, explain purpose, parameter semantics, and non-obvious return values +- Add short comments for non-obvious logic, constraints, security decisions, compatibility behavior, or workarounds; do not restate the code +- Keep documentation aligned with current behavior whenever code changes + +## Sources of Truth + +Use current repository code, configuration, tests, and `README.md` as the primary sources for implementation behavior. Also follow: + +- [ADAMANT organization governance](https://github.com/Adamant-im/.github) +- [ADAMANT issue title guidance](https://github.com/orgs/Adamant-im/discussions/5) +- [ADAMANT label guidance](https://github.com/orgs/Adamant-im/discussions/1) +- [ADAMANT documentation](https://docs.adamant.im) + +When sources disagree: + +1. Treat current code and passing tests as implementation truth +2. Document the mismatch instead of silently choosing or changing behavior +3. Propose synchronized documentation or follow-up work when needed + +Do not infer project architecture, runtime behavior, or operational procedures from stale documentation. Verify technical details before adding them to this file. + +## Change Discipline + +1. Read the relevant files end-to-end before editing +2. Identify the requested scope and behavior that must remain unchanged +3. Make a focused change with an explicit rationale +4. Add or update tests near changed behavior when applicable +5. Run targeted checks first, then broader checks in proportion to risk +6. Review the final diff for unintended changes +7. Report assumptions, risks, skipped checks, and deferred work + +Preserve backward compatibility unless the task explicitly approves a breaking change. Improve legacy code locally, but do not turn a focused task into a broad refactor. + +## Security, Privacy, and Reliability + +- Never expose secrets, passphrases, private keys, tokens, credentials, or sensitive user data in code, logs, tests, fixtures, issues, or PRs +- Keep validation strict for untrusted input and external data +- Do not introduce dynamic code execution, unsafe deserialization, unvalidated shell execution, or insecure fallbacks +- Minimize new dependencies and justify additions, especially for security-sensitive or network-facing code +- Fail safely on malformed data, timeouts, and partial external failures +- Preserve existing privacy, decentralization, and self-hosting properties +- Stop and request maintainer guidance when a change has unclear security, privacy, compatibility, or data-integrity consequences + +## Validation Policy + +- Use repository-defined scripts and configuration as the source of truth for checks +- Run the narrowest relevant validation first and broaden it when shared or high-risk behavior changes +- For documentation-only changes, run Markdown lint and repository diff checks +- Never claim a check passed unless it was actually executed +- Report exact commands, results, and any checks that were not run + +## Issue, Label, and PR Conventions + +Follow organization-wide governance, templates, title conventions, and label casing. + +### Issues + +- Search existing issues before creating a new one +- Use the appropriate organization issue template +- Use one concise title prefix, or two only when necessary: `[Bug]`, `[Feat]`, `[Enhancement]`, `[Refactor]`, `[Docs]`, `[Test]`, `[Chore]`, `[Task]`, or `[Composite]` +- Apply a minimal informative label set: one type or status label, relevant domain labels, and an optional priority label +- Link related issues and PRs explicitly + +### Pull requests and commits + +- Use `Type: Short summary` for PR titles, such as `Docs: Add AGENTS.md`; do not use issue-style square brackets +- Keep the PR title type aligned with the issue intent: `Docs:`, `Fix:`, `Feat:`, `Refactor:`, `Test:`, or `Chore:` +- Use Conventional Commits style for commit messages +- Structure the PR body with `Description`, `Related issue`, `How to test`, and `Checklist` sections +- Reference the issue with a closing keyword when appropriate, such as `Closes #123` +- Include validation evidence and note relevant risks or intentionally unchanged behavior +- Add a link to the PR in the related issue + +### Command-line content + +For multi-line CLI input, use a dated temporary Markdown file under `.ai-ignored/` and a file-based flag such as `--body-file`. Do not put multi-line issue, PR, or commit text directly in a shell argument. + +## Definition of Done + +A change is complete only when: + +- The requested behavior or documentation is implemented without unrelated scope expansion +- Security, privacy, reliability, and compatibility are preserved +- Relevant validation has passed or blockers are reported clearly +- Documentation and configuration are updated when behavior changes +- Repository artifacts are in English +- The final report states what changed, what was verified, and what remains From ad786d5e6c9140e269c1c954af5eaa8b49baeda4 Mon Sep 17 00:00:00 2001 From: adamantmm Date: Fri, 21 Aug 2026 20:46:26 +0300 Subject: [PATCH 098/167] fix: complete security audit hardening --- .github/workflows/security.yml | 14 +- .gitignore | 1 + .husky/pre-push | 2 +- README.md | 268 ++++++------ config.default.json5 | 27 +- package-lock.json | 773 ++++++++++++++++++--------------- package.json | 18 +- scripts/audit-production.mjs | 80 ++++ src/api/file.ts | 31 +- src/api/helia.ts | 56 ++- src/api/node.ts | 5 +- src/config.ts | 2 + src/helia.ts | 4 +- src/index.ts | 31 +- src/middleware/apiKeyAuth.ts | 56 --- src/middleware/errorHandler.ts | 22 + src/middleware/rateLimiter.ts | 46 +- src/multer.ts | 7 +- src/security/accessPolicy.ts | 38 ++ src/security/apiKey.ts | 41 ++ src/security/config.ts | 70 +++ src/security/cors.ts | 134 ++++++ src/security/errors.ts | 42 ++ src/security/rateLimit.ts | 38 ++ src/security/trustProxy.ts | 45 ++ src/security/uploadLimits.ts | 24 + src/utils/sanitizeFilename.ts | 35 +- test/sanitizeFilename.test.ts | 19 + test/security.test.ts | 299 +++++++++++++ tsconfig.test.json | 8 + 30 files changed, 1566 insertions(+), 670 deletions(-) create mode 100644 scripts/audit-production.mjs delete mode 100644 src/middleware/apiKeyAuth.ts create mode 100644 src/middleware/errorHandler.ts create mode 100644 src/security/accessPolicy.ts create mode 100644 src/security/apiKey.ts create mode 100644 src/security/config.ts create mode 100644 src/security/cors.ts create mode 100644 src/security/errors.ts create mode 100644 src/security/rateLimit.ts create mode 100644 src/security/trustProxy.ts create mode 100644 src/security/uploadLimits.ts create mode 100644 test/sanitizeFilename.test.ts create mode 100644 test/security.test.ts create mode 100644 tsconfig.test.json diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 0dc4395..0e132b2 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -1,5 +1,8 @@ name: Security Audit +permissions: + contents: read + on: push: branches: [master, dev] @@ -18,6 +21,9 @@ jobs: runs-on: ubuntu-latest container: image: semgrep/semgrep + permissions: + contents: read + security-events: write steps: - uses: actions/checkout@v4 @@ -58,14 +64,14 @@ jobs: cache: 'npm' - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - - name: Run npm audit (fail on critical) - run: npm audit --audit-level=critical + - name: Run production dependency policy + run: npm run security:audit - name: Generate full audit report (artifact) if: always() - run: npm audit --json > npm-audit-report.json || true + run: npm audit --omit=dev --omit=peer --json > npm-audit-report.json || true - name: Upload audit report uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index e08d3f5..e0f4670 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules logs dist/ +dist-test/ config.json5 semgrep-results.sarif npm-audit-report.json diff --git a/.husky/pre-push b/.husky/pre-push index 3134abd..e53a43f 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,3 +1,3 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npm audit --audit-level=high +npm run security:audit diff --git a/README.md b/README.md index 3bc7c3e..ad3464f 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,189 @@ -# ipfs-node -ADAMANT ipfs-node. Designed for downloading and exchanging files in the ADAMANT Messenger. +# ADAMANT IPFS node -Unlike the standard libraries (helia or kubo), this ipfs-node is equipped with a web server for performing REST requests for downloading and receiving files. +This service embeds a Helia/libp2p node and exposes the file-transfer API used by ADAMANT Messenger. It stores uploaded files in an on-disk IPFS blockstore, pins them, retrieves content by CID, and maintains connections to configured peers. -The plans also include the implementation of the Garbage Collector function, which will save disk space by removing unsent files. +The application is not a Kubo wrapper. It is a Node.js service with an Express REST API around an in-process Helia node. + +## Requirements + +- Node.js 20 or later +- A TLS-terminating reverse proxy for every public deployment +- A unique administrative API key for operator endpoints + +## Install and run -## How to start -- You will need Node.js v20.11.1 (You can install via nvm: https://github.com/nvm-sh/nvm): -```bash -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash -nvm install 20.11.1 -``` -- Cloning and building node: ```bash git clone https://github.com/Adamant-im/ipfs-node.git -cd ipfs-node -npm i +cd ipfs-node +npm ci npm run build +node dist/index.js ``` -- Running node with [pm2](https://github.com/Unitech/pm2): + +The process can also be managed with PM2: + ```bash -npm i -g pm2 +npm install --global pm2 pm2 start dist/index.js --name="IPFS node" ``` +## Configuration -> **Security notice:** `ipfs-node` does not handle TLS itself. Always deploy it behind a TLS-terminating reverse proxy (nginx, Caddy, etc.). Never expose port 4000 directly to the internet without HTTPS. - -## How to configure -Using `config.default.json5` as a template, you can create various configuration files. - -For example: -`node dist/index.js test1` — this command will launch the server with the configuration from the file config.test1.json5 +Copy `config.default.json5` to `config.json5` and replace all deployment-specific values. The security-relevant fields are shown below: ```jsonc { - // List of IPFS ADAMANT nodes interacting between each other - nodes: [ - { - name: "ipfs1", - multiAddr: "/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq" - }, - ... - ], - storeFolder: '.adm-ipfs', // File storage directory (the directory is set from the user’s home directory) - logLevel: 'debug', // Logging level: fatal, error, warn, info, debug, trace - peerDiscovery: { - // IPFS network nodes that will be used to search for new nodes - // Details: https://github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap - bootstrap: [ - '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', - ... - ], - // Addresses that helia will listen to - listen: [ - '/ip4/0.0.0.0/tcp/4001', - ] + "serverPort": 4000, + "uploadLimitSizeBytes": 268435456, + "maxFileCount": 10, + "cors": { + "allowedOrigins": ["https://adm.im", "https://*.adamant.im", "http://localhost:8080"] }, - serverPort: 4000, // API server deployment port - autoPeeringPeriod: '*/10 * * * * *', - diskUsageScanPeriod: '*/30 * * * * *', // Disk space scanning period. Set in cron format: '* * * * * *' - uploadLimitSizeBytes: 268435456, // Maximum upload file size (in bytes) - maxFileCount: 5, // Maximum upload count of files per request - findFileTimeout: 20000, // Time limit for searching for a file on the IPFS network - cors: { - // Allowed URLs to make requests to node. Set using properly anchored regular expressions. - // Dots in domain names must be escaped (\\.), and patterns must be anchored (^ and $) - // to prevent unintended matches (e.g. 'adm.im' without anchors would also match 'admXim'). - originRegexps: [ - '^https://.*\\.adamant\\.im$', - '^https://adm\\.im$', - '^https://.*\\.vercel\\.app$', - '^https://.*\\.surge\\.sh$', - '^http://localhost:8080$' - ] + "trustProxy": false, + "rateLimits": { + "upload": { "windowMs": 900000, "limit": 10 }, + "pin": { "windowMs": 900000, "limit": 10 }, + "read": { "windowMs": 60000, "limit": 100 } }, - - // API key for the protected admin endpoint GET /api/node/info. - // This endpoint exposes sensitive node topology data (peerId, IP addresses, disk usage) - // and must not be publicly accessible. - // - // Generate a strong random key before starting the node: - // openssl rand -hex 32 - // - // If this field is missing or empty, GET /api/node/info returns 503. - // GET /api/node/health remains public and does not require this key. - adminApiKey: 'replace-with-output-of-openssl-rand-hex-32' + "adminApiKey": "", + "enableDebugApi": false } ``` -## Security - -### Protected endpoints - -`GET /api/node/info` requires authentication via the `x-api-key` HTTP header. This endpoint exposes sensitive node topology data — `peerId`, `multiAddresses`, disk usage statistics — which could be used to enumerate and map ADAMANT IPFS infrastructure. - -Before starting the node, generate a strong API key and add it to your config: +Generate the administrative secret before enabling operator endpoints: ```bash openssl rand -hex 32 ``` -Then set it in `config.json5`: +Set the generated value as `adminApiKey`. A missing or empty key fails closed: administrative routes return `503 Service not configured`. Known placeholder values and secrets shorter than 32 characters prevent startup. -```jsonc -adminApiKey: 'your-generated-key-here' -``` +### Configuration migration -To call the protected endpoint: +- Replace `cors.origin` or `cors.originRegexps` with `cors.allowedOrigins` +- Use exact origins such as `https://adm.im` or left-most subdomain wildcards such as `https://*.adamant.im` +- Set `adminApiKey` before using any administrative API +- Leave `trustProxy` as `false` for direct connections; configure exact proxy addresses, CIDR ranges, or a verified hop count behind a proxy +- Set `enableDebugApi: true` only when the authenticated debug route is operationally required +- Tune the endpoint-specific `rateLimits` for the deployment perimeter -```bash -curl -H "x-api-key: your-generated-key-here" http://localhost:4000/api/node/info -``` +Invalid CORS, proxy, API-key, or rate-limit configuration stops the process instead of silently weakening the boundary. + +## HTTP access policy -If `adminApiKey` is not set in config, the endpoint returns `503 Service not configured`. +| Class | Routes | Policy | +| -------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------- | +| Public | `GET /`, `GET /api/node/health` | No authentication | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | +| Administrative | `GET /api/node/info`, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | +| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | +| Authenticated user | None | The service has no end-user identity or session layer | -`GET /api/node/health` is **public** and does not require authentication. It is safe to use for uptime monitoring and client availability checks. +Administrative coverage includes pin operations, provider queries, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. + +### Public upload decision + +Upload remains public for compatibility with direct Messenger clients. The service has no safe channel for distributing an upload secret and does not implement a short-lived signing protocol. Public upload is constrained by per-client request limits, per-file size, per-request file count, filename sanitization, and the deployment proxy. + +This is an explicit compatibility decision, not an authorization guarantee. A deployment that requires signed upload authorization must enforce it at a trusted gateway until a client-compatible signing protocol is designed. Files of any content type are accepted, but downloads are served as `application/octet-stream` attachments with content sniffing disabled. ### CORS -Allowed browser origins are configured via `cors.originRegexps` in your config file. Patterns must be properly anchored and use escaped dots to avoid unintended matches. See `config.default.json5` for examples. +`cors.allowedOrigins` accepts canonical HTTP(S) origins only. It does not accept paths, credentials, query strings, fragments, or unrestricted `*`. Wildcards are limited to a left-most subdomain. For example, `https://*.adamant.im` matches `https://chat.adamant.im` but not `https://adamant.im` or `https://adamant.im.example.org`. -## How to use +Requests without an `Origin` header, such as server-to-server calls and `curl`, are not blocked by CORS. Unauthorized administrative requests are still rejected by API-key middleware. -### Upload file -Request body type: `form-data` +### Trusted proxy and rate limits -It should contain a "files" field, which can accept an array of files up to 5 pieces at a time. +Express uses the socket address as the client IP while `trustProxy` is `false`. Behind a reverse proxy, set `trustProxy` to the exact proxy address or CIDR when possible: -#### Request -```POST /api/file/upload``` -```bash -curl -i --location 'http://localhost:4000/api/file/upload' --form 'files=@"file.txt"' +```jsonc +trustProxy: ['127.0.0.1/8', '::1/128'] ``` -#### Response -``` -HTTP/1.1 200 OK -Content-Type: application/json; charset=utf-8 -... +A numeric hop count is supported only for a fixed topology in which every path to the application has exactly that number of trusted hops. The blanket value `true` is rejected because a client could spoof `X-Forwarded-For` when the last proxy does not overwrite it. -{"filesNames":["file.txt"],"cids":["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"]} -``` +Application limiters use in-memory counters per process. The TLS proxy must also enforce request rates, connection limits, header limits, and a body-size limit no larger than `uploadLimitSizeBytes` for multi-process or distributed deployments. + +### TLS boundary + +The Node.js process serves HTTP and does not terminate TLS. Bind it to a private interface or firewall it so clients can reach it only through a correctly configured HTTPS reverse proxy. The proxy must replace untrusted forwarding headers and forward requests to the configured `serverPort`. -### Get file +Never expose the application port directly to the internet. + +### Dependency audit policy + +Run the production policy and static checks with: -#### Request -```GET /api/file/:cid``` ```bash -curl -i --location 'http://localhost:4000/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm' +npm run security:audit +npm run security:semgrep ``` -#### Response +The audit fails on every unaccepted high or critical production advisory. One exact advisory, `GHSA-32mq-hpph-xfvr`, is temporarily accepted until [#21](https://github.com/Adamant-im/ipfs-node/issues/21) upgrades Helia: Helia 4 installs the affected Kademlia DHT package, but `src/helia.ts` replaces the default service map and does not instantiate the vulnerable DHT service. The audit script validates the exact package and advisory relationship so unrelated future findings still fail. + +Use `npm run security:audit:raw` to inspect the unfiltered npm result. + +## API usage + +### Upload files + +Send one or more `files` parts as multipart form data: + +```bash +curl --fail-with-body \ + --form 'files=@file.txt' \ + https://ipfs.example.org/api/file/upload ``` -HTTP/1.1 200 OK -Content-Type: application/octet-stream -... -Hello ipfs-node! +Example response: + +```json +{ + "filesNames": ["file.txt"], + "cids": ["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"] +} ``` -### Get node health +### Download a file -#### Request -```GET /api/node/health``` ```bash -curl -i --location 'http://localhost:4000/api/node/health' +curl --fail-with-body \ + --output file.bin \ + https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm ``` -#### Response +### Check public health + +```bash +curl --fail-with-body https://ipfs.example.org/api/node/health ``` -HTTP/1.1 200 OK -Content-Type: application/json; charset=utf-8 -... +Example response: + +```json { "timestamp": 1720614998797, "heliaStatus": "started" } ``` -### Get node info (protected) - -Requires `x-api-key` header matching `adminApiKey` from config. +### Get administrative node information -#### Request -```GET /api/node/info``` ```bash -curl -i --location 'http://localhost:4000/api/node/info' \ - --header 'x-api-key: your-generated-key-here' +curl --fail-with-body \ + --header 'x-api-key: your-generated-key' \ + https://ipfs.example.org/api/node/info ``` -#### Response -``` -HTTP/1.1 200 OK -Content-Type: application/json; charset=utf-8 -... +## Validation -{ - "version": "0.0.1", - "timestamp": 1720614998797, - "heliaStatus": "started", - "peerId": "12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", - "multiAddresses": [ - "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9", - "/ip4/62.72.43.99/tcp/4001/p2p/12D3KooWJSiMDfyDLK3EMe2567sSM1VKQVnUn2getimGqVTWqKX9" - ], - "blockstoreSizeMb": 0.0009489059448242188, - "datastoreSizeMb": 0.006007194519042969, - "availableSizeInMb": 2257731 -} +```bash +npm ci --ignore-scripts +npm run build +npm test +npm run lint +npm run security:audit +npm run security:semgrep +git diff --check ``` diff --git a/config.default.json5 b/config.default.json5 index 3f1158d..bbb85c4 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -30,13 +30,26 @@ maxFileCount: 10, findFileTimeout: 20000, cors: { - originRegexps: [ - '^https://.*\\.adamant\\.im$', - '^https://adm\\.im$', - '^https://.*\\.vercel\\.app$', - '^https://.*\\.surge\\.sh$', - '^http://localhost:8080$' + // Exact origins and left-most subdomain wildcards are supported. + allowedOrigins: [ + 'https://*.adamant.im', + 'https://adm.im', + 'https://*.vercel.app', + 'https://*.surge.sh', + 'http://localhost:8080' ] }, - adminApiKey: 'change-me-use-openssl-rand-hex-32' + // Keep false when clients connect directly. Prefer exact proxy IP/CIDR values + // when a reverse proxy forwards requests to this process. + trustProxy: false, + rateLimits: { + upload: { windowMs: 900000, limit: 10 }, + pin: { windowMs: 900000, limit: 10 }, + read: { windowMs: 60000, limit: 100 } + }, + // Generate with: openssl rand -hex 32 + // Admin routes return 503 while this value is empty. + adminApiKey: '', + // The debug API is absent unless explicitly enabled; it still requires the admin key. + enableDebugApi: false } diff --git a/package-lock.json b/package-lock.json index 4a2efb4..60cc9e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,12 @@ "cors": "^2.8.5", "cron": "^3.1.7", "datastore-fs": "^9.1.9", - "express": "^4.19.2", - "express-rate-limit": "^8.4.1", + "express": "^4.22.2", + "express-rate-limit": "^8.6.2", "go-get-folder-size": "^0.5.5", "helia": "^4.2.5", "json5": "^2.2.3", - "multer": "^1.4.5-lts.1", + "multer": "^2.2.0", "multiformats": "^13.2.2", "pino": "^9.3.2", "pino-http": "^10.2.0" @@ -33,7 +33,7 @@ "@commitlint/config-conventional": "^19.2.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/multer": "^1.4.11", + "@types/multer": "^2.2.0", "@types/node": "^20.14.8", "@typescript-eslint/eslint-plugin": "^8.59.1", "eslint": "^8.57.0", @@ -76,31 +76,18 @@ "xml2js": "^0.6.2" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@assemblyscript/loader": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -109,30 +96,32 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", - "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", "peer": true, "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -157,15 +146,17 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.25.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -197,14 +188,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -305,6 +297,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", @@ -319,28 +321,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -362,9 +365,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", "peer": true, "engines": { "node": ">=6.9.0" @@ -431,9 +435,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "peer": true, "engines": { @@ -441,18 +445,19 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", "peer": true, "engines": { "node": ">=6.9.0" @@ -473,27 +478,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -1459,15 +1464,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", - "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" }, "engines": { "node": ">=6.9.0" @@ -2160,56 +2166,48 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", - "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.2", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2621,13 +2619,14 @@ } }, "node_modules/@helia/bitswap": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.3.tgz", - "integrity": "sha512-nCszNky/kOxaj1CZrZbzzFz6Fn7uNWigudn4KaLV+ZuFx6xyB8+t6aCmjKQ59me0WA3mwkmHkPtUOi9Y75zLSw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.4.tgz", + "integrity": "sha512-BcmIc2FZ0NEjSYrwJxzFO5OapcOCBfcHmaBKaDoPNVh1egLLUkws2a7C3hPH5ZR7PXGQPXonH1YFz1CKYypzgA==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@helia/interface": "^4.3.1", "@helia/utils": "^0.3.3", - "@libp2p/interface": "^1.1.2", + "@libp2p/interface": "^1.1.4", "@libp2p/logger": "^4.0.5", "@libp2p/peer-collections": "^5.1.6", "@libp2p/utils": "^5.2.3", @@ -2652,11 +2651,12 @@ } }, "node_modules/@helia/block-brokers": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.3.tgz", - "integrity": "sha512-xSnmWv1jgeBJURcdF/KFOHEa1CQygk3jm1qxYPYqnMWxRXhsFG5Qi13EA+QfslCXyywfxbr4yJWleUUV2Mw4oQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.4.tgz", + "integrity": "sha512-ymtV2cNrVeRoXsAOTpx558nDzfyOu87bLAewF98OVZ/NB28EUTy0c592GHLX3QcMaW/XfTzSGCi+te3fvVIuQg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@helia/bitswap": "^1.1.3", + "@helia/bitswap": "^1.1.4", "@helia/interface": "^4.3.1", "@helia/utils": "^0.3.3", "@libp2p/interface": "^1.1.4", @@ -2751,6 +2751,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.3.tgz", "integrity": "sha512-iCS8dJRULgltfx7aLLoqfjWUpqBWrTyouCToB9RgHpNs4lHcIsZn/VSbHyjC4NUF9nIWsxhH/3UAdvWl23HEgw==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@helia/interface": "^4.3.1", "@ipld/dag-cbor": "^9.2.0", @@ -2984,17 +2985,25 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -3006,15 +3015,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", @@ -3032,9 +3032,10 @@ "peer": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3102,11 +3103,12 @@ } }, "node_modules/@libp2p/crypto": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.8.tgz", - "integrity": "sha512-j8d1gOS6ypt6CSYgBTJKSTGsht3SFPkhfOkGND6sUCl1w9pRslDlvFnU9UrNHLMiaNmVHaeS9B2IKzKMbLX92Q==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.9.tgz", + "integrity": "sha512-8Cf2VKh0uC/rQLvTLSloIOMqUvf4jsSTHXgjWQRf47lDNJlNNI0wSv2S6gakT72GZsRV/jCjYwKPqRlsa5S0iA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", + "@libp2p/interface": "^1.7.0", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "asn1js": "^3.0.5", @@ -3153,9 +3155,10 @@ } }, "node_modules/@libp2p/interface": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.6.3.tgz", - "integrity": "sha512-Tm8W5Q2FsjcSdeA5BvP/GTUq/lp3SjeW6GPmWbbIasBJdv67UGHahu8YDFTME90IxTijnikkfGNkOPsd/4UuvA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.7.0.tgz", + "integrity": "sha512-/zFyaIaIGW0aihhsH7/93vQdpWInUzFocxF11RO/029Y6h0SVjs24HHbils+DqaFDTqN+L7oNlBx2rM2MnmTjA==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@multiformats/multiaddr": "^12.2.3", "it-pushable": "^3.2.3", @@ -3166,29 +3169,31 @@ } }, "node_modules/@libp2p/interface-internal": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.3.3.tgz", - "integrity": "sha512-xng1cBGDhSZmCu0kUEv3363oSgpoPmvpDr11wumhKNEgGOwBVbEr0v7serYcrlORxFmyUBW8y98CkPRJxskPLA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.3.4.tgz", + "integrity": "sha512-8x/0sdeH8T16yZ9t/Cfja0ms6Ho9fF3riX56WhQrNxMU6C1sIgAFmzUNzHLxxOR+rkKyL9cyXIyB+RcBf4gzjA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-collections": "^5.2.8", + "@libp2p/interface": "^1.7.0", + "@libp2p/peer-collections": "^5.2.9", "@multiformats/multiaddr": "^12.2.3", "progress-events": "^1.0.0", "uint8arraylist": "^2.4.8" } }, "node_modules/@libp2p/kad-dht": { - "version": "12.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.1.4.tgz", - "integrity": "sha512-TWg3WB/Ghqz2vVOBxZz3PD0gDw1j24GlcQiJw5AieXMY1va8ZxkYyJtpHluhPpjLnlI4zxNLbE30A05Tp6XD4w==", - "dependencies": { - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/peer-collections": "^5.2.8", - "@libp2p/peer-id": "^4.2.3", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.1.5.tgz", + "integrity": "sha512-n/Tdd3zVa2p1S4L6wRIBUAo3ctCbiEkp1aewpOUthL6rOwBh6U/RQ+dssiZNEDHCsF1ta/mZkREuXqxOPpplFQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/crypto": "^4.1.9", + "@libp2p/interface": "^1.7.0", + "@libp2p/interface-internal": "^1.3.4", + "@libp2p/peer-collections": "^5.2.9", + "@libp2p/peer-id": "^4.2.4", "@libp2p/record": "^4.0.4", - "@libp2p/utils": "^5.4.8", + "@libp2p/utils": "^5.4.9", "@multiformats/multiaddr": "^12.2.3", "any-signal": "^4.1.1", "hashlru": "^2.3.0", @@ -3230,11 +3235,12 @@ } }, "node_modules/@libp2p/logger": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.19.tgz", - "integrity": "sha512-VKpIMbjzs60AaTezh55iEDPJ0W2icbkJkBXSlAMycCT4C+RYxOTRgevasw3mDB6+Lj9etM0nfa4vutoG4fsYCw==", + "version": "4.0.20", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.20.tgz", + "integrity": "sha512-TTh2dhHsOTAlMPxSa9ncFPHa/0jTt+0AQxwHdlxg/OGLAgc9VRhnrhHUbJZp07Crcw4T/MOfS4KhjlxgqYgJRw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", + "@libp2p/interface": "^1.7.0", "@multiformats/multiaddr": "^12.2.3", "interface-datastore": "^8.2.11", "multiformats": "^13.1.0", @@ -3288,21 +3294,23 @@ } }, "node_modules/@libp2p/peer-collections": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.8.tgz", - "integrity": "sha512-dIakIFms3GgFYg4lVgl9afAm+OAxGTofz4KdFnVrlNW1z6VFhTbnRtvTweR6woSr2es1+Fu6WutmZ+62Fn0iOQ==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.9.tgz", + "integrity": "sha512-8gBmzQlCWjjb+FSQBKK33T25Y5Df/8FWCXFtJDsprVxVUzDOQoibQJ5Tb4Y+mb96HUhNzoaRWVEamB78MMB3DA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/utils": "^5.4.8" + "@libp2p/interface": "^1.7.0", + "@libp2p/peer-id": "^4.2.4", + "@libp2p/utils": "^5.4.9" } }, "node_modules/@libp2p/peer-id": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.2.3.tgz", - "integrity": "sha512-hRqPzcYOz/5q6QvHYdmPMGeFZCjC/9qxQ/+jstSDMnY1DuKEXCre2+tCpG9OeRAFyPBbs5isfaqbY3zNZV2pqA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.2.4.tgz", + "integrity": "sha512-mvvsVxt4HkF14BrTNKbqr14VObW+KBJBWu1Oe6BFCoDttGMQLaI+PdduE1r6Tquntv5IONBqoITgD7ow5dQ+vQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", + "@libp2p/interface": "^1.7.0", "multiformats": "^13.1.0", "uint8arrays": "^5.1.0" } @@ -3407,14 +3415,15 @@ } }, "node_modules/@libp2p/utils": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.8.tgz", - "integrity": "sha512-3ysAjrheDvcrUd8RWOCwa6Hm+5FMsYOlPq9IPllfD0jOyHO2RHc/huvAmjBg/PWrTdUyx1nWQf9wmdQFgTDUDQ==", + "version": "5.4.9", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.9.tgz", + "integrity": "sha512-0fRdX98WqhTmXU2WEVLegLFxs/kKTtUHanHk5Lzs4oGsIzlPHR7zE6lj/U1WfsFA+Xo1eYQpNLiXEL29hG+Nyw==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/logger": "^4.0.19", + "@libp2p/crypto": "^4.1.9", + "@libp2p/interface": "^1.7.0", + "@libp2p/logger": "^4.0.20", "@multiformats/multiaddr": "^12.2.3", "@multiformats/multiaddr-matcher": "^1.2.1", "@sindresorhus/fnv1a": "^3.1.0", @@ -3917,9 +3926,9 @@ } }, "node_modules/@react-native-community/cli-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "license": "MIT", "peer": true, "dependencies": { @@ -4555,9 +4564,10 @@ } }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", + "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "license": "MIT", "peer": true, "dependencies": { "async-limiter": "~1.0.0" @@ -5362,9 +5372,10 @@ } }, "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", + "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "license": "MIT", "peer": true, "dependencies": { "async-limiter": "~1.0.0" @@ -5668,10 +5679,11 @@ "dev": true }, "node_modules/@types/multer": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", - "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -5937,16 +5949,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { @@ -6108,9 +6120,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "peer": true, @@ -6118,7 +6130,7 @@ "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { @@ -6450,16 +6462,16 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { @@ -6928,6 +6940,19 @@ } ] }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.17.tgz", + "integrity": "sha512-KAUDn1OSS0fmPlGO+NOUMRcOQ/b/shUBH3OgkG73mPgdf+JD/BQ6fHboGxNOxnUmlwcq+lLq3dTkayRPuSfXwg==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -6995,9 +7020,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -7033,25 +7058,10 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7075,9 +7085,9 @@ "integrity": "sha512-g1Aznml3HmqTLSXylZhGwdfnAa67+vlNAYhT9ROJZkAxY7yYmWusND10olvCMPe4sVhZyVwn5tPkRzOg85kBEg==" }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "funding": [ { "type": "opencollective", @@ -7092,12 +7102,14 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -7282,9 +7294,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "funding": [ { "type": "opencollective", @@ -7299,6 +7311,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "CC-BY-4.0", "peer": true }, "node_modules/cborg": { @@ -7687,19 +7700,34 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "engines": [ - "node >= 0.8" + "node >= 6.0" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", - "readable-stream": "^2.2.2", + "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -7878,7 +7906,8 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "peer": true }, "node_modules/cors": { "version": "2.8.5", @@ -8295,9 +8324,10 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", - "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", + "version": "1.5.412", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz", + "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==", + "license": "ISC", "peer": true }, "node_modules/emoji-regex": { @@ -8410,9 +8440,9 @@ } }, "node_modules/es-object-atoms": { - "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==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -8422,9 +8452,10 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -8950,14 +8981,14 @@ "peer": true }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -8976,7 +9007,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -8996,12 +9027,13 @@ } }, "node_modules/express-rate-limit": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", - "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz", + "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==", "license": "MIT", "dependencies": { - "ip-address": "10.1.0" + "debug": "^4.4.3", + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -9120,10 +9152,21 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fast-write-atomic": { "version": "0.2.1", @@ -9131,9 +9174,9 @@ "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" }, "node_modules/fast-xml-parser": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", - "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.7.tgz", + "integrity": "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ==", "funding": [ { "type": "github", @@ -10079,13 +10122,14 @@ } }, "node_modules/helia": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.5.tgz", - "integrity": "sha512-0X4Kb1jIwGGROcIcfS5xSjQHH8uisBbMGvktnWjziGkmlcgb8JXzilLXTtH+jDpkTkpo0s1VTKZkHo9rozjWkw==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.6.tgz", + "integrity": "sha512-qa/ADpLRRqzQHchd0XqpC4cVj9+OC4BA4QldN6dxBO0OLuGoUEo6kGrbpkygqNQ8RupA1ViFBWP7TqX6OIrppg==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/block-brokers": "^3.0.3", + "@helia/block-brokers": "^3.0.4", "@helia/delegated-routing-v1-http-api-client": "^3.0.0", "@helia/interface": "^4.3.1", "@helia/routers": "^1.1.1", @@ -10465,9 +10509,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", + "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", "license": "MIT", "engines": { "node": ">= 12" @@ -10796,7 +10840,8 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "peer": true }, "node_modules/isexe": { "version": "2.0.0", @@ -11384,9 +11429,10 @@ } }, "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "version": "17.13.6", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.6.tgz", + "integrity": "sha512-ImNZaq/LSysofih+xIGYfR0WUXMA9GLUNB//YTCSrZptoRmVgaNAdJyi6K1kXi9pkLEoSkoI8I4UwtNiu/D7nw==", + "license": "BSD-3-Clause", "peer": true, "dependencies": { "@hapi/hoek": "^9.3.0", @@ -11411,10 +11457,20 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -11523,15 +11579,16 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "peer": true, "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -12554,9 +12611,9 @@ } }, "node_modules/metro-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "license": "MIT", "peer": true, "dependencies": { @@ -12864,9 +12921,10 @@ } }, "node_modules/metro/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", "peer": true, "engines": { "node": ">=8.3.0" @@ -13028,6 +13086,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -13056,20 +13115,22 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", + "license": "MIT", "dependencies": { "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/multicast-dns": { @@ -13256,10 +13317,14 @@ "peer": true }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "peer": true + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } }, "node_modules/node-stream-zip": { "version": "1.15.0", @@ -14201,7 +14266,8 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "peer": true }, "node_modules/process-warning": { "version": "3.0.0", @@ -14298,12 +14364,13 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -14476,9 +14543,10 @@ } }, "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", "peer": true, "engines": { "node": ">=8.3.0" @@ -14640,9 +14708,10 @@ } }, "node_modules/react-native/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", + "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "license": "MIT", "peer": true, "dependencies": { "async-limiter": "~1.0.0" @@ -14812,6 +14881,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14825,7 +14895,8 @@ "node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "peer": true }, "node_modules/readdirp": { "version": "3.6.0", @@ -15106,16 +15177,16 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/rimraf/node_modules/glob": { @@ -15435,23 +15506,27 @@ } }, "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "license": "MIT", "peer": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -16367,7 +16442,8 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" }, "node_modules/typescript": { "version": "5.5.4", @@ -16488,9 +16564,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "funding": [ { "type": "opencollective", @@ -16505,10 +16581,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16785,9 +16862,10 @@ "dev": true }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -16828,6 +16906,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "peer": true, "engines": { "node": ">=0.4" } diff --git a/package.json b/package.json index 7c9c8c1..6698eca 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,19 @@ "private": true, "type": "module", "scripts": { - "build": "npx rimraf dist; tsc", + "build": "rimraf dist && tsc", "start": "node dist/index.js", "dev": "nodemon", + "lint": "eslint src test scripts --ext .ts,.mjs", + "test:build": "rimraf dist-test && tsc -p tsconfig.test.json", + "test": "npm run test:build && node --test dist-test/test/*.test.js", "prepare": "husky", "bump-patch": "npm version patch", "bump-minor": "npm version minor", "bump-major": "npm version major", - "security:audit": "npm audit --audit-level=high", - "security:audit:full": "npm audit --json > npm-audit-report.json", + "security:audit": "node scripts/audit-production.mjs", + "security:audit:raw": "npm audit --omit=dev --omit=peer --audit-level=high", + "security:audit:full": "npm audit --omit=dev --omit=peer --json > npm-audit-report.json", "security:semgrep": "semgrep --config p/owasp-top-ten --config p/nodejs --config p/expressjs --config p/typescript --config .semgrep/rules.yml --no-strict src/" }, "author": "ADAMANT Foundation ", @@ -24,7 +28,7 @@ "@commitlint/config-conventional": "^19.2.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/multer": "^1.4.11", + "@types/multer": "^2.2.0", "@types/node": "^20.14.8", "@typescript-eslint/eslint-plugin": "^8.59.1", "eslint": "^8.57.0", @@ -47,12 +51,12 @@ "cors": "^2.8.5", "cron": "^3.1.7", "datastore-fs": "^9.1.9", - "express": "^4.19.2", - "express-rate-limit": "^8.4.1", + "express": "^4.22.2", + "express-rate-limit": "^8.6.2", "go-get-folder-size": "^0.5.5", "helia": "^4.2.5", "json5": "^2.2.3", - "multer": "^1.4.5-lts.1", + "multer": "^2.2.0", "multiformats": "^13.2.2", "pino": "^9.3.2", "pino-http": "^10.2.0" diff --git a/scripts/audit-production.mjs b/scripts/audit-production.mjs new file mode 100644 index 0000000..946c705 --- /dev/null +++ b/scripts/audit-production.mjs @@ -0,0 +1,80 @@ +import { spawnSync } from 'node:child_process' + +const acceptedAdvisory = 'https://github.com/advisories/GHSA-32mq-hpph-xfvr' +const acceptedPackages = new Set(['@libp2p/kad-dht', 'helia']) + +const result = spawnSync('npm', ['audit', '--omit=dev', '--omit=peer', '--json'], { + encoding: 'utf8', + maxBuffer: 10 * 1024 * 1024 +}) + +if (result.error) { + throw result.error +} + +if (!result.stdout) { + throw new Error(result.stderr || 'npm audit produced no report') +} + +let report +try { + report = JSON.parse(result.stdout) +} catch { + throw new Error('npm audit produced invalid JSON') +} + +if (!report.metadata || typeof report.vulnerabilities !== 'object') { + throw new Error('npm audit report is incomplete') +} + +const blocking = [] +const accepted = [] + +for (const [name, vulnerability] of Object.entries(report.vulnerabilities || {})) { + if (!['high', 'critical'].includes(vulnerability.severity)) { + continue + } + + if (isAcceptedDhtFinding(name, vulnerability)) { + accepted.push(name) + } else { + blocking.push(name) + } +} + +if (accepted.length > 0) { + process.stdout.write( + `Accepted until #21: ${accepted.join(', ')} (${acceptedAdvisory}). ` + + 'The vulnerable DHT service is installed by Helia 4 but is replaced by the explicit service map in src/helia.ts.\n' + ) +} + +if (blocking.length > 0) { + throw new Error(`Blocking high/critical production vulnerabilities: ${blocking.join(', ')}`) +} + +process.stdout.write( + 'No unaccepted high or critical production dependency vulnerabilities found.\n' +) + +function isAcceptedDhtFinding(name, vulnerability) { + if (!acceptedPackages.has(name)) { + return false + } + + if (name === 'helia') { + return ( + Array.isArray(vulnerability.via) && + vulnerability.via.length === 1 && + vulnerability.via[0] === '@libp2p/kad-dht' + ) + } + + return ( + Array.isArray(vulnerability.via) && + vulnerability.via.length === 1 && + typeof vulnerability.via[0] === 'object' && + vulnerability.via[0] && + vulnerability.via[0].url === acceptedAdvisory + ) +} diff --git a/src/api/file.ts b/src/api/file.ts index f559950..6a27ac0 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -7,12 +7,13 @@ import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js' -import { writeLimiter, readLimiter } from '../middleware/rateLimiter.js' +import { uploadLimiter, readLimiter } from '../middleware/rateLimiter.js' +import { InvalidRequestError } from '../security/errors.js' const router = Router() -router.post('/upload', writeLimiter, multerStorage.array('files'), async (req, res, next) => { - if (!req.files) { +router.post('/upload', uploadLimiter, multerStorage.array('files'), async (req, res, next) => { + if (!Array.isArray(req.files) || req.files.length === 0) { res.statusCode = 400 return res.send({ error: 'No file uploaded' }) } @@ -59,19 +60,14 @@ router.post('/upload', writeLimiter, multerStorage.array('files'), async (req, r router.get('/:cid', readLimiter, async (req, res, next) => { try { - const cid = CID.parse(req.params.cid) + const cid = parseCid(req.params.cid) const fileStats = await getFileStats(cid) - - let streamStarted = false const stream = downloadFile(cid) - stream.on('data', () => { - if (!streamStarted) { - streamStarted = true - res.set('Content-Type', 'application/octet-stream') - res.set('Content-Length', fileStats.fileSize.toString()) - } - }) + res.set('Content-Type', 'application/octet-stream') + res.set('Content-Length', fileStats.fileSize.toString()) + res.set('Content-Disposition', `attachment; filename="${cid.toString()}"`) + res.set('X-Content-Type-Options', 'nosniff') stream.on('error', (err) => { pino.logger.error(err) @@ -88,4 +84,13 @@ router.get('/:cid', readLimiter, async (req, res, next) => { } }) +/** Parse a route CID without exposing parser internals to the client. */ +function parseCid(value: string): CID { + try { + return CID.parse(value) + } catch { + throw new InvalidRequestError('Invalid CID') + } +} + export default router diff --git a/src/api/helia.ts b/src/api/helia.ts index 87da582..bf81158 100644 --- a/src/api/helia.ts +++ b/src/api/helia.ts @@ -3,26 +3,29 @@ import { Pin } from 'helia' import { CID } from 'multiformats/cid' import { helia } from '../helia.js' import { pino } from '../utils/logger.js' -import { writeLimiter, readLimiter } from '../middleware/rateLimiter.js' +import { pinLimiter, readLimiter } from '../middleware/rateLimiter.js' +import { InvalidRequestError } from '../security/errors.js' const router = Router() -router.get('/pins', readLimiter, async (req, res) => { - const pins: Pin[] = [] +router.get('/pins', readLimiter, async (req, res, next) => { + try { + const pins: Pin[] = [] - for await (const pin of helia.pins.ls()) { - pino.logger.info('PIN LS', pin) - pins.push(pin) - } + for await (const pin of helia.pins.ls()) { + pino.logger.info('PIN LS', pin) + pins.push(pin) + } - res.send({ - pins - }) + res.send({ pins }) + } catch (err) { + next(err) + } }) -router.post('/pin/:cid', writeLimiter, async (req, res, next) => { +router.post('/pin/:cid', pinLimiter, async (req, res, next) => { try { - const cid = CID.parse(req.params.cid) + const cid = parseCid(req.params.cid) for await (const pin of helia.pins.add(cid)) { pino.logger.info('PINNED', pin) } @@ -32,20 +35,24 @@ router.post('/pin/:cid', writeLimiter, async (req, res, next) => { } }) -router.get('/pins/isPinned/:cid', readLimiter, async (req, res) => { - const cid = CID.parse(req.params.cid) +router.get('/pins/isPinned/:cid', readLimiter, async (req, res, next) => { + try { + const cid = parseCid(req.params.cid) - const isPinned = await helia.pins.isPinned(cid) + const isPinned = await helia.pins.isPinned(cid) - res.send({ - cid: cid.toString(), - isPinned - }) + res.send({ + cid: cid.toString(), + isPinned + }) + } catch (err) { + next(err) + } }) router.get('/routing/findProviders/:cid', readLimiter, async (req, res, next) => { try { - const cid = CID.parse(req.params.cid) + const cid = parseCid(req.params.cid) const providers: string[] = [] for await (const provider of helia.routing.findProviders(cid)) { @@ -62,4 +69,13 @@ router.get('/routing/findProviders/:cid', readLimiter, async (req, res, next) => } }) +/** Parse a route CID without exposing parser internals to the client. */ +function parseCid(value: string): CID { + try { + return CID.parse(value) + } catch { + throw new InvalidRequestError('Invalid CID') + } +} + export default router diff --git a/src/api/node.ts b/src/api/node.ts index 9815870..9766674 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -2,7 +2,6 @@ import { Router } from 'express' import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' -import { apiKeyAuth } from '../middleware/apiKeyAuth.js' const router = Router() @@ -17,9 +16,7 @@ router.get('/health', async (req, res, next) => { } }) -// Protected — full node topology for operators only -// Requires x-api-key header matching config.adminApiKey -router.get('/info', apiKeyAuth, async (req, res, next) => { +router.get('/info', async (req, res, next) => { try { const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() res.send({ diff --git a/src/config.ts b/src/config.ts index 8a09a90..8701147 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,6 +2,7 @@ import * as fs from 'fs' import { fileURLToPath } from 'url' import { dirname, join } from 'path' import JSON5 from 'json5' +import { validateSecurityConfig } from './security/config.js' const currDir = dirname(fileURLToPath(import.meta.url)) const rootDir = join(currDir, '..') @@ -11,5 +12,6 @@ export const CONFIG_FILE_NAME = 'config.json5' const configPath = join(rootDir, CONFIG_FILE_NAME) export const config = JSON5.parse(fs.readFileSync(configPath, 'utf8')) +validateSecurityConfig(config) export const packageJson = JSON5.parse(fs.readFileSync(join(rootDir, 'package.json'), 'utf8')) diff --git a/src/helia.ts b/src/helia.ts index 6ae3dec..706303d 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -55,11 +55,11 @@ helia.libp2p.addEventListener('peer:disconnect', (evt) => { pino.logger.info(`Peer disconnected: ${peerId}`) }) -helia.libp2p.addEventListener('start', (event) => { +helia.libp2p.addEventListener('start', () => { pino.logger.info('Libp2p node started') }) -helia.libp2p.addEventListener('stop', (event) => { +helia.libp2p.addEventListener('stop', () => { pino.logger.info('Libp2p node stopped') }) diff --git a/src/index.ts b/src/index.ts index 81c6463..5b3400e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,14 @@ import express from 'express' -import { Request, Response } from 'express' import { pino } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' import cors from 'cors' import * as routers from './api/index.js' +import { errorHandler, notFoundHandler } from './middleware/errorHandler.js' +import { mountApiRoutes } from './security/accessPolicy.js' +import { createApiKeyAuth } from './security/apiKey.js' +import { createCorsOriginDelegate } from './security/cors.js' +import { parseTrustProxy } from './security/trustProxy.js' pino.logger.info(`Using config file: ${CONFIG_FILE_NAME}`) @@ -13,18 +17,17 @@ diskUsageCron.start() const PORT = config.serverPort const app = express() app.disable('x-powered-by') +app.set('trust proxy', parseTrustProxy(config.trustProxy)) app.use(pino) -const allowedOrigins: RegExp[] = (config.cors?.originRegexps ?? []).map( - (pattern: string) => new RegExp(pattern) -) - app.use( cors({ - origin: allowedOrigins, - credentials: true, - methods: ['GET', 'POST'] + origin: createCorsOriginDelegate(config.cors.allowedOrigins), + credentials: false, + methods: ['GET', 'POST'], + allowedHeaders: ['content-type', 'x-api-key'], + maxAge: 600 }) ) @@ -32,16 +35,10 @@ app.get('/', (req, res) => { res.send('IPFS node') }) -app.use('/api/file', routers.file) -app.use('/api/node', routers.node) -app.use('/api/helia', routers.helia) -app.use('/api/libp2p', routers.libp2p) -app.use('/api/debug', routers.debug) +mountApiRoutes(app, routers, createApiKeyAuth(config.adminApiKey), config.enableDebugApi === true) -app.use((err: Error, req: Request, res: Response) => { - pino.logger.error(`${err.message}\n${err.stack}`) - res.status(500).send({ error: 'Internal Server Error' }) -}) +app.use(notFoundHandler) +app.use(errorHandler) app.listen(PORT, () => { pino.logger.info(`Server is running on http://localhost:${PORT}`) diff --git a/src/middleware/apiKeyAuth.ts b/src/middleware/apiKeyAuth.ts deleted file mode 100644 index 6a7a6e3..0000000 --- a/src/middleware/apiKeyAuth.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createHmac, timingSafeEqual } from 'crypto' -import { Request, Response, NextFunction } from 'express' -import { config } from '../config.js' - -/** - * API key authentication middleware. - * Protects endpoints that expose sensitive node topology information - * (peerId, multiAddresses, disk usage). - * - * Key is read from config.adminApiKey. - * Fail-secure: if no key is configured, access is blocked entirely. - * - * Uses crypto.timingSafeEqual to prevent timing-based key enumeration attacks. - * - * Usage in route: router.get('/info', apiKeyAuth, handler) - * Client usage: curl -H "x-api-key: " http://node/api/node/info - */ -export function apiKeyAuth(req: Request, res: Response, next: NextFunction): void { - const configuredKey = config.adminApiKey as string | undefined - - // Fail-secure: block access if key is not configured - // Prevents accidental exposure after config migration - if (!configuredKey) { - res.status(503).send({ error: 'Service not configured' }) - return - } - - const providedKey = req.headers['x-api-key'] - - if (!providedKey || typeof providedKey !== 'string') { - res.status(401).send({ error: 'Unauthorized' }) - return - } - - // Use timing-safe comparison to prevent key enumeration via response timing. - // timingSafeEqual requires equal-length buffers — compare HMAC digests - // (always 32 bytes for SHA-256) so length differences leak no information. - // Use Uint8Array explicitly to satisfy TypeScript strict buffer typing. - const encoder = new TextEncoder() - const keyBytes = encoder.encode(configuredKey) - - const hmac = (value: string): Uint8Array => { - const result = createHmac('sha256', keyBytes).update(encoder.encode(value)).digest() - return new Uint8Array(result) - } - - const providedDigest = hmac(providedKey) - const configuredDigest = hmac(configuredKey) - - if (!timingSafeEqual(providedDigest, configuredDigest)) { - res.status(401).send({ error: 'Unauthorized' }) - return - } - - next() -} diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts new file mode 100644 index 0000000..1e29d22 --- /dev/null +++ b/src/middleware/errorHandler.ts @@ -0,0 +1,22 @@ +import type { ErrorRequestHandler, RequestHandler } from 'express' +import { getPublicError } from '../security/errors.js' +import { pino } from '../utils/logger.js' + +/** Return a generic response for routes outside the documented API surface. */ +export const notFoundHandler: RequestHandler = (req, res): void => { + res.status(404).send({ error: 'Not found' }) +} + +/** + * Log complete errors server-side while returning only controlled messages. + */ +export const errorHandler: ErrorRequestHandler = (err, req, res, next): void => { + if (res.headersSent) { + next(err) + return + } + + pino.logger.error(err) + const publicError = getPublicError(err) + res.status(publicError.status).send(publicError.body) +} diff --git a/src/middleware/rateLimiter.ts b/src/middleware/rateLimiter.ts index d07338c..c94bb54 100644 --- a/src/middleware/rateLimiter.ts +++ b/src/middleware/rateLimiter.ts @@ -1,29 +1,33 @@ -import { rateLimit } from 'express-rate-limit' +import { config } from '../config.js' +import { createRateLimiter, type RateLimitPolicy } from '../security/rateLimit.js' + +const uploadPolicy: RateLimitPolicy = config.rateLimits?.upload ?? { + windowMs: 15 * 60 * 1000, + limit: 10 +} + +const pinPolicy: RateLimitPolicy = config.rateLimits?.pin ?? { + windowMs: 15 * 60 * 1000, + limit: 10 +} + +const readPolicy: RateLimitPolicy = config.rateLimits?.read ?? { + windowMs: 60 * 1000, + limit: 100 +} /** - * Strict limiter for write operations (upload, pin). - * 10 requests per 15 minutes per IP. - * Prevents disk exhaustion via rapid uploads or forced pinning. + * Endpoint-specific limiter for public multipart uploads. */ -export const writeLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, - limit: 10, - standardHeaders: 'draft-8', - legacyHeaders: false, - message: { error: 'Too many requests. Please try again later.' }, - // Skip rate limiting for localhost — internal cron jobs and tooling - skip: (req) => req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1' -}) +export const uploadLimiter = createRateLimiter(uploadPolicy) + +/** + * Strict limiter for explicit administrative pin requests. + */ +export const pinLimiter = createRateLimiter(pinPolicy) /** * Lenient limiter for read operations (file download, pin status checks). * 100 requests per minute per IP. */ -export const readLimiter = rateLimit({ - windowMs: 60 * 1000, - limit: 100, - standardHeaders: 'draft-8', - legacyHeaders: false, - message: { error: 'Too many requests. Please try again later.' }, - skip: (req) => req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1' -}) +export const readLimiter = createRateLimiter(readPolicy) diff --git a/src/multer.ts b/src/multer.ts index 860761c..2d6d6c8 100644 --- a/src/multer.ts +++ b/src/multer.ts @@ -2,12 +2,13 @@ import multer from 'multer' import { ifs } from './helia.js' import { config } from './config.js' import { UnixfsMulterStorage } from './utils/unixfs-multer.storage.js' +import { createMultipartLimits } from './security/uploadLimits.js' export const multerStorage = multer({ storage: new UnixfsMulterStorage({ unixfs: ifs, - destination: (req, file) => '/', - filename: (req, file) => file.originalname + destination: () => '/', + filename: (_req, file) => file.originalname }), - limits: { fileSize: config.uploadLimitSizeBytes } + limits: createMultipartLimits(config.uploadLimitSizeBytes, config.maxFileCount) }) diff --git a/src/security/accessPolicy.ts b/src/security/accessPolicy.ts new file mode 100644 index 0000000..58de158 --- /dev/null +++ b/src/security/accessPolicy.ts @@ -0,0 +1,38 @@ +import type { Application, RequestHandler, Router } from 'express' + +export type ApiRouters = { + file: Router + node: Router + helia: Router + libp2p: Router + debug: Router +} + +/** + * Mount API routers at their documented public, administrative, or disabled + * boundaries. + * + * @param app Express application receiving the routes + * @param routers API routers without cross-cutting access middleware + * @param adminAuth fail-closed administrative authentication middleware + * @param enableDebugApi whether the authenticated debug API is mounted + */ +export function mountApiRoutes( + app: Application, + routers: ApiRouters, + adminAuth: RequestHandler, + enableDebugApi: boolean +): void { + app.use('/api/file', routers.file) + + // Node health remains public while topology-sensitive information is admin-only. + app.use('/api/node/info', adminAuth) + app.use('/api/node', routers.node) + + app.use('/api/helia', adminAuth, routers.helia) + app.use('/api/libp2p', adminAuth, routers.libp2p) + + if (enableDebugApi) { + app.use('/api/debug', adminAuth, routers.debug) + } +} diff --git a/src/security/apiKey.ts b/src/security/apiKey.ts new file mode 100644 index 0000000..e684bee --- /dev/null +++ b/src/security/apiKey.ts @@ -0,0 +1,41 @@ +import { createHmac, timingSafeEqual } from 'node:crypto' +import type { RequestHandler } from 'express' + +/** + * Create fail-closed administrative API-key middleware. + * + * @param configuredKey operator secret, or an empty value to disable admin access + * @returns Express middleware that accepts only a matching `x-api-key` header + */ +export function createApiKeyAuth(configuredKey: unknown): RequestHandler { + if (typeof configuredKey !== 'string' || configuredKey.length === 0) { + return (req, res): void => { + res.status(503).send({ error: 'Service not configured' }) + } + } + + const encoder = new TextEncoder() + const keyBytes = encoder.encode(configuredKey) + const configuredDigest = digest(configuredKey, keyBytes) + + return (req, res, next): void => { + const providedKey = req.headers['x-api-key'] + if (typeof providedKey !== 'string') { + res.status(401).send({ error: 'Unauthorized' }) + return + } + + const providedDigest = digest(providedKey, keyBytes) + if (!timingSafeEqual(providedDigest, configuredDigest)) { + res.status(401).send({ error: 'Unauthorized' }) + return + } + + res.set('Cache-Control', 'no-store') + next() + } +} + +function digest(value: string, key: Uint8Array): Uint8Array { + return new Uint8Array(createHmac('sha256', key).update(value, 'utf8').digest()) +} diff --git a/src/security/config.ts b/src/security/config.ts new file mode 100644 index 0000000..89eadeb --- /dev/null +++ b/src/security/config.ts @@ -0,0 +1,70 @@ +import { createOriginMatcher } from './cors.js' +import { validateRateLimitPolicy, type RateLimitPolicy } from './rateLimit.js' +import { parseTrustProxy } from './trustProxy.js' + +const insecureApiKeys = new Set([ + 'change-me-use-openssl-rand-hex-32', + 'replace-with-output-of-openssl-rand-hex-32', + 'your-generated-key-here' +]) + +type SecurityConfigInput = { + cors?: { allowedOrigins?: unknown } + trustProxy?: unknown + adminApiKey?: unknown + enableDebugApi?: unknown + rateLimits?: Partial> + uploadLimitSizeBytes?: unknown + maxFileCount?: unknown +} + +/** + * Validate configuration values that define HTTP security boundaries. + * Missing optional admin and proxy settings fail closed at runtime. + * + * @param config parsed application configuration + */ +export function validateSecurityConfig(config: SecurityConfigInput): void { + createOriginMatcher(config.cors?.allowedOrigins) + parseTrustProxy(config.trustProxy) + + if (config.adminApiKey !== undefined && config.adminApiKey !== '') { + if ( + typeof config.adminApiKey !== 'string' || + config.adminApiKey.length < 32 || + insecureApiKeys.has(config.adminApiKey) + ) { + throw new Error( + 'adminApiKey must be an empty value or a unique secret of at least 32 characters' + ) + } + } + + if (config.enableDebugApi !== undefined && typeof config.enableDebugApi !== 'boolean') { + throw new Error('enableDebugApi must be a boolean') + } + + if ( + typeof config.uploadLimitSizeBytes !== 'number' || + !Number.isSafeInteger(config.uploadLimitSizeBytes) || + config.uploadLimitSizeBytes < 1 + ) { + throw new Error('uploadLimitSizeBytes must be a positive integer') + } + + if ( + typeof config.maxFileCount !== 'number' || + !Number.isSafeInteger(config.maxFileCount) || + config.maxFileCount < 1 || + config.maxFileCount > 100 + ) { + throw new Error('maxFileCount must be an integer from 1 through 100') + } + + for (const name of ['upload', 'pin', 'read'] as const) { + const policy = config.rateLimits?.[name] as RateLimitPolicy | undefined + if (policy !== undefined) { + validateRateLimitPolicy(policy) + } + } +} diff --git a/src/security/cors.ts b/src/security/cors.ts new file mode 100644 index 0000000..963e2d0 --- /dev/null +++ b/src/security/cors.ts @@ -0,0 +1,134 @@ +import type { CorsOptions } from 'cors' + +type OriginRule = { + protocol: string + hostname: string + port: string + wildcard: boolean +} + +/** + * Compile a list of exact origins and left-most subdomain wildcards. + * Wildcards use the form `https://*.example.org`; paths, credentials, + * query strings, and fragments are rejected. + * + * @param allowedOrigins browser origins accepted by the API + * @returns a predicate suitable for testing a request Origin value + */ +export function createOriginMatcher(allowedOrigins: unknown): (origin: string) => boolean { + if (!Array.isArray(allowedOrigins) || allowedOrigins.length === 0) { + throw new Error('cors.allowedOrigins must be a non-empty array') + } + + const rules = allowedOrigins.map(parseOriginRule) + + return (origin: string): boolean => { + let parsed: URL + try { + parsed = new URL(origin) + } catch { + return false + } + + if ( + parsed.username || + parsed.password || + parsed.pathname !== '/' || + parsed.search || + parsed.hash + ) { + return false + } + + return rules.some((rule) => { + if (parsed.protocol !== rule.protocol || parsed.port !== rule.port) { + return false + } + + if (!rule.wildcard) { + return parsed.hostname === rule.hostname + } + + return parsed.hostname !== rule.hostname && parsed.hostname.endsWith(`.${rule.hostname}`) + }) + } +} + +/** + * Create the callback used by the Express CORS middleware. Requests without an + * Origin header are non-browser requests and are allowed. + * + * @param allowedOrigins browser origins accepted by the API + * @returns a CORS origin callback + */ +export function createCorsOriginDelegate(allowedOrigins: unknown): CorsOptions['origin'] { + const matchesOrigin = createOriginMatcher(allowedOrigins) + + return (origin, callback): void => { + callback(null, origin === undefined || matchesOrigin(origin)) + } +} + +function parseOriginRule(value: unknown): OriginRule { + if (typeof value !== 'string' || value.length === 0 || value.length > 255) { + throw new Error('Each CORS origin must be a non-empty string of at most 255 characters') + } + + const wildcardMatch = /^(https?):\/\/\*\.([a-z0-9.-]+)(?::([0-9]{1,5}))?$/i.exec(value) + if (wildcardMatch) { + const [, protocol, hostname, port = ''] = wildcardMatch + validateHostname(hostname) + validatePort(port) + return { + protocol: `${protocol.toLowerCase()}:`, + hostname: hostname.toLowerCase(), + port, + wildcard: true + } + } + + let parsed: URL + try { + parsed = new URL(value) + } catch { + throw new Error(`Invalid CORS origin: ${value}`) + } + + if ( + !['http:', 'https:'].includes(parsed.protocol) || + parsed.username || + parsed.password || + parsed.hostname.includes('*') || + parsed.pathname !== '/' || + parsed.search || + parsed.hash || + parsed.origin !== value + ) { + throw new Error(`CORS entries must be canonical HTTP(S) origins: ${value}`) + } + + return { + protocol: parsed.protocol, + hostname: parsed.hostname, + port: parsed.port, + wildcard: false + } +} + +function validateHostname(hostname: string): void { + if ( + hostname.length > 253 || + !hostname.includes('.') || + hostname.startsWith('.') || + hostname.endsWith('.') || + hostname.includes('..') + ) { + throw new Error(`Invalid wildcard CORS hostname: ${hostname}`) + } +} + +function validatePort(port: string): void { + if (port && Number(port) > 65535) { + throw new Error(`Invalid CORS port: ${port}`) + } +} diff --git a/src/security/errors.ts b/src/security/errors.ts new file mode 100644 index 0000000..c43477d --- /dev/null +++ b/src/security/errors.ts @@ -0,0 +1,42 @@ +import multer from 'multer' + +export type PublicError = { + status: number + body: { error: string } +} + +/** Error whose constructor accepts only an approved client-facing message. */ +export class InvalidRequestError extends Error { + constructor(public readonly publicMessage: 'Invalid CID') { + super(publicMessage) + } +} + +/** + * Map thrown values to an HTTP response without exposing dependency messages, + * stack traces, paths, or other internal details. + * + * @param error value forwarded to the Express error handler + * @returns controlled status and JSON body + */ +export function getPublicError(error: unknown): PublicError { + if (error instanceof InvalidRequestError) { + return { status: 400, body: { error: error.publicMessage } } + } + + if (error instanceof multer.MulterError) { + return { status: 400, body: { error: multerErrorMessage(error.code) } } + } + + return { status: 500, body: { error: 'Internal Server Error' } } +} + +function multerErrorMessage(code: string): string { + if (code === 'LIMIT_FILE_SIZE') { + return 'Upload size limit exceeded' + } + if (code === 'LIMIT_FILE_COUNT' || code === 'LIMIT_PART_COUNT') { + return 'Upload file count limit exceeded' + } + return 'Invalid multipart upload' +} diff --git a/src/security/rateLimit.ts b/src/security/rateLimit.ts new file mode 100644 index 0000000..a0ec37c --- /dev/null +++ b/src/security/rateLimit.ts @@ -0,0 +1,38 @@ +import { rateLimit, type RateLimitRequestHandler } from 'express-rate-limit' + +export type RateLimitPolicy = { + windowMs: number + limit: number +} + +/** + * Create an IP-based limiter for one endpoint class. + * + * @param policy fixed window duration and maximum request count + * @returns Express rate-limit middleware + */ +export function createRateLimiter(policy: RateLimitPolicy): RateLimitRequestHandler { + validateRateLimitPolicy(policy) + + return rateLimit({ + windowMs: policy.windowMs, + limit: policy.limit, + standardHeaders: 'draft-8', + legacyHeaders: false, + message: { error: 'Too many requests. Please try again later.' } + }) +} + +/** + * Validate operator-provided rate-limit settings. + * + * @param policy candidate policy + */ +export function validateRateLimitPolicy(policy: RateLimitPolicy): void { + if (!Number.isSafeInteger(policy.windowMs) || policy.windowMs < 1) { + throw new Error('Rate-limit windowMs must be a positive integer') + } + if (!Number.isSafeInteger(policy.limit) || policy.limit < 1) { + throw new Error('Rate-limit limit must be a positive integer') + } +} diff --git a/src/security/trustProxy.ts b/src/security/trustProxy.ts new file mode 100644 index 0000000..d64d790 --- /dev/null +++ b/src/security/trustProxy.ts @@ -0,0 +1,45 @@ +export type TrustProxySetting = false | number | string | string[] + +/** + * Validate the Express trust-proxy setting without permitting the unsafe + * blanket value `true`. + * + * @param value configured proxy hops, addresses, or named subnet ranges + * @returns a value accepted by Express `app.set('trust proxy', value)` + */ +export function parseTrustProxy(value: unknown): TrustProxySetting { + if (value === undefined || value === false) { + return false + } + + if (value === true) { + throw new Error('trustProxy=true is not allowed; configure exact proxy addresses or hop count') + } + + if (typeof value === 'number') { + if (!Number.isSafeInteger(value) || value < 1) { + throw new Error('trustProxy hop count must be a positive integer') + } + return value + } + + if (typeof value === 'string') { + return validateAddress(value) + } + + if (Array.isArray(value) && value.length > 0) { + return value.map(validateAddress) + } + + throw new Error('trustProxy must be false, a hop count, an address, or an address array') +} + +function validateAddress(value: unknown): string { + if (typeof value !== 'string' || value.trim() !== value || value.length === 0) { + throw new Error('trustProxy addresses must be non-empty strings without surrounding whitespace') + } + if (['*', '0.0.0.0/0', '::/0'].includes(value) || value.includes(',')) { + throw new Error('trustProxy must not trust every address or use comma-separated values') + } + return value +} diff --git a/src/security/uploadLimits.ts b/src/security/uploadLimits.ts new file mode 100644 index 0000000..4b99d8e --- /dev/null +++ b/src/security/uploadLimits.ts @@ -0,0 +1,24 @@ +export type MultipartLimits = { + fileSize: number + files: number + fields: number + parts: number +} + +/** + * Build streaming multipart limits. Busboy emits its part-count error when the + * configured value is reached, so one sentinel part is required to accept the + * documented maximum number of file parts. + * + * @param fileSize maximum bytes accepted for one file + * @param fileCount maximum files accepted in one request + * @returns Multer/Busboy limits that reject non-file fields and excess files + */ +export function createMultipartLimits(fileSize: number, fileCount: number): MultipartLimits { + return { + fileSize, + files: fileCount, + fields: 0, + parts: fileCount + 1 + } +} diff --git a/src/utils/sanitizeFilename.ts b/src/utils/sanitizeFilename.ts index 57c2679..4b08cca 100644 --- a/src/utils/sanitizeFilename.ts +++ b/src/utils/sanitizeFilename.ts @@ -2,33 +2,24 @@ * Sanitizes a filename from multipart Content-Disposition header. */ export function sanitizeFilename(raw: string): string { - let name = raw + let name = raw.normalize('NFKC') - // 1. Normalize Unicode — collapse homoglyphs, remove zero-width chars - name = name.normalize('NFKC') - - // 2. Strip null bytes — terminate strings in C libs prematurely - // eslint-disable-next-line no-control-regex - name = name.replace(/\x00/g, '') - - // 3. Strip control characters including \n, \r, \t — prevent log injection - // eslint-disable-next-line no-control-regex + // Strip control and formatting characters to prevent path and log injection. + /* eslint-disable no-control-regex */ name = name.replace(/[\x00-\x1f\x7f]/g, '') - - // 4. Strip path separators and traversal sequences + /* eslint-enable no-control-regex */ + name = name.replace(/[\u200b-\u200f\u202a-\u202e\u2060\ufeff]/g, '') name = name.replace(/[/\\]/g, '_') name = name.replace(/\.\.+/g, '') + name = name.replace(/^[. ]+|[. ]+$/g, '') - // 5. Enforce max length — 255 bytes is POSIX limit for filename - if (Buffer.byteLength(name, 'utf8') > 255) { - const buf = Buffer.from(name, 'utf8').subarray(0, 255) - name = new TextDecoder('utf-8', { fatal: false }).decode(buf) - } - - // 6. Fallback for empty result after sanitization - if (!name.trim()) { - name = 'unnamed' + let result = '' + for (const character of name) { + if (Buffer.byteLength(result + character, 'utf8') > 255) { + break + } + result += character } - return name + return result && !/^[._ -]+$/.test(result) ? result : 'unnamed' } diff --git a/test/sanitizeFilename.test.ts b/test/sanitizeFilename.test.ts new file mode 100644 index 0000000..239337a --- /dev/null +++ b/test/sanitizeFilename.test.ts @@ -0,0 +1,19 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { sanitizeFilename } from '../src/utils/sanitizeFilename.js' + +describe('sanitizeFilename', () => { + it('removes traversal, separators, control characters, and formatting marks', () => { + assert.equal(sanitizeFilename('../folder\\evil\n\u202efile.txt'), '_folder_evilfile.txt') + }) + + it('uses a safe fallback for an empty filename', () => { + assert.equal(sanitizeFilename('../..'), 'unnamed') + }) + + it('never splits UTF-8 characters or exceeds 255 bytes', () => { + const value = sanitizeFilename('🙂'.repeat(100)) + assert.ok(Buffer.byteLength(value, 'utf8') <= 255) + assert.equal(value.includes('\ufffd'), false) + }) +}) diff --git a/test/security.test.ts b/test/security.test.ts new file mode 100644 index 0000000..6613322 --- /dev/null +++ b/test/security.test.ts @@ -0,0 +1,299 @@ +import assert from 'node:assert/strict' +import type { AddressInfo } from 'node:net' +import { after, before, describe, it } from 'node:test' +import cors from 'cors' +import express, { Router, type Express } from 'express' +import multer from 'multer' +import { mountApiRoutes } from '../src/security/accessPolicy.js' +import { createApiKeyAuth } from '../src/security/apiKey.js' +import { validateSecurityConfig } from '../src/security/config.js' +import { createCorsOriginDelegate, createOriginMatcher } from '../src/security/cors.js' +import { getPublicError, InvalidRequestError } from '../src/security/errors.js' +import { createRateLimiter } from '../src/security/rateLimit.js' +import { parseTrustProxy } from '../src/security/trustProxy.js' +import { createMultipartLimits } from '../src/security/uploadLimits.js' + +describe('CORS origin policy', () => { + const matches = createOriginMatcher([ + 'https://adm.im', + 'https://*.adamant.im', + 'http://localhost:8080' + ]) + + it('accepts exact and wildcard subdomain origins', () => { + assert.equal(matches('https://adm.im'), true) + assert.equal(matches('https://chat.adamant.im'), true) + assert.equal(matches('https://nested.chat.adamant.im'), true) + assert.equal(matches('http://localhost:8080'), true) + }) + + it('rejects suffix confusion, paths, and unlisted schemes', () => { + assert.equal(matches('https://adamant.im.evil.example'), false) + assert.equal(matches('https://adamant.im'), false) + assert.equal(matches('https://chat.adamant.im/path'), false) + assert.equal(matches('http://chat.adamant.im'), false) + }) + + it('rejects invalid configured origin rules', () => { + assert.throws(() => createOriginMatcher(['*'])) + assert.throws(() => createOriginMatcher(['https://example.org/path'])) + assert.throws(() => createOriginMatcher(['https://*example.org'])) + }) + + it('emits an allow-origin header only for an accepted browser origin', async () => { + const app = express() + app.use(cors({ origin: createCorsOriginDelegate(['https://adm.im']) })) + app.get('/', (req, res) => res.send({ ok: true })) + const server = await startServer(app) + + try { + const allowed = await fetch(server.url, { headers: { origin: 'https://adm.im' } }) + const rejected = await fetch(server.url, { headers: { origin: 'https://evil.example' } }) + + assert.equal(allowed.headers.get('access-control-allow-origin'), 'https://adm.im') + assert.equal(rejected.headers.get('access-control-allow-origin'), null) + } finally { + await server.close() + } + }) +}) + +describe('security configuration', () => { + const baseConfig = { + cors: { allowedOrigins: ['https://adm.im'] }, + trustProxy: false, + adminApiKey: '', + enableDebugApi: false, + uploadLimitSizeBytes: 1024, + maxFileCount: 10 + } + + it('accepts a fail-closed default configuration', () => { + assert.doesNotThrow(() => validateSecurityConfig(baseConfig)) + }) + + it('rejects unsafe proxy trust and placeholder admin keys', () => { + assert.throws(() => validateSecurityConfig({ ...baseConfig, trustProxy: true })) + assert.throws(() => validateSecurityConfig({ ...baseConfig, trustProxy: '0.0.0.0/0' })) + assert.throws(() => + validateSecurityConfig({ + ...baseConfig, + adminApiKey: 'change-me-use-openssl-rand-hex-32' + }) + ) + }) + + it('rejects disabled or unreasonably broad upload limits', () => { + assert.throws(() => validateSecurityConfig({ ...baseConfig, uploadLimitSizeBytes: 0 })) + assert.throws(() => validateSecurityConfig({ ...baseConfig, maxFileCount: 101 })) + }) +}) + +describe('public error mapping', () => { + it('does not expose messages from unexpected exceptions', () => { + const result = getPublicError(new Error('/srv/private/blockstore failed with secret')) + + assert.deepEqual(result, { + status: 500, + body: { error: 'Internal Server Error' } + }) + }) + + it('returns only an approved validation message', () => { + assert.deepEqual(getPublicError(new InvalidRequestError('Invalid CID')), { + status: 400, + body: { error: 'Invalid CID' } + }) + }) +}) + +describe('streaming multipart limits', () => { + const app = express() + let serverUrl = '' + let closeServer: (() => Promise) | undefined + + before(async () => { + const upload = multer({ + storage: multer.memoryStorage(), + limits: createMultipartLimits(1024, 2) + }).array('files') + app.post('/upload', upload, (req, res) => res.send({ ok: true })) + app.use( + (err: unknown, req: express.Request, res: express.Response, _next: express.NextFunction) => { + void _next + res.status(400).send({ error: 'Rejected' }) + } + ) + + const server = await startServer(app) + serverUrl = server.url + closeServer = server.close + }) + + after(async () => closeServer?.()) + + it('accepts the configured maximum and rejects the next file while streaming', async () => { + assert.equal((await sendFiles(serverUrl, 2)).status, 200) + assert.equal((await sendFiles(serverUrl, 3)).status, 400) + }) +}) + +describe('administrative API key', () => { + const key = 'a'.repeat(64) + const app = express() + let serverUrl = '' + let closeServer: (() => Promise) | undefined + + before(async () => { + app.get('/admin', createApiKeyAuth(key), (req, res) => res.send({ ok: true })) + app.get('/disabled', createApiKeyAuth(''), (req, res) => res.send({ ok: true })) + const server = await startServer(app) + serverUrl = server.url + closeServer = server.close + }) + + after(async () => closeServer?.()) + + it('rejects missing and invalid keys and accepts the configured key', async () => { + assert.equal((await fetch(`${serverUrl}/disabled`)).status, 503) + assert.equal((await fetch(`${serverUrl}/admin`)).status, 401) + assert.equal( + (await fetch(`${serverUrl}/admin`, { headers: { 'x-api-key': 'invalid' } })).status, + 401 + ) + assert.equal((await fetch(`${serverUrl}/admin`, { headers: { 'x-api-key': key } })).status, 200) + assert.equal( + (await fetch(`${serverUrl}/admin`, { headers: { 'x-api-key': key } })).headers.get( + 'cache-control' + ), + 'no-store' + ) + }) +}) + +describe('route access policy', () => { + const key = 'b'.repeat(64) + const app = express() + let serverUrl = '' + let closeServer: (() => Promise) | undefined + + before(async () => { + const file = Router().get('/test', (req, res) => res.send({ public: true })) + const node = Router() + .get('/health', (req, res) => res.send({ public: true })) + .get('/info', (req, res) => res.send({ admin: true })) + const helia = Router().get('/test', (req, res) => res.send({ admin: true })) + const libp2p = Router().get('/test', (req, res) => res.send({ admin: true })) + const debug = Router().get('/test', (req, res) => res.send({ admin: true })) + + mountApiRoutes(app, { file, node, helia, libp2p, debug }, createApiKeyAuth(key), false) + + const server = await startServer(app) + serverUrl = server.url + closeServer = server.close + }) + + after(async () => closeServer?.()) + + it('keeps health and file transfer public', async () => { + assert.equal((await fetch(`${serverUrl}/api/node/health`)).status, 200) + assert.equal((await fetch(`${serverUrl}/api/file/test`)).status, 200) + }) + + it('protects node information, Helia, and libp2p routes', async () => { + for (const path of ['/api/node/info', '/api/helia/test', '/api/libp2p/test']) { + assert.equal((await fetch(`${serverUrl}${path}`)).status, 401) + assert.equal( + (await fetch(`${serverUrl}${path}`, { headers: { 'x-api-key': key } })).status, + 200 + ) + } + }) + + it('does not mount the debug API by default', async () => { + assert.equal((await fetch(`${serverUrl}/api/debug/test`)).status, 404) + }) + + it('protects the debug API when explicitly enabled', async () => { + const debugApp = express() + const debug = Router().get('/test', (req, res) => res.send({ admin: true })) + const emptyRouter = Router() + mountApiRoutes( + debugApp, + { + file: emptyRouter, + node: emptyRouter, + helia: emptyRouter, + libp2p: emptyRouter, + debug + }, + createApiKeyAuth(key), + true + ) + const server = await startServer(debugApp) + + try { + assert.equal((await fetch(`${server.url}/api/debug/test`)).status, 401) + assert.equal( + ( + await fetch(`${server.url}/api/debug/test`, { + headers: { 'x-api-key': key } + }) + ).status, + 200 + ) + } finally { + await server.close() + } + }) +}) + +describe('rate limiting behind a trusted proxy', () => { + const app = express() + let serverUrl = '' + let closeServer: (() => Promise) | undefined + + before(async () => { + app.set('trust proxy', parseTrustProxy('loopback')) + app.get('/upload', createRateLimiter({ windowMs: 60_000, limit: 1 }), (req, res) => + res.send({ ok: true }) + ) + const server = await startServer(app) + serverUrl = server.url + closeServer = server.close + }) + + after(async () => closeServer?.()) + + it('keys limits by the validated forwarded client address', async () => { + const firstClient = { 'x-forwarded-for': '198.51.100.10' } + const secondClient = { 'x-forwarded-for': '198.51.100.11' } + + assert.equal((await fetch(`${serverUrl}/upload`, { headers: firstClient })).status, 200) + assert.equal((await fetch(`${serverUrl}/upload`, { headers: firstClient })).status, 429) + assert.equal((await fetch(`${serverUrl}/upload`, { headers: secondClient })).status, 200) + }) +}) + +async function startServer(app: Express): Promise<{ url: string; close: () => Promise }> { + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + + return { + url: `http://127.0.0.1:${address.port}`, + close: () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + } +} + +async function sendFiles(serverUrl: string, count: number): Promise { + const body = new FormData() + for (let index = 0; index < count; index += 1) { + body.append('files', new Blob([String(index)]), `${index}.txt`) + } + return fetch(`${serverUrl}/upload`, { method: 'POST', body }) +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..4055431 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./dist-test", + "rootDir": "." + }, + "include": ["src/security/**/*.ts", "src/utils/sanitizeFilename.ts", "test/**/*.ts"] +} From 231979ded28ffb9aa56c8e91c920132d2f7243be Mon Sep 17 00:00:00 2001 From: adamantmm Date: Sat, 22 Aug 2026 14:56:43 +0300 Subject: [PATCH 099/167] fix: after review fixes chore: update README for CORS origin wildcard behavior and multipart contract chore: update package dependencies for libp2p and multiformats fix: enhance npm audit script to handle incomplete reports refactor: remove unnecessary error logging in debug and file APIs feat: implement controlled download response streaming with error handling feat: introduce FileNotFoundError for better error management in file retrieval test: add tests for download response streaming and error handling --- .eslintrc.json | 5 +- .eslintrc.override.json | 6 --- README.md | 22 +++++++- package-lock.json | 64 ++++++++++++++--------- package.json | 5 ++ scripts/audit-production.mjs | 6 ++- src/api/debug.ts | 1 - src/api/file.ts | 38 ++++---------- src/api/helia.ts | 13 +---- src/api/index.ts | 4 +- src/api/libp2p.ts | 36 ++++++------- src/api/node.ts | 3 +- src/index.ts | 10 +++- src/security/accessPolicy.ts | 6 +-- src/security/cors.ts | 6 +-- src/security/errors.ts | 15 +++++- src/utils/cid.ts | 16 ++++++ src/utils/downloadResponse.ts | 50 ++++++++++++++++++ src/utils/file.ts | 24 ++++----- src/utils/fileErrors.ts | 7 +++ test/downloadResponse.test.ts | 95 +++++++++++++++++++++++++++++++++++ test/security.test.ts | 56 +++++++++++++++++++-- 22 files changed, 361 insertions(+), 127 deletions(-) delete mode 100644 .eslintrc.override.json create mode 100644 src/utils/cid.ts create mode 100644 src/utils/downloadResponse.ts create mode 100644 src/utils/fileErrors.ts create mode 100644 test/downloadResponse.test.ts diff --git a/.eslintrc.json b/.eslintrc.json index 3474edf..25e03d0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,3 @@ { - "extends": [ - "./node_modules/gts/", - "./.eslintrc.override.json" - ] + "extends": ["./node_modules/gts/"] } diff --git a/.eslintrc.override.json b/.eslintrc.override.json deleted file mode 100644 index d83d127..0000000 --- a/.eslintrc.override.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rules": { - "n/no-extraneous-import": "off", - "@typescript-eslint/no-unsafe-function-type": "off" - } -} diff --git a/README.md b/README.md index ad3464f..0546cd4 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a ### Configuration migration - Replace `cors.origin` or `cors.originRegexps` with `cors.allowedOrigins` -- Use exact origins such as `https://adm.im` or left-most subdomain wildcards such as `https://*.adamant.im` +- Use exact origins such as `https://adm.im` or any-depth subdomain suffix wildcards such as `https://*.adamant.im` - Set `adminApiKey` before using any administrative API - Leave `trustProxy` as `false` for direct connections; configure exact proxy addresses, CIDR ranges, or a verified hop count behind a proxy - Set `enableDebugApi: true` only when the authenticated debug route is operationally required @@ -87,9 +87,13 @@ Upload remains public for compatibility with direct Messenger clients. The servi This is an explicit compatibility decision, not an authorization guarantee. A deployment that requires signed upload authorization must enforce it at a trusted gateway until a client-compatible signing protocol is designed. Files of any content type are accepted, but downloads are served as `application/octet-stream` attachments with content sniffing disabled. +The multipart contract accepts `files` parts only. Text fields are rejected with a controlled `400 Bad Request` response. + +Interrupted uploads can leave unpinned blocks in the blockstore. Deleting only the returned root CID would miss descendant blocks, while recursively deleting content-addressed blocks could remove data shared with pinned files. Until [#22](https://github.com/Adamant-im/ipfs-node/issues/22) adds reference-aware garbage collection, operators must monitor and bound blockstore growth at the deployment layer. + ### CORS -`cors.allowedOrigins` accepts canonical HTTP(S) origins only. It does not accept paths, credentials, query strings, fragments, or unrestricted `*`. Wildcards are limited to a left-most subdomain. For example, `https://*.adamant.im` matches `https://chat.adamant.im` but not `https://adamant.im` or `https://adamant.im.example.org`. +`cors.allowedOrigins` accepts canonical HTTP(S) origins only. It does not accept paths, credentials, query strings, fragments, or unrestricted `*`. Wildcards match subdomains at any depth beneath the configured suffix. For example, `https://*.adamant.im` matches both `https://chat.adamant.im` and `https://nested.chat.adamant.im`, but not `https://adamant.im` or `https://adamant.im.example.org`. Requests without an `Origin` header, such as server-to-server calls and `curl`, are not blocked by CORS. Unauthorized administrative requests are still rejected by API-key middleware. @@ -105,6 +109,8 @@ A numeric hop count is supported only for a fixed topology in which every path t Application limiters use in-memory counters per process. The TLS proxy must also enforce request rates, connection limits, header limits, and a body-size limit no larger than `uploadLimitSizeBytes` for multi-process or distributed deployments. +Keeping `trustProxy` disabled behind a proxy makes every client share the proxy's IP identity and therefore the same application rate-limit bucket. The process logs a startup warning for this configuration. Do not enable a broader trust rule merely to suppress the warning; configure only the known proxy addresses or a verified fixed hop count. + ### TLS boundary The Node.js process serves HTTP and does not terminate TLS. Bind it to a private interface or firewall it so clients can reach it only through a correctly configured HTTPS reverse proxy. The proxy must replace untrusted forwarding headers and forward requests to the configured `serverPort`. @@ -153,6 +159,18 @@ curl --fail-with-body \ https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm ``` +Download responses use the following stable status contract: + +| Status | Meaning | +| --------------------------- | ---------------------------------------------------------------------- | +| `200 OK` | The response contains the requested file as an attachment | +| `400 Bad Request` | The CID is invalid | +| `408 Request Timeout` | The file could not be found or retrieved before the configured timeout | +| `429 Too Many Requests` | The read rate limit was exceeded | +| `500 Internal Server Error` | An unexpected internal failure occurred before streaming started | + +The timeout status remains `408 Request Timeout` for compatibility with existing clients. If an error occurs after response bytes have started, the server terminates the incomplete response because an HTTP status and JSON error body can no longer be sent safely. + ### Check public health ```bash diff --git a/package-lock.json b/package-lock.json index 60cc9e6..2b9f374 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,15 @@ "license": "GPLv3", "dependencies": { "@chainsafe/libp2p-noise": "^15.1.0", + "@chainsafe/libp2p-yamux": "^6.0.2", "@helia/unixfs": "^3.0.7", "@libp2p/bootstrap": "^10.1.4", + "@libp2p/identify": "^2.1.5", + "@libp2p/interface": "^1.7.0", "@libp2p/peer-id": "^4.2.3", + "@libp2p/ping": "^1.1.6", "@libp2p/tcp": "^9.1.4", + "@multiformats/multiaddr": "^12.5.1", "blockstore-fs": "^1.1.11", "cors": "^2.8.5", "cron": "^3.1.7", @@ -2260,6 +2265,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-yamux/-/libp2p-yamux-6.0.2.tgz", "integrity": "sha512-S5OkLHqYhEVMQQ4BTgnRANEIbGTQhaC23glCgBwGdeoTRtMpIozwDiPfljFLCm0RYWdCRJw9oFztO95KUHjptA==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^1.1.3", "@libp2p/utils": "^5.2.5", @@ -3135,14 +3141,15 @@ } }, "node_modules/@libp2p/identify": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.1.4.tgz", - "integrity": "sha512-w2JkmoZkXT3OJnTuyBPlEBicj1J1n90pcusZbs+YIiJ+mhsOvNKnQhJgCG8Q6wnTimcBjinqhRRp0hiaHqX7VQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.1.5.tgz", + "integrity": "sha512-uVghY2KfZ3ffDkPmcivfiRDlq1h5rCcoHAW+Kb7JF2qrDfg6BgHAn6IRN4pe/DnYXOuJXIIm6+jjcReTPGBKBQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/peer-record": "^7.0.24", + "@libp2p/interface": "^1.7.0", + "@libp2p/interface-internal": "^1.3.4", + "@libp2p/peer-id": "^4.2.4", + "@libp2p/peer-record": "^7.0.25", "@multiformats/multiaddr": "^12.2.3", "@multiformats/multiaddr-matcher": "^1.2.1", "it-drain": "^3.0.7", @@ -3329,14 +3336,15 @@ } }, "node_modules/@libp2p/peer-record": { - "version": "7.0.24", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.24.tgz", - "integrity": "sha512-2LYvLqr3XDjA5u2LGD/3IvdgdrwOYdMzQnzr612/Q+yVS+36CN0dAIqU74OEWx+XeeUJSBQnQ0ryBQq/arbXuA==", + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.25.tgz", + "integrity": "sha512-b54P3cSeQniW/HPJjBVbeF3KaVUQkWa431gotuIFUS1PYgtz69uzkRrVY6Qt+RBb4R4fcmH4K4jWyZi3xyLGfQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/utils": "^5.4.8", + "@libp2p/crypto": "^4.1.9", + "@libp2p/interface": "^1.7.0", + "@libp2p/peer-id": "^4.2.4", + "@libp2p/utils": "^5.4.9", "@multiformats/multiaddr": "^12.2.3", "protons-runtime": "^5.4.0", "uint8-varint": "^2.0.4", @@ -3364,13 +3372,14 @@ } }, "node_modules/@libp2p/ping": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.1.4.tgz", - "integrity": "sha512-biwYtLaEHky2hsLzVZnPzrwME2WwaMu2MG+poKepxS36Ib9EM7XPGVUjpoafCQBM+Z67AMiFpKVtk3k9Kb24Gw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.1.6.tgz", + "integrity": "sha512-tzTL0BzS1JaHE8v4PhRZ5K8wQQQcTMXM/0baCkLTLIaSMe1fzhj+KHbFNoUrY3yni4yfsVY1uR0qchhc1/J9qg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", + "@libp2p/crypto": "^4.1.9", + "@libp2p/interface": "^1.7.0", + "@libp2p/interface-internal": "^1.3.4", "@multiformats/multiaddr": "^12.2.3", "it-first": "^3.0.6", "it-pipe": "^3.0.1", @@ -3535,14 +3544,15 @@ } }, "node_modules/@multiformats/multiaddr": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.3.0.tgz", - "integrity": "sha512-JQ8Gc/jgucqqvEaDTFN/AvxlYDHEE7lgEWLMYW7hKZkWggER+GvG/tVxUgUxIP8M0vFpvEHKKHE0lKzyMsgi8Q==", + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@chainsafe/netmask": "^2.0.0", - "@libp2p/interface": "^1.0.0", "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", "multiformats": "^13.0.0", "uint8-varint": "^2.0.1", "uint8arrays": "^5.0.0" @@ -6537,6 +6547,12 @@ "node": ">=6.5" } }, + "node_modules/abort-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", + "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", diff --git a/package.json b/package.json index 6698eca..c33b6a6 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,15 @@ }, "dependencies": { "@chainsafe/libp2p-noise": "^15.1.0", + "@chainsafe/libp2p-yamux": "^6.0.2", "@helia/unixfs": "^3.0.7", "@libp2p/bootstrap": "^10.1.4", + "@libp2p/identify": "^2.1.5", + "@libp2p/interface": "^1.7.0", "@libp2p/peer-id": "^4.2.3", + "@libp2p/ping": "^1.1.6", "@libp2p/tcp": "^9.1.4", + "@multiformats/multiaddr": "^12.5.1", "blockstore-fs": "^1.1.11", "cors": "^2.8.5", "cron": "^3.1.7", diff --git a/scripts/audit-production.mjs b/scripts/audit-production.mjs index 946c705..4de2cda 100644 --- a/scripts/audit-production.mjs +++ b/scripts/audit-production.mjs @@ -23,7 +23,11 @@ try { throw new Error('npm audit produced invalid JSON') } -if (!report.metadata || typeof report.vulnerabilities !== 'object') { +if ( + !report.metadata || + report.vulnerabilities === null || + typeof report.vulnerabilities !== 'object' +) { throw new Error('npm audit report is incomplete') } diff --git a/src/api/debug.ts b/src/api/debug.ts index efcd3e6..2b0c800 100644 --- a/src/api/debug.ts +++ b/src/api/debug.ts @@ -27,7 +27,6 @@ router.get('/autopeering', async (req, res, next) => { peeredSuccessfullyTo: successPeers }) } catch (err) { - pino.logger.error(err) next(err) } }) diff --git a/src/api/file.ts b/src/api/file.ts index 6a27ac0..53fff4b 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -6,9 +6,10 @@ import { helia } from '../helia.js' import { pino } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' -import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js' +import { downloadFile, getFileStats } from '../utils/file.js' import { uploadLimiter, readLimiter } from '../middleware/rateLimiter.js' -import { InvalidRequestError } from '../security/errors.js' +import { parseCid } from '../utils/cid.js' +import { sendDownloadStream } from '../utils/downloadResponse.js' const router = Router() @@ -52,8 +53,6 @@ router.post('/upload', uploadLimiter, multerStorage.array('files'), async (req, cids: cids.map((cid) => cid.toString()) }) } catch (err) { - pino.logger.error(err) - next(err) } }) @@ -64,33 +63,16 @@ router.get('/:cid', readLimiter, async (req, res, next) => { const fileStats = await getFileStats(cid) const stream = downloadFile(cid) - res.set('Content-Type', 'application/octet-stream') - res.set('Content-Length', fileStats.fileSize.toString()) - res.set('Content-Disposition', `attachment; filename="${cid.toString()}"`) - res.set('X-Content-Type-Options', 'nosniff') - - stream.on('error', (err) => { - pino.logger.error(err) - next(err) - }) - - stream.pipe(res) + sendDownloadStream( + stream, + res, + { cid: cid.toString(), fileSize: fileStats.fileSize }, + next, + (error) => pino.logger.error(error) + ) } catch (error) { - if (error instanceof FileNotFoundError) { - res.status(404).send({ error: 'File not found' }) - return - } next(error) } }) -/** Parse a route CID without exposing parser internals to the client. */ -function parseCid(value: string): CID { - try { - return CID.parse(value) - } catch { - throw new InvalidRequestError('Invalid CID') - } -} - export default router diff --git a/src/api/helia.ts b/src/api/helia.ts index bf81158..eb9328f 100644 --- a/src/api/helia.ts +++ b/src/api/helia.ts @@ -1,10 +1,9 @@ import { Router } from 'express' import { Pin } from 'helia' -import { CID } from 'multiformats/cid' import { helia } from '../helia.js' import { pino } from '../utils/logger.js' import { pinLimiter, readLimiter } from '../middleware/rateLimiter.js' -import { InvalidRequestError } from '../security/errors.js' +import { parseCid } from '../utils/cid.js' const router = Router() @@ -64,18 +63,8 @@ router.get('/routing/findProviders/:cid', readLimiter, async (req, res, next) => providers }) } catch (err) { - pino.logger.error(err) next(err) } }) -/** Parse a route CID without exposing parser internals to the client. */ -function parseCid(value: string): CID { - try { - return CID.parse(value) - } catch { - throw new InvalidRequestError('Invalid CID') - } -} - export default router diff --git a/src/api/index.ts b/src/api/index.ts index 18d1d53..790d26a 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,7 +1,7 @@ import file from './file.js' -import node from './node.js' +import node, { publicNodeRouter } from './node.js' import helia from './helia.js' import libp2p from './libp2p.js' import debug from './debug.js' -export { file, node, helia, libp2p, debug } +export { file, node, publicNodeRouter, helia, libp2p, debug } diff --git a/src/api/libp2p.ts b/src/api/libp2p.ts index 9233e26..bd89406 100644 --- a/src/api/libp2p.ts +++ b/src/api/libp2p.ts @@ -2,16 +2,17 @@ import { PeerId } from '@libp2p/interface' import { peerIdFromString } from '@libp2p/peer-id' import type { PingService } from '@libp2p/ping' import { multiaddr, Multiaddr } from '@multiformats/multiaddr' -import { Request, Response, Router } from 'express' +import { type NextFunction, Request, Response, Router } from 'express' import { PeerIdDto } from '../dto/peer-id.dto.js' import { helia } from '../helia.js' import { pino } from '../utils/logger.js' +import { InvalidRequestError } from '../security/errors.js' const router = Router() router.get( '/services/ping', - async (req: Request, res: Response, next: Function) => { + async (req: Request, res: Response, next: NextFunction) => { try { const peerId = peerIdFromString(req.query.peerId || '') const pingService = helia.libp2p.services.ping as PingService @@ -28,7 +29,7 @@ router.get( router.get( '/peerStore', - async (req: Request, res: Response, next: Function) => { + async (req: Request, res: Response, next: NextFunction) => { const peerId = req.query.peerId try { @@ -61,7 +62,7 @@ router.get( router.get( '/peerInfo', - async (req: Request, res: Response, next: Function) => { + async (req: Request, res: Response, next: NextFunction) => { const peerId = req.query.peerId try { @@ -85,7 +86,7 @@ router.get( async ( req: Request, res: Response, - next: Function + next: NextFunction ) => { let peerId: PeerId | undefined let multiAddr: Multiaddr | undefined @@ -97,12 +98,8 @@ router.get( if (req.query.multiAddr) { multiAddr = multiaddr(req.query.multiAddr || '') } - } catch (err) { - pino.logger.error('Invalid peer ID:' + err.message) - res.send({ - success: false, - error: 'Invalid peer ID' - }) + } catch { + next(new InvalidRequestError('Invalid peer identifier or multiaddress')) return } @@ -112,17 +109,16 @@ router.get( pino.logger.info(`Peering by PeerID: ${peerId}`) } + const peer = multiAddr || peerId + if (!peer) { + next(new InvalidRequestError('Peer identifier or multiaddress is required')) + return + } + try { - const peer = multiAddr || peerId - if (!peer) { - throw new Error('Empty peerId and MultiAddr') - } const connection = await helia.libp2p.dial(peer) res.send({ success: true, connection }) } catch (err) { - pino.logger.warn(`Cannot dial peer: ${err.message}`) - - pino.logger.error(err) next(err) } } @@ -130,7 +126,7 @@ router.get( router.get( '/connections', - async (req: Request, res: Response, next: Function) => { + async (req: Request, res: Response, next: NextFunction) => { try { const peerId = req.query.peerId?.toString() || '' const connections = helia.libp2p.getConnections(peerId ? peerIdFromString(peerId) : undefined) @@ -140,7 +136,6 @@ router.get( connections }) } catch (err) { - pino.logger.error(err) next(err) } } @@ -160,7 +155,6 @@ router.get('/peers', (req, res, next) => { res.send({ peers }) } catch (err) { - pino.logger.error(err) next(err) } }) diff --git a/src/api/node.ts b/src/api/node.ts index 9766674..9cb08b4 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -3,9 +3,10 @@ import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' +export const publicNodeRouter = Router() const router = Router() -router.get('/health', async (req, res, next) => { +publicNodeRouter.get('/health', async (req, res, next) => { try { res.send({ timestamp: Date.now(), diff --git a/src/index.ts b/src/index.ts index 5b3400e..7627130 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,15 @@ diskUsageCron.start() const PORT = config.serverPort const app = express() app.disable('x-powered-by') -app.set('trust proxy', parseTrustProxy(config.trustProxy)) +const trustProxy = parseTrustProxy(config.trustProxy) +app.set('trust proxy', trustProxy) + +if (trustProxy === false) { + pino.logger.warn( + 'trustProxy is false. If requests arrive through a reverse proxy, all clients will share ' + + 'the proxy IP for rate limiting until exact trusted proxy addresses are configured.' + ) +} app.use(pino) diff --git a/src/security/accessPolicy.ts b/src/security/accessPolicy.ts index 58de158..e7fcc2c 100644 --- a/src/security/accessPolicy.ts +++ b/src/security/accessPolicy.ts @@ -2,6 +2,7 @@ import type { Application, RequestHandler, Router } from 'express' export type ApiRouters = { file: Router + publicNodeRouter: Router node: Router helia: Router libp2p: Router @@ -25,9 +26,8 @@ export function mountApiRoutes( ): void { app.use('/api/file', routers.file) - // Node health remains public while topology-sensitive information is admin-only. - app.use('/api/node/info', adminAuth) - app.use('/api/node', routers.node) + app.use('/api/node', routers.publicNodeRouter) + app.use('/api/node', adminAuth, routers.node) app.use('/api/helia', adminAuth, routers.helia) app.use('/api/libp2p', adminAuth, routers.libp2p) diff --git a/src/security/cors.ts b/src/security/cors.ts index 963e2d0..2647447 100644 --- a/src/security/cors.ts +++ b/src/security/cors.ts @@ -8,9 +8,9 @@ type OriginRule = { } /** - * Compile a list of exact origins and left-most subdomain wildcards. - * Wildcards use the form `https://*.example.org`; paths, credentials, - * query strings, and fragments are rejected. + * Compile a list of exact origins and any-depth subdomain suffix wildcards. + * Wildcards use the form `https://*.example.org`; paths, credentials, query + * strings, fragments, and the suffix origin itself are rejected. * * @param allowedOrigins browser origins accepted by the API * @returns a predicate suitable for testing a request Origin value diff --git a/src/security/errors.ts b/src/security/errors.ts index c43477d..ab011de 100644 --- a/src/security/errors.ts +++ b/src/security/errors.ts @@ -1,4 +1,5 @@ import multer from 'multer' +import { FileNotFoundError } from '../utils/fileErrors.js' export type PublicError = { status: number @@ -7,7 +8,12 @@ export type PublicError = { /** Error whose constructor accepts only an approved client-facing message. */ export class InvalidRequestError extends Error { - constructor(public readonly publicMessage: 'Invalid CID') { + constructor( + public readonly publicMessage: + | 'Invalid CID' + | 'Invalid peer identifier or multiaddress' + | 'Peer identifier or multiaddress is required' + ) { super(publicMessage) } } @@ -24,6 +30,10 @@ export function getPublicError(error: unknown): PublicError { return { status: 400, body: { error: error.publicMessage } } } + if (error instanceof FileNotFoundError) { + return { status: 408, body: { error: 'File request timed out' } } + } + if (error instanceof multer.MulterError) { return { status: 400, body: { error: multerErrorMessage(error.code) } } } @@ -38,5 +48,8 @@ function multerErrorMessage(code: string): string { if (code === 'LIMIT_FILE_COUNT' || code === 'LIMIT_PART_COUNT') { return 'Upload file count limit exceeded' } + if (code === 'LIMIT_FIELD_COUNT') { + return 'Multipart fields are not allowed' + } return 'Invalid multipart upload' } diff --git a/src/utils/cid.ts b/src/utils/cid.ts new file mode 100644 index 0000000..7edbf9e --- /dev/null +++ b/src/utils/cid.ts @@ -0,0 +1,16 @@ +import { CID } from 'multiformats/cid' +import { InvalidRequestError } from '../security/errors.js' + +/** + * Parse a route CID without exposing parser internals to API clients. + * + * @param value CID string received at the HTTP boundary + * @returns parsed CID + */ +export function parseCid(value: string): CID { + try { + return CID.parse(value) + } catch { + throw new InvalidRequestError('Invalid CID') + } +} diff --git a/src/utils/downloadResponse.ts b/src/utils/downloadResponse.ts new file mode 100644 index 0000000..cc73fe3 --- /dev/null +++ b/src/utils/downloadResponse.ts @@ -0,0 +1,50 @@ +import type { NextFunction, Response } from 'express' +import type { Readable } from 'node:stream' + +export type DownloadMetadata = { + cid: string + fileSize: bigint +} + +/** + * Pipe a file stream while preserving a controlled JSON error response before + * the first byte and terminating an incomplete binary response afterwards. + * + * @param stream IPFS content stream + * @param res Express response + * @param metadata CID and expected byte length + * @param next Express error callback for failures before streaming starts + * @param onLateError logger callback for failures after response bytes start + */ +export function sendDownloadStream( + stream: Readable, + res: Response, + metadata: DownloadMetadata, + next: NextFunction, + onLateError: (error: Error) => void +): void { + const setDownloadHeaders = (): void => { + if (res.headersSent) { + return + } + + res.set('Content-Type', 'application/octet-stream') + res.set('Content-Length', metadata.fileSize.toString()) + res.set('Content-Disposition', `attachment; filename="${metadata.cid}"`) + res.set('X-Content-Type-Options', 'nosniff') + } + + stream.once('data', setDownloadHeaders) + stream.once('end', setDownloadHeaders) + stream.once('error', (error: Error) => { + stream.unpipe(res) + if (res.headersSent) { + onLateError(error) + res.destroy(error) + return + } + next(error) + }) + + stream.pipe(res) +} diff --git a/src/utils/file.ts b/src/utils/file.ts index a0f72c2..31770c8 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -3,7 +3,7 @@ import { Readable } from 'node:stream' import { clearTimeout } from 'node:timers' import { config } from '../config.js' import { ifs } from '../helia.js' -import { pino } from './logger.js' +import { FileNotFoundError } from './fileErrors.js' /** * Return file statistics by CID. @@ -19,11 +19,12 @@ export async function getFileStats(cid: CID) { const stats = await ifs.stat(cid, { signal: abortController.signal }) return stats - } catch (err) { - pino.logger.error(err) - clearTimeout(timeout) - + } catch { throw new FileNotFoundError('Cannot find requested CID. Request timed out.') + } finally { + if (timeout !== undefined) { + clearTimeout(timeout) + } } } @@ -54,13 +55,12 @@ export function downloadFile(cid: CID) { stream.on('end', () => { clearTimeout(abortTimer) }) + stream.on('error', () => { + clearTimeout(abortTimer) + }) + stream.on('close', () => { + clearTimeout(abortTimer) + }) return stream } - -export class FileNotFoundError extends Error { - constructor(message: string) { - super(message) - this.name = 'FileNotFoundError' - } -} diff --git a/src/utils/fileErrors.ts b/src/utils/fileErrors.ts new file mode 100644 index 0000000..ec79c84 --- /dev/null +++ b/src/utils/fileErrors.ts @@ -0,0 +1,7 @@ +/** Error raised when an IPFS file cannot be retrieved within the configured timeout. */ +export class FileNotFoundError extends Error { + constructor(message: string) { + super(message) + this.name = 'FileNotFoundError' + } +} diff --git a/test/downloadResponse.test.ts b/test/downloadResponse.test.ts new file mode 100644 index 0000000..6ed9f84 --- /dev/null +++ b/test/downloadResponse.test.ts @@ -0,0 +1,95 @@ +import assert from 'node:assert/strict' +import type { AddressInfo } from 'node:net' +import { Readable } from 'node:stream' +import { describe, it } from 'node:test' +import express, { type Express } from 'express' +import { getPublicError } from '../src/security/errors.js' +import { sendDownloadStream } from '../src/utils/downloadResponse.js' +import { FileNotFoundError } from '../src/utils/fileErrors.js' + +describe('download response streaming', () => { + it('keeps an early stream error as a controlled JSON response', async () => { + const app = express() + app.get('/file', (req, res, next) => { + void req + const stream = new Readable({ + read() { + this.destroy(new FileNotFoundError('/private/blockstore/path')) + } + }) + + sendDownloadStream(stream, res, { cid: 'bafytest', fileSize: 1n }, next, () => undefined) + }) + app.use( + (err: unknown, req: express.Request, res: express.Response, _next: express.NextFunction) => { + void req + void _next + const publicError = getPublicError(err) + res.status(publicError.status).send(publicError.body) + } + ) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/file`) + + assert.equal(response.status, 408) + assert.match(response.headers.get('content-type') ?? '', /^application\/json/) + assert.equal(response.headers.get('content-disposition'), null) + assert.deepEqual(await response.json(), { error: 'File request timed out' }) + } finally { + await server.close() + } + }) + + it('terminates an incomplete binary response after streaming starts', async () => { + const app = express() + let lateErrors = 0 + app.get('/file', (req, res, next) => { + void req + let emitted = false + const stream = new Readable({ + read() { + if (emitted) { + return + } + emitted = true + this.push(Buffer.from('a')) + queueMicrotask(() => this.destroy(new Error('late failure'))) + } + }) + + sendDownloadStream(stream, res, { cid: 'bafytest', fileSize: 2n }, next, () => { + lateErrors += 1 + }) + }) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/file`) + + assert.equal(response.status, 200) + assert.equal(response.headers.get('content-type'), 'application/octet-stream') + assert.equal(response.headers.get('content-disposition'), 'attachment; filename="bafytest"') + await assert.rejects(response.arrayBuffer()) + assert.equal(lateErrors, 1) + } finally { + await server.close() + } + }) +}) + +async function startServer(app: Express): Promise<{ url: string; close: () => Promise }> { + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + + return { + url: `http://127.0.0.1:${address.port}`, + close: () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + } +} diff --git a/test/security.test.ts b/test/security.test.ts index 6613322..d9dc680 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -12,6 +12,7 @@ import { getPublicError, InvalidRequestError } from '../src/security/errors.js' import { createRateLimiter } from '../src/security/rateLimit.js' import { parseTrustProxy } from '../src/security/trustProxy.js' import { createMultipartLimits } from '../src/security/uploadLimits.js' +import { FileNotFoundError } from '../src/utils/fileErrors.js' describe('CORS origin policy', () => { const matches = createOriginMatcher([ @@ -105,6 +106,23 @@ describe('public error mapping', () => { body: { error: 'Invalid CID' } }) }) + + it('preserves the public timeout response without exposing internal details', () => { + assert.deepEqual(getPublicError(new FileNotFoundError('/private/path was not found')), { + status: 408, + body: { error: 'File request timed out' } + }) + }) + + it('maps peer input failures to controlled validation responses', () => { + assert.deepEqual( + getPublicError(new InvalidRequestError('Invalid peer identifier or multiaddress')), + { + status: 400, + body: { error: 'Invalid peer identifier or multiaddress' } + } + ) + }) }) describe('streaming multipart limits', () => { @@ -120,8 +138,10 @@ describe('streaming multipart limits', () => { app.post('/upload', upload, (req, res) => res.send({ ok: true })) app.use( (err: unknown, req: express.Request, res: express.Response, _next: express.NextFunction) => { + void req void _next - res.status(400).send({ error: 'Rejected' }) + const publicError = getPublicError(err) + res.status(publicError.status).send(publicError.body) } ) @@ -136,6 +156,13 @@ describe('streaming multipart limits', () => { assert.equal((await sendFiles(serverUrl, 2)).status, 200) assert.equal((await sendFiles(serverUrl, 3)).status, 400) }) + + it('rejects text fields with a dedicated public response', async () => { + const response = await sendFiles(serverUrl, 1, true) + + assert.equal(response.status, 400) + assert.deepEqual(await response.json(), { error: 'Multipart fields are not allowed' }) + }) }) describe('administrative API key', () => { @@ -179,14 +206,20 @@ describe('route access policy', () => { before(async () => { const file = Router().get('/test', (req, res) => res.send({ public: true })) + const publicNodeRouter = Router().get('/health', (req, res) => res.send({ public: true })) const node = Router() - .get('/health', (req, res) => res.send({ public: true })) .get('/info', (req, res) => res.send({ admin: true })) + .get('/future', (req, res) => res.send({ admin: true })) const helia = Router().get('/test', (req, res) => res.send({ admin: true })) const libp2p = Router().get('/test', (req, res) => res.send({ admin: true })) const debug = Router().get('/test', (req, res) => res.send({ admin: true })) - mountApiRoutes(app, { file, node, helia, libp2p, debug }, createApiKeyAuth(key), false) + mountApiRoutes( + app, + { file, publicNodeRouter, node, helia, libp2p, debug }, + createApiKeyAuth(key), + false + ) const server = await startServer(app) serverUrl = server.url @@ -201,7 +234,12 @@ describe('route access policy', () => { }) it('protects node information, Helia, and libp2p routes', async () => { - for (const path of ['/api/node/info', '/api/helia/test', '/api/libp2p/test']) { + for (const path of [ + '/api/node/info', + '/api/node/future', + '/api/helia/test', + '/api/libp2p/test' + ]) { assert.equal((await fetch(`${serverUrl}${path}`)).status, 401) assert.equal( (await fetch(`${serverUrl}${path}`, { headers: { 'x-api-key': key } })).status, @@ -222,6 +260,7 @@ describe('route access policy', () => { debugApp, { file: emptyRouter, + publicNodeRouter: emptyRouter, node: emptyRouter, helia: emptyRouter, libp2p: emptyRouter, @@ -290,10 +329,17 @@ async function startServer(app: Express): Promise<{ url: string; close: () => Pr } } -async function sendFiles(serverUrl: string, count: number): Promise { +async function sendFiles( + serverUrl: string, + count: number, + includeTextField = false +): Promise { const body = new FormData() for (let index = 0; index < count; index += 1) { body.append('files', new Blob([String(index)]), `${index}.txt`) } + if (includeTextField) { + body.append('description', 'not accepted') + } return fetch(`${serverUrl}/upload`, { method: 'POST', body }) } From a1470a6e28dc000a346ef006fbe7e1d6824165a3 Mon Sep 17 00:00:00 2001 From: adamantmm Date: Sat, 22 Aug 2026 16:17:25 +0300 Subject: [PATCH 100/167] refactor: update logging utility and middleware integration - Replaced pino logger with a new logger instance in index.ts and errorHandler.ts. - Updated middleware to use httpLogger for request logging. - Simplified multer storage configuration in multer.ts. - Added openStores function to create store directories if missing in store.ts. - Enhanced logger configuration in logger.ts for better transport handling. - Improved UnixfsMulterStorage to sanitize filenames and handle file uploads more effectively. - Added utility functions for filesystem size reporting in utils.ts. - Updated TypeScript configuration to target ES2024. - Created CI workflow for automated testing and linting. - Added Prettier ignore file to exclude certain directories from formatting. - Introduced test configuration file for isolated testing. - Set up ESLint configuration for consistent code style. - Implemented IPFS node creation logic in ipfs-node.ts. - Developed comprehensive tests for CID compatibility and configuration validation. - Added tests for disk usage and file exchange between peers. --- .eslintignore | 1 - .eslintrc.json | 3 - .github/workflows/ci.yml | 46 + .github/workflows/security.yml | 4 +- .nvmrc | 2 +- .prettierignore | 13 + README.md | 62 +- config.default.json5 | 1 - config.test.json5 | 28 + eslint.config.js | 33 + package-lock.json | 19359 ++++++++---------------- package.json | 80 +- scripts/audit-production.mjs | 41 +- src/api/debug.ts | 10 +- src/api/file.ts | 16 +- src/api/helia.ts | 27 +- src/api/libp2p.ts | 38 +- src/config.ts | 240 +- src/disk-usage.cron.ts | 10 +- src/helia.ts | 73 +- src/index.ts | 12 +- src/ipfs-node.ts | 110 + src/middleware/errorHandler.ts | 4 +- src/multer.ts | 6 +- src/store.ts | 12 + src/utils/logger.ts | 23 +- src/utils/unixfs-multer.storage.ts | 29 +- src/utils/utils.ts | 73 +- test/cidCompatibility.test.ts | 98 + test/config.test.ts | 153 + test/diskUsage.test.ts | 77 + test/fixtures.ts | 96 + test/integration/peerExchange.test.ts | 166 + tsconfig.json | 4 +- tsconfig.test.json | 2 +- 35 files changed, 8066 insertions(+), 12886 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json create mode 100644 .github/workflows/ci.yml create mode 100644 .prettierignore create mode 100644 config.test.json5 create mode 100644 eslint.config.js create mode 100644 src/ipfs-node.ts create mode 100644 test/cidCompatibility.test.ts create mode 100644 test/config.test.ts create mode 100644 test/diskUsage.test.ts create mode 100644 test/fixtures.ts create mode 100644 test/integration/peerExchange.test.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 8b13789..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ - diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 25e03d0..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["./node_modules/gts/"] -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c3b28a7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +permissions: + contents: read + +on: + push: + branches: [master, dev] + pull_request: + branches: [master, dev] + +jobs: + build: + name: Build and test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + + # Install scripts must run here: Helia pulls @libp2p/webrtc, whose + # node-datachannel binary is required to import the module at all. + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Check formatting + run: npm run format + + - name: Build + run: npm run build + + - name: Build tests + run: npm run test:build + + - name: Unit tests + run: npm run test:unit + + - name: Integration tests + run: npm run test:integration diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 0e132b2..e25e69c 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -60,9 +60,11 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version-file: .nvmrc cache: 'npm' + # Install scripts are skipped on purpose: the audit only needs the + # dependency tree, not the native WebRTC binary Helia pulls in. - name: Install dependencies run: npm ci --ignore-scripts diff --git a/.nvmrc b/.nvmrc index e6a1787..a45fd52 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.* +24 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e880d95 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,13 @@ +dist +dist-test +node_modules +package-lock.json +npm-audit-report.json + +# Semgrep rule definitions are formatted for readability of the pattern blocks +.semgrep + +# Scratch worktrees and agent working directories +.claude +.ai-ignored +.ai-tasks diff --git a/README.md b/README.md index 0546cd4..16e7d46 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The application is not a Kubo wrapper. It is a Node.js service with an Express R ## Requirements -- Node.js 20 or later +- Node.js 24 LTS. The repository ships an `.nvmrc`, so `nvm use` selects it - A TLS-terminating reverse proxy for every public deployment - A unique administrative API key for operator endpoints @@ -15,11 +15,17 @@ The application is not a Kubo wrapper. It is a Node.js service with an Express R ```bash git clone https://github.com/Adamant-im/ipfs-node.git cd ipfs-node +nvm use npm ci npm run build node dist/index.js ``` +`npm ci` must run install scripts. Helia depends on `@libp2p/webrtc`, whose +`node-datachannel` native module downloads a prebuilt binary from GitHub +releases; installing with `--ignore-scripts` produces a tree that fails at +startup. See [Dependency notes](#dependency-notes). + The process can also be managed with PM2: ```bash @@ -29,7 +35,11 @@ pm2 start dist/index.js --name="IPFS node" ## Configuration -Copy `config.default.json5` to `config.json5` and replace all deployment-specific values. The security-relevant fields are shown below: +Copy `config.default.json5` to `config.json5` and replace all deployment-specific values. + +The config file is selected by the `IPFS_NODE_CONFIG` environment variable, or by the first CLI argument when the variable is not set. Both select `config..json5`; with neither, `config.json5` is used. `node dist/index.js test1` and `IPFS_NODE_CONFIG=test1 node dist/index.js` are equivalent. + +The whole configuration is validated at startup: a missing file, invalid JSON5, or a field with the wrong type aborts the process with a message naming the offending field. The security-relevant fields are shown below: ```jsonc { @@ -60,6 +70,7 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a ### Configuration migration +- Remove `autoPeeringPeriod`; no scheduled auto-peering exists, and `GET /api/debug/autopeering` performs it on request - Replace `cors.origin` or `cors.originRegexps` with `cors.allowedOrigins` - Use exact origins such as `https://adm.im` or any-depth subdomain suffix wildcards such as `https://*.adamant.im` - Set `adminApiKey` before using any administrative API @@ -79,7 +90,9 @@ Invalid CORS, proxy, API-key, or rate-limit configuration stops the process inst | Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | | Authenticated user | None | The service has no end-user identity or session layer | -Administrative coverage includes pin operations, provider queries, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. +Administrative coverage includes pin operations, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. + +`GET /api/helia/routing/findProviders/:cid` no longer exists. Provider lookup requires content routing, and this deployment intentionally runs no DHT — see [Network topology](#network-topology). ### Public upload decision @@ -111,6 +124,20 @@ Application limiters use in-memory counters per process. The TLS proxy must also Keeping `trustProxy` disabled behind a proxy makes every client share the proxy's IP identity and therefore the same application rate-limit bucket. The process logs a startup warning for this configuration. Do not enable a broader trust rule merely to suppress the warning; configure only the known proxy addresses or a verified fixed hop count. +## Network topology + +The node forms a private mesh with the peers listed in `nodes` and `peerDiscovery.bootstrap`. libp2p is configured explicitly and the Helia defaults are not merged in, so the node runs: + +- TCP transport only, with Noise encryption and Yamux stream multiplexing +- bootstrap peer discovery, restricted to the configured multiaddrs +- the `identify` and `ping` services only + +There is no DHT, no mDNS discovery, no circuit relay, no NAT traversal, and no HTTP gateway routing. Blocks are exchanged with known peers over bitswap, so block requests never leave the configured peer set and no CID is disclosed to a public gateway. + +The node is composed from `createHeliaLight`, `withLibp2pLight`, and `withBitswap` rather than `createHelia`, because `createHelia` merges its default libp2p configuration into whatever is passed in. Keeping it would silently add mDNS, the public IPFS bootstrap list, kad-DHT, AutoNAT, AutoTLS, UPnP, circuit relay, and WebRTC/WebSocket transports. + +The libp2p private key is stored in the datastore under `/pkcs8/self`, so a node keeps its peer identity across restarts as long as its `storeFolder` is preserved. Upgrading from the previous Helia 4 stack preserves both the peer identity and the existing blockstore contents; no store migration is required. + ### TLS boundary The Node.js process serves HTTP and does not terminate TLS. Bind it to a private interface or firewall it so clients can reach it only through a correctly configured HTTPS reverse proxy. The proxy must replace untrusted forwarding headers and forward requests to the configured `serverPort`. @@ -126,10 +153,32 @@ npm run security:audit npm run security:semgrep ``` -The audit fails on every unaccepted high or critical production advisory. One exact advisory, `GHSA-32mq-hpph-xfvr`, is temporarily accepted until [#21](https://github.com/Adamant-im/ipfs-node/issues/21) upgrades Helia: Helia 4 installs the affected Kademlia DHT package, but `src/helia.ts` replaces the default service map and does not instantiate the vulnerable DHT service. The audit script validates the exact package and advisory relationship so unrelated future findings still fail. +The audit fails on every high or critical production advisory. There are no accepted exceptions: the Helia 4 Kademlia DHT advisory `GHSA-32mq-hpph-xfvr`, previously tolerated until the runtime upgrade, is resolved by the Helia 7 dependency set. Use `npm run security:audit:raw` to inspect the unfiltered npm result. +## Dependency notes + +`helia` depends on `@helia/libp2p`, which depends on `@libp2p/webrtc` even though WebRTC is never configured here. That pulls two things into the runtime tree: + +- `node-datachannel`, a native module whose prebuilt binary is downloaded from GitHub releases during `npm install`. An installer that reaches the npm registry but not GitHub releases produces a tree that fails at startup, so `--ignore-scripts` is suitable only for auditing, not for running or testing +- `react-native-webrtc`, and through it `react-native` and its Metro bundler + +`npm run security:audit` scopes the audit with `--omit=dev --omit=peer` and reports no findings. Running a bare `npm audit --omit=dev` additionally surfaces advisories against Metro and its `image-size` dependency; Metro is a React Native build tool that this service never loads. Re-check these when Helia is upgraded. + +## Development + +```bash +npm run dev +npm run lint +npm run format +npm test +``` + +`npm test` compiles `src` and `test` to `dist-test` and then runs the unit and integration suites with Node's built-in test runner against `config.test.json5`, which has no bootstrap peers and listens on loopback with an OS-assigned port. + +The unit suites cover the HTTP security boundary, configuration validation, filename sanitization, disk measurement, and the file CIDs issued before the Helia migration. The integration suite starts two isolated nodes with temporary stores, transfers a file between them over bitswap, and verifies that a peer identity survives a restart. + ## API usage ### Upload files @@ -197,10 +246,11 @@ curl --fail-with-body \ ## Validation ```bash -npm ci --ignore-scripts +npm ci npm run build -npm test npm run lint +npm run format +npm test npm run security:audit npm run security:semgrep git diff --check diff --git a/config.default.json5 b/config.default.json5 index bbb85c4..ad8a0c7 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -24,7 +24,6 @@ listen: ['/ip4/0.0.0.0/tcp/4001'] }, serverPort: 4000, - autoPeeringPeriod: '*/10 * * * * *', diskUsageScanPeriod: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. maxFileCount: 10, diff --git a/config.test.json5 b/config.test.json5 new file mode 100644 index 0000000..aecf76f --- /dev/null +++ b/config.test.json5 @@ -0,0 +1,28 @@ +// Configuration used by `npm test`. Selected through IPFS_NODE_CONFIG=test. +// It must not reach the network: no bootstrap peers and no known nodes. +{ + nodes: [], + storeFolder: '.adm-ipfs-test', + logLevel: 'silent', + peerDiscovery: { + bootstrap: [], + // Port 0 lets the OS pick a free port so tests never clash with a running node + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + serverPort: 4000, + diskUsageScanPeriod: '*/30 * * * * *', + uploadLimitSizeBytes: 268435456, // 256 Mb. + maxFileCount: 10, + findFileTimeout: 20000, + cors: { + allowedOrigins: ['http://localhost:8080'] + }, + trustProxy: false, + rateLimits: { + upload: { windowMs: 900000, limit: 10 }, + pin: { windowMs: 900000, limit: 10 }, + read: { windowMs: 60000, limit: 100 } + }, + adminApiKey: '', + enableDebugApi: false +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..962a260 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import tseslint from 'typescript-eslint' +import prettier from 'eslint-config-prettier' + +export default tseslint.config( + { + // `.claude/` holds scratch worktrees with their own checked-out copies of + // this repository; linting them would report the same file many times + ignores: ['dist/', 'dist-test/', 'node_modules/', '.claude/', '.ai-ignored/', '.ai-tasks/'] + }, + js.configs.recommended, + tseslint.configs.recommended, + { + languageOptions: { + globals: { + ...globals.node + } + } + }, + { + files: ['**/*.ts'], + rules: { + // Express error handlers must declare `next` to be recognised as such, + // and route handlers frequently ignore `req` + '@typescript-eslint/no-unused-vars': [ + 'error', + { args: 'none', caughtErrors: 'none', varsIgnorePattern: '^_' } + ] + } + }, + prettier +) diff --git a/package-lock.json b/package-lock.json index 2b9f374..b30e071 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,73 +9,90 @@ "version": "0.0.1", "license": "GPLv3", "dependencies": { - "@chainsafe/libp2p-noise": "^15.1.0", - "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/unixfs": "^3.0.7", - "@libp2p/bootstrap": "^10.1.4", - "@libp2p/identify": "^2.1.5", - "@libp2p/interface": "^1.7.0", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/ping": "^1.1.6", - "@libp2p/tcp": "^9.1.4", - "@multiformats/multiaddr": "^12.5.1", - "blockstore-fs": "^1.1.11", - "cors": "^2.8.5", - "cron": "^3.1.7", - "datastore-fs": "^9.1.9", + "@chainsafe/libp2p-noise": "^17.0.0", + "@chainsafe/libp2p-yamux": "^8.0.1", + "@helia/bitswap": "^4.0.12", + "@helia/libp2p": "^2.1.1", + "@helia/unixfs": "^8.0.5", + "@libp2p/bootstrap": "^12.0.29", + "@libp2p/identify": "^4.1.12", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/ping": "^3.1.11", + "@libp2p/tcp": "^11.0.26", + "@multiformats/multiaddr": "^13.0.3", + "blockstore-fs": "^4.0.1", + "cors": "^2.8.6", + "cron": "^4.4.0", + "datastore-fs": "^12.0.1", "express": "^4.22.2", "express-rate-limit": "^8.6.2", - "go-get-folder-size": "^0.5.5", - "helia": "^4.2.5", + "helia": "^7.1.8", + "interface-blockstore": "^7.0.1", + "interface-datastore": "^10.0.1", "json5": "^2.2.3", "multer": "^2.2.0", - "multiformats": "^13.2.2", - "pino": "^9.3.2", - "pino-http": "^10.2.0" + "multiformats": "^14.0.5", + "pino": "^10.3.1", + "pino-http": "^11.0.0", + "pino-pretty": "^13.1.3" }, "devDependencies": { - "@commitlint/cli": "^19.4.0", - "@commitlint/config-conventional": "^19.2.2", + "@commitlint/cli": "^21.2.2", + "@commitlint/config-conventional": "^21.0.0", + "@eslint/js": "^10.0.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^2.2.0", - "@types/node": "^20.14.8", - "@typescript-eslint/eslint-plugin": "^8.59.1", - "eslint": "^8.57.0", - "gts": "^5.3.1", - "husky": "^9.1.4", - "lint-staged": "^15.2.8", - "nodemon": "^3.1.4", - "pino-pretty": "^11.2.2", - "prettier": "^3.3.3", - "rimraf": "^6.0.1", - "typescript": "^5.5.4" + "@types/node": "^24.13.3", + "eslint": "^10.9.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.11.0", + "husky": "^9.1.7", + "lint-staged": "^17.3.0", + "nodemon": "^3.1.14", + "prettier": "^3.9.6", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.67.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=24.0.0" + } + }, + "node_modules/@achingbrain/http-parser-js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@achingbrain/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-nPuMf2zVzBAGRigH/1jFpb/6HmJsps+15f4BPlGDp3vsjYB2ZgruAErUpKpcFiVRz3DHLXcGNmuwmqZx/sVI7A==", + "license": "MIT", + "dependencies": { + "uint8arrays": "^5.1.0" } }, "node_modules/@achingbrain/nat-port-mapper": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@achingbrain/nat-port-mapper/-/nat-port-mapper-1.0.13.tgz", - "integrity": "sha512-B5GL6ILDek72OjoEyFGEuuNYaEOYxO06Ulhcaf/5iQ4EO8uaZWS+OkolYST7L+ecJrkjfaSNmSAsWRRuh+1Z5A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@achingbrain/nat-port-mapper/-/nat-port-mapper-4.0.5.tgz", + "integrity": "sha512-YAA4MW6jO6W7pmJaFzQ0AOLpu8iQClUkdT2HbfKLmtFjrpoZugnFj9wH8EONV9LxnIW+0W1J98ri+oApKyAKLQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@achingbrain/ssdp": "^4.0.1", - "@libp2p/logger": "^4.0.1", - "default-gateway": "^7.2.2", + "@achingbrain/ssdp": "^4.1.0", + "@chainsafe/is-ip": "^2.0.2", + "@libp2p/logger": "^6.0.5", + "abort-error": "^1.0.0", "err-code": "^3.0.1", - "it-first": "^3.0.1", + "netmask": "^2.0.2", "p-defer": "^4.0.0", - "p-timeout": "^6.1.1", + "race-signal": "^2.0.0", "xml2js": "^0.6.0" } }, "node_modules/@achingbrain/ssdp": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@achingbrain/ssdp/-/ssdp-4.0.6.tgz", - "integrity": "sha512-Y4JE2L9150i50V6lg/Y8+ilhxRpUZKKv+PKo68Aj7MjPfaUAar6ZHilF9h4/Zb3q0fqGMXNc9o11cQLNI8J8bA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@achingbrain/ssdp/-/ssdp-4.2.4.tgz", + "integrity": "sha512-1dZIV7dwYJRS1sTA0qIDzsMdwZAnPa7DGb2YuPqMq4PjEjvzBBuz2WIsXnrkRFCNY00JuqLiMby9GecnGsOgaQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "event-iterator": "^2.0.0", + "abort-error": "^1.0.0", "freeport-promise": "^2.0.0", "merge-options": "^3.0.4", "xml2js": "^0.6.2" @@ -84,7 +101,8 @@ "node_modules/@assemblyscript/loader": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", - "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" + "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==", + "license": "Apache-2.0" }, "node_modules/@babel/code-frame": { "version": "7.29.7", @@ -145,6 +163,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "peer": true, "bin": { "semver": "bin/semver.js" @@ -167,31 +186,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "peer": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", @@ -213,95 +207,12 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "peer": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", - "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", - "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "peer": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -312,19 +223,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", @@ -357,88 +255,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", - "peer": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", - "peer": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -468,20 +284,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", - "peer": true, - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", @@ -512,4537 +314,4105 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", - "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", - "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", - "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", - "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", - "peer": true, + "node_modules/@chainsafe/as-chacha20poly1305": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz", + "integrity": "sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew==", + "license": "Apache-2.0" + }, + "node_modules/@chainsafe/as-sha256": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-1.2.5.tgz", + "integrity": "sha512-8LzmWxBC/2O5BgbP+aRom/FaDJMrM04VZMlVIivYTc6yNQKYrONmIv27m3q/1VBgxrfrNNqmibitb2hE2tQzIw==", + "license": "Apache-2.0" + }, + "node_modules/@chainsafe/is-ip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", + "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", + "license": "MIT" + }, + "node_modules/@chainsafe/libp2p-noise": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-17.0.0.tgz", + "integrity": "sha512-vwrmY2Y+L1xYhIDiEpl61KHxwrLCZoXzTpwhyk34u+3+6zCAZPL3GxH3i2cs+u5IYNoyLptORdH17RKFXy7upA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@chainsafe/as-chacha20poly1305": "^0.1.0", + "@chainsafe/as-sha256": "^1.2.0", + "@libp2p/crypto": "^5.1.9", + "@libp2p/interface": "^3.0.0", + "@libp2p/peer-id": "^6.0.0", + "@libp2p/utils": "^7.0.0", + "@noble/ciphers": "^2.0.1", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1", + "protons-runtime": "^5.6.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.1.0", + "wherearewe": "^2.0.1" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", - "peer": true, + "node_modules/@chainsafe/libp2p-yamux": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-yamux/-/libp2p-yamux-8.0.1.tgz", + "integrity": "sha512-pJsqmUg1cZRJZn/luAtQaq0uLcVfExo51Rg7iRtAEceNYtsKUi/exfegnvTBzTnF1CGmTzVEV3MCLsRhqiNyoA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/interface": "^3.0.0", + "@libp2p/utils": "^7.0.0", + "race-signal": "^2.0.0", + "uint8arraylist": "^2.4.8" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "peer": true, + "node_modules/@chainsafe/netmask": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", + "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@chainsafe/is-ip": "^2.0.1" } }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.7.tgz", - "integrity": "sha512-CcmFwUJ3tKhLjPdt4NP+SHMshebytF8ZTYOv5ZDpkzq2sin80Wb5vJrGt8fhPrORQCfoSa0LAxC/DW+GAC5+Hw==", - "peer": true, + "node_modules/@commitlint/cli": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", + "integrity": "sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-default-from": "^7.24.7" + "@commitlint/config-conventional": "^21.2.2", + "@commitlint/format": "^21.2.2", + "@commitlint/lint": "^21.2.2", + "@commitlint/load": "^21.2.2", + "@commitlint/read": "^21.2.1", + "@commitlint/types": "^21.2.0", + "tinyexec": "^1.0.0", + "yargs": "^18.0.0" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "commitlint": "cli.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", - "peer": true, + "node_modules/@commitlint/config-conventional": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.2.tgz", + "integrity": "sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@commitlint/types": "^21.2.0", + "conventional-changelog-conventionalcommits": "^10.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", - "peer": true, + "node_modules/@commitlint/config-validator": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz", + "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@commitlint/types": "^21.2.0", + "ajv": "^8.11.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", - "peer": true, + "node_modules/@commitlint/ensure": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz", + "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, + "node_modules/@commitlint/execute-rule": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", - "peer": true, + "node_modules/@commitlint/format": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.2.tgz", + "integrity": "sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@commitlint/types": "^21.2.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", - "peer": true, + "node_modules/@commitlint/is-ignored": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.2.tgz", + "integrity": "sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@commitlint/types": "^21.2.0", + "semver": "^7.6.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "peer": true, - "engines": { - "node": ">=6.9.0" + "node_modules/@commitlint/lint": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.2.tgz", + "integrity": "sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^21.2.2", + "@commitlint/parse": "^21.2.2", + "@commitlint/rules": "^21.2.2", + "@commitlint/types": "^21.2.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "peer": true, + "node_modules/@commitlint/load": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.2.tgz", + "integrity": "sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/config-validator": "^21.2.0", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.2.2", + "@commitlint/types": "^21.2.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/@commitlint/message": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.2.0.tgz", + "integrity": "sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "peer": true, + "node_modules/@commitlint/parse": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.2.tgz", + "integrity": "sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@commitlint/types": "^21.2.0", + "conventional-changelog-angular": "^9.0.0", + "conventional-commits-parser": "^7.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "peer": true, + "node_modules/@commitlint/read": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", + "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/top-level": "^21.2.0", + "@commitlint/types": "^21.2.0", + "@conventional-changelog/git-client": "^3.0.0", + "tinyexec": "^1.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.7.tgz", - "integrity": "sha512-bTPz4/635WQ9WhwsyPdxUJDVpsi/X9BMmy/8Rf/UAlOO4jSql4CxUCjWI5PiM+jG+c4LVPTScoTw80geFj9+Bw==", - "peer": true, + "node_modules/@commitlint/resolve-extends": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.2.tgz", + "integrity": "sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@commitlint/config-validator": "^21.2.0", + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "peer": true, + "node_modules/@commitlint/rules": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.2.tgz", + "integrity": "sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@commitlint/ensure": "^21.2.0", + "@commitlint/message": "^21.2.0", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.2.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", - "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", - "peer": true, + "node_modules/@commitlint/to-lines": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/top-level": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.2.0.tgz", + "integrity": "sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "escalade": "^3.2.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", - "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", - "peer": true, + "node_modules/@commitlint/types": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz", + "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "conventional-commits-parser": "^7.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", - "peer": true, + "node_modules/@conventional-changelog/git-client": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.2.tgz", + "integrity": "sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "semver": "^7.5.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=22" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.2" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/@conventional-changelog/template": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.4.0.tgz", + "integrity": "sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "peer": true, + "node_modules/@dnsquery/dns-packet": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", + "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@leichtgewicht/ip-codec": "^2.0.4", + "utf8-codec": "^1.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "peer": true, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "peer": true, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "peer": true, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@eslint/core": "^1.2.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "peer": true, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@types/json-schema": "^7.0.15" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", - "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", - "peer": true, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "peer": true, + "node_modules/@helia/bitswap": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-4.0.14.tgz", + "integrity": "sha512-Fd9/KYi67PAqk98tw9uCIPTCjsLXaK+5Wsq8+z8zRn3dLfcYNtkj86vPN6IKrnL+A4FP3URz2q8CUrzYnHFp3Q==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@helia/interface": "^7.1.1", + "@helia/libp2p": "^2.1.3", + "@helia/utils": "^3.0.5", + "@libp2p/interface": "^3.2.3", + "@libp2p/logger": "^6.2.8", + "@libp2p/peer-collections": "^7.0.21", + "@libp2p/peer-id": "^6.0.10", + "@libp2p/utils": "^7.2.2", + "@multiformats/multiaddr": "^13.0.3", + "any-signal": "^4.2.0", + "interface-blockstore": "^7.0.1", + "interface-store": "^8.0.0", + "it-drain": "^3.0.12", + "it-length-prefixed": "^11.0.1", + "it-map": "^3.1.6", + "it-pushable": "^3.2.4", + "it-take": "^3.0.11", + "it-to-buffer": "^5.0.0", + "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.1.0", + "protons-runtime": "^7.0.0", + "race-event": "^1.6.1", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", - "peer": true, + "node_modules/@helia/bitswap/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", - "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", - "peer": true, + "node_modules/@helia/bitswap/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", - "peer": true, + "node_modules/@helia/bitswap/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/@helia/delegated-routing-client": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-client/-/delegated-routing-client-1.0.6.tgz", + "integrity": "sha512-UoAIy+qsro1Qde4l3hGbRXdxID1nY53m4Oig0VCrlafNFOScxYBy93H4/T2bwk/mBONMxHa40NbpCxlYLMZznw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@helia/delegated-routing-v1-http-api-client": "^9.0.0", + "@helia/interface": "^7.1.1", + "@libp2p/interface": "^3.2.3", + "it-first": "^3.0.11", + "it-map": "^3.1.6", + "multiformats": "^14.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", - "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", - "peer": true, + "node_modules/@helia/delegated-routing-client/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", - "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", - "peer": true, + "node_modules/@helia/delegated-routing-v1-http-api-client": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-9.0.2.tgz", + "integrity": "sha512-NVbX/8Uf+04g5gZkpqk6J6aFMCgMxzyQ2XBxW2gpYysGEuMSEESRyiYuWUrKysy9Oo08ST4tJJ19Vh/+4wjzOA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/interface": "^3.2.2", + "@libp2p/peer-id": "^6.0.9", + "@libp2p/utils": "^7.2.2", + "@multiformats/multiaddr": "^13.0.3", + "any-signal": "^4.2.0", + "browser-readablestream-to-it": "^2.0.12", + "it-first": "^3.0.11", + "it-map": "^3.1.6", + "it-ndjson": "^2.0.0", + "multiformats": "^14.0.0", + "race-signal": "^2.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@helia/delegated-routing-v1-http-api-client/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", - "peer": true, + "node_modules/@helia/fallback-router": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@helia/fallback-router/-/fallback-router-1.1.1.tgz", + "integrity": "sha512-dfkS7TzvCHOoiKafht7VIrXlESHLiWjbgsg3tEszK06UVG+C6sWhuSN+gD13VMHiyhPSKjAzupyoTFgglZ6FuA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" + "@helia/interface": "^7.1.1", + "@multiformats/uri-to-multiaddr": "^10.0.0", + "multiformats": "^14.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", - "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", - "peer": true, + "node_modules/@helia/fallback-router/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/traverse": "^7.25.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "peer": true, - "engines": { - "node": ">=4" + "node_modules/@helia/http": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@helia/http/-/http-4.0.6.tgz", + "integrity": "sha512-jtpV0BuhPEnIAkBv/QKpsh9I6f3MDhDzLChJsgCi/wtN2ztQls/Xc2n4Qipr92Ytfmq0R4hai6ragxhq5O87Cg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@helia/delegated-routing-client": "^1.0.6", + "@helia/fallback-router": "^1.1.1", + "@helia/interface": "^7.1.1", + "@helia/trustless-gateway-client": "^1.0.5" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", - "peer": true, + "node_modules/@helia/interface": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-7.1.1.tgz", + "integrity": "sha512-L3uLlfEY8MYUV01YdJfv6C35FRhVwRiNx36Yh+8qCpJuCs+GmTmaxVrAb4wXXRg2yIjMR+HAAIAcJIOV/x5n0A==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@ipshipyard/crypto": "^1.1.0", + "@ipshipyard/keychain": "^1.0.2", + "@libp2p/interface": "^3.2.3", + "@multiformats/dns": "^1.0.13", + "@multiformats/multiaddr": "^13.0.3", + "abort-error": "^1.0.2", + "birnam": "^1.0.0", + "interface-blockstore": "^7.0.1", + "interface-datastore": "^10.0.1", + "main-event": "^1.0.4", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0" + } + }, + "node_modules/@helia/libp2p": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@helia/libp2p/-/libp2p-2.1.3.tgz", + "integrity": "sha512-AqNqSv9N2YVWLhCdeRAGGc2OnxWFxBwLB9KT+WVCSBUjBbbLjKhPe4y+py6pK+LX6tXVqSY05mZM3X6DzFH04g==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/libp2p-noise": "^17.0.0", + "@chainsafe/libp2p-yamux": "^8.0.1", + "@helia/delegated-routing-client": "^1.0.6", + "@helia/delegated-routing-v1-http-api-client": "^9.0.0", + "@helia/interface": "^7.1.1", + "@ipshipyard/libp2p-auto-tls": "^2.0.1", + "@libp2p/autonat": "^3.0.21", + "@libp2p/bootstrap": "^12.0.24", + "@libp2p/circuit-relay-v2": "^4.2.6", + "@libp2p/config": "^1.1.32", + "@libp2p/dcutr": "^3.0.21", + "@libp2p/http": "^2.0.2", + "@libp2p/identify": "^4.1.7", + "@libp2p/interface": "^3.2.3", + "@libp2p/kad-dht": "^16.3.2", + "@libp2p/keychain": "^6.1.2", + "@libp2p/mdns": "^12.0.24", + "@libp2p/mplex": "^12.0.24", + "@libp2p/peer-id": "^6.0.10", + "@libp2p/ping": "^3.1.6", + "@libp2p/tcp": "^11.0.21", + "@libp2p/tls": "^3.1.3", + "@libp2p/upnp-nat": "^4.0.21", + "@libp2p/webrtc": "^6.0.24", + "@libp2p/websockets": "^10.1.14", + "@multiformats/multiaddr": "^13.0.3", + "it-foreach": "^2.1.7", + "it-map": "^3.1.6", + "libp2p": "^3.3.3", + "multiformats": "^14.0.0", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@helia/libp2p/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", - "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", - "peer": true, + "node_modules/@helia/trustless-gateway-client": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@helia/trustless-gateway-client/-/trustless-gateway-client-1.0.5.tgz", + "integrity": "sha512-7hmipIMOEK9x1pnCsA06gMxijDHn/0KjILQ6f9qy/Yfqg7aaUQfrC9OGpM59DTayGlo29QknoShsDY1LdrAEVg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@helia/interface": "^7.1.1", + "@helia/utils": "^3.0.5", + "@libp2p/utils": "^7.2.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@multiformats/multiaddr-to-uri": "^12.0.0", + "@multiformats/uri-to-multiaddr": "^10.0.0", + "abort-error": "^1.0.2", + "birnam": "^1.0.0", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@helia/trustless-gateway-client/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", - "peer": true, + "node_modules/@helia/trustless-gateway-client/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", - "peer": true, + "node_modules/@helia/unixfs": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-8.0.5.tgz", + "integrity": "sha512-fXloT3Wi045JyHEYBfyaulrDjEKzdFkdXSgxKRVlqd0aBaC6txAq4Io1ZAeoByNZ6sjI0wgo8loD1QA/QNQtsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@helia/interface": "^7.1.1", + "@ipld/dag-pb": "^4.1.7", + "@libp2p/interface": "^3.2.3", + "@libp2p/logger": "^6.2.8", + "@libp2p/utils": "^7.2.2", + "@multiformats/murmur3": "^2.2.5", + "interface-blockstore": "^7.0.1", + "ipfs-unixfs": "^13.0.0", + "ipfs-unixfs-exporter": "^16.0.2", + "ipfs-unixfs-importer": "^17.0.1", + "it-all": "^3.0.11", + "it-first": "^3.0.11", + "it-glob": "^3.0.6", + "it-last": "^3.0.11", + "it-pipe": "^3.0.1", + "it-to-buffer": "^5.0.0", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0", + "sparse-array": "^1.3.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", - "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", - "peer": true, + "node_modules/@helia/unixfs/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", - "peer": true, + "node_modules/@helia/utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-3.0.5.tgz", + "integrity": "sha512-vcJ8FltQ5vNHiULhx3WO/0nm4/ljWDpZdwIS9cwibF6hfDYna5w2IZXFMu1czI6wHt26fR+EtyUOCFIoS51xcA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@helia/interface": "^7.1.1", + "@libp2p/interface": "^3.2.3", + "@libp2p/utils": "^7.2.2", + "@multiformats/multiaddr": "^13.0.3", + "it-filter": "^3.1.6", + "it-to-buffer": "^5.0.0", + "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.1.0", + "race-signal": "^2.0.0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", - "peer": true, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@humanfs/types": "^0.15.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", - "peer": true, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.2.tgz", - "integrity": "sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/plugin-syntax-flow": "^7.24.7" - }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" - }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=12.22" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", - "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" - }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=18.18" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", - "peer": true, + "node_modules/@ipld/dag-cbor": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-10.0.2.tgz", + "integrity": "sha512-1eCD4YMTlkx5i36oQUmeAojLS/wu+XCd1Gs31BA0afk0TaVkhnyMofIAT/pi0wV3HS3/G7QSN/1NibhiksMyZQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "cborg": "^6.1.1", + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", - "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", - "peer": true, + "node_modules/@ipld/dag-json": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-11.0.1.tgz", + "integrity": "sha512-cMDD0yWkUruqhbLDwAWaV5q92hYKQDP2xP0n+yZp0oxsC68Pw+xFZNc9rj0v2n5m5m/Aa1A+Qlb2tY3NjQMsWg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "cborg": "^6.1.1", + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", - "peer": true, + "node_modules/@ipld/dag-pb": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.7.tgz", + "integrity": "sha512-/i/13trFihjWfDyXlylRwhuYjtzYjvOFw0vlRjYGnZuv7d7MOgA2lV/vRuL5RfeUajM03aZfFLdq4S7cTbbTRg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "multiformats": "^14.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", - "peer": true, + "node_modules/@ipshipyard/crypto": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ipshipyard/crypto/-/crypto-1.1.0.tgz", + "integrity": "sha512-xMnzqJFuDirmjdMlmHb9r/olj++oFv3Pf5utF49+MvNQqKjYZ2YHMiO8Y8xk8nh6E+xa4g9+r/hij0FpNVZusw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "abort-error": "^1.0.2", + "multiformats": "^14.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", - "peer": true, + "node_modules/@ipshipyard/crypto/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", - "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", - "peer": true, + "node_modules/@ipshipyard/crypto/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", - "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", - "license": "MIT", - "peer": true, + "node_modules/@ipshipyard/crypto/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", - "peer": true, + "node_modules/@ipshipyard/keychain": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ipshipyard/keychain/-/keychain-1.0.2.tgz", + "integrity": "sha512-otMCFOItScEX3VNy/gHF+CVd2QGPNo75uwZBoYUj2Rr8xnF5eq1nlbk6iEqH6cwqu2j/UuzFvtD2s2lmGo0mDA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@ipshipyard/crypto": "^1.1.0", + "abort-error": "^1.0.2", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.0", + "sanitize-filename": "^1.6.4", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", - "peer": true, + "node_modules/@ipshipyard/keychain/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", - "peer": true, + "node_modules/@ipshipyard/libp2p-auto-tls": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@ipshipyard/libp2p-auto-tls/-/libp2p-auto-tls-2.0.2.tgz", + "integrity": "sha512-jnGqf/eif9GpjH03PaenekRWRvlJgoVZ1JsAawW0dJxcIW8qHiSMIbVh49xOjUGTK8vUbN26bsO+Rustg0n72A==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@chainsafe/is-ip": "^2.0.2", + "@libp2p/crypto": "^5.0.9", + "@libp2p/http": "^2.0.0", + "@libp2p/interface": "^3.0.2", + "@libp2p/interface-internal": "^3.0.4", + "@libp2p/keychain": "^6.0.4", + "@libp2p/utils": "^7.0.4", + "@multiformats/multiaddr": "^13.0.1", + "@multiformats/multiaddr-matcher": "^3.0.1", + "@peculiar/x509": "^2.0.0", + "acme-client": "^5.4.0", + "any-signal": "^4.1.1", + "delay": "^7.0.0", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.2", + "reflect-metadata": "^0.2.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", - "peer": true, + "node_modules/@ipshipyard/libp2p-auto-tls/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", - "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", - "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", - "peer": true, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@libp2p/autonat": { + "version": "3.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-3.0.26.tgz", + "integrity": "sha512-gA1eJU4p7I6Dn8uvn6GsKSxpD5kbcxres3cht+LVG/Kga4UGX+yE/os0RT+4uNGyZGIfXqLxNUy8Hq1xQTJCmQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "any-signal": "^4.1.1", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", - "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", - "peer": true, + "node_modules/@libp2p/autonat/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", - "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", - "peer": true, + "node_modules/@libp2p/autonat/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.25.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", - "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", - "peer": true, + "node_modules/@libp2p/autonat/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", - "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", - "peer": true, + "node_modules/@libp2p/bootstrap": { + "version": "12.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-12.0.29.tgz", + "integrity": "sha512-HJqRyr75io10F/zaJCMGhrdOi7an4bJmgw12DLrBR/hPteeFAIZ4bXeiV+GNxw5vmRJoEpfUTVzIwPTsRJMViw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-id": "^6.0.14", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "main-event": "^1.0.1" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", - "peer": true, + "node_modules/@libp2p/circuit-relay-v2": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-4.2.11.tgz", + "integrity": "sha512-q47qWuJxmNocAX4oRAUWpp2d56aiymsih9ORCkBdQGUkBzynPyBsucvCX5bnF8tb80Ji5wBHl4jCa1DmrW1T1A==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/peer-record": "^9.0.15", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "any-signal": "^4.1.1", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "nanoid": "^6.0.0", + "progress-events": "^1.1.0", + "protons-runtime": "^7.0.0", + "retimeable-signal": "^1.0.1", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", - "peer": true, + "node_modules/@libp2p/circuit-relay-v2/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", - "peer": true, + "node_modules/@libp2p/circuit-relay-v2/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@libp2p/circuit-relay-v2/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", - "peer": true, + "node_modules/@libp2p/config": { + "version": "1.1.36", + "resolved": "https://registry.npmjs.org/@libp2p/config/-/config-1.1.36.tgz", + "integrity": "sha512-DWP4CRU/Datb8t8mvGF1LQtc45Z3kZBEzG1Ua+1CuLcm+unLkRr2UcNyb4AG8nSA9jdQQocO2sQHofQ/9LTmBg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/keychain": "^6.1.6", + "@libp2p/logger": "^6.2.12", + "interface-datastore": "^10.0.1" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", - "peer": true, + "node_modules/@libp2p/crypto": { + "version": "5.1.22", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.22.tgz", + "integrity": "sha512-yVk4BefSiEz044kjsiLSEoJTbhRoyamqx4kFI9LnX8/asr4wnqDWKTXcn6mSx/RVNEzmDO70l3vOvcMCl4aLCQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/interface": "^3.2.5", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1", + "multiformats": "^14.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", - "peer": true, + "node_modules/@libp2p/crypto/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", - "peer": true, + "node_modules/@libp2p/crypto/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", - "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", - "peer": true, + "node_modules/@libp2p/crypto/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", - "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", - "peer": true, + "node_modules/@libp2p/dcutr": { + "version": "3.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-3.0.26.tgz", + "integrity": "sha512-8knF7Ppx69mSgljdSz7xrYZHl3KQmJs4u04kMk1oCDlzq8ehxcKPdbYbUnDTlsBoKsFV7WSHQfW1y6xqTg4srw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-typescript": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "delay": "^7.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", - "peer": true, + "node_modules/@libp2p/dcutr/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", - "peer": true, + "node_modules/@libp2p/dcutr/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", - "peer": true, + "node_modules/@libp2p/dcutr/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", - "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", - "peer": true, + "node_modules/@libp2p/http": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/http/-/http-2.0.6.tgz", + "integrity": "sha512-z3RRdZP/+/kjYREY9MyZ5E90bKrGThQAw9sTZyv0ahxzUM+7I0/JPFLj98Wu0tgI9LStA6vtweVi0Ffwp0u9bA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@libp2p/http-fetch": "^4.0.0", + "@libp2p/http-peer-id-auth": "^2.0.0", + "@libp2p/http-utils": "^2.0.0", + "@libp2p/http-websocket": "^2.0.0", + "@libp2p/interface": "^3.2.0", + "@libp2p/interface-internal": "^3.1.0", + "@multiformats/multiaddr": "^13.0.3", + "cookie": "^1.1.1", + "undici": "^8.0.3" } }, - "node_modules/@babel/preset-env": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", - "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", - "peer": true, + "node_modules/@libp2p/http-fetch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/http-fetch/-/http-fetch-4.0.4.tgz", + "integrity": "sha512-MHROqwXP4KnRIZguzUIyrexRWuEF6InXKzUCFktergqgkkkkkt45ZiGx8wOa/1xbdUEZmiDoOITpHsNhU5GWBQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.0", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.0", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@achingbrain/http-parser-js": "^0.5.9", + "@libp2p/http-utils": "^2.0.0", + "@libp2p/interface": "^3.2.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@libp2p/http-fetch/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@babel/preset-flow": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.7.tgz", - "integrity": "sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==", - "peer": true, + "node_modules/@libp2p/http-peer-id-auth": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@libp2p/http-peer-id-auth/-/http-peer-id-auth-2.0.3.tgz", + "integrity": "sha512-lBzwbnqIJa4SnMssseEtbgdGHB+sH2CQr3HxhSwEhLRRpU1iCmkUR1L9X+FTH6A5Xo62y/JYnnhBO7CG3YTSdg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-transform-flow-strip-types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@libp2p/crypto": "^5.1.15", + "@libp2p/interface": "^3.2.0", + "@libp2p/peer-id": "^6.0.6", + "uint8-varint": "^3.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "peer": true, + "node_modules/@libp2p/http-peer-id-auth/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", - "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", - "peer": true, + "node_modules/@libp2p/http-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/http-utils/-/http-utils-2.0.6.tgz", + "integrity": "sha512-WIYT1luITS4AZl0/b3tB5c7Kw9MKHAX2Lumx83oKImBPI9lPpRNafOmVVVRvwfjPq77t5nVt4ha8y1UDEtRN6g==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@achingbrain/http-parser-js": "^0.5.9", + "@libp2p/interface": "^3.2.0", + "@libp2p/peer-id": "^6.0.6", + "@libp2p/utils": "^7.0.15", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-to-uri": "^12.0.0", + "@multiformats/uri-to-multiaddr": "^10.0.0", + "it-to-browser-readablestream": "^2.0.14", + "multiformats": "^14.0.0", + "race-event": "^1.6.1", + "readable-stream": "^4.7.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/http-utils/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/register": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", - "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", - "peer": true, + "node_modules/@libp2p/http-utils/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "peer": true - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" + "node_modules/@libp2p/http-websocket": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/http-websocket/-/http-websocket-2.0.4.tgz", + "integrity": "sha512-AEATJ6MERtxY/UZkFWCgNpB7bhcVTmeeBKZkVl2RJbVYTk8CSv3fO3uXLcYFQ9W4rOa0eG4Gvr/USBU+41xB8A==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@achingbrain/http-parser-js": "^0.5.9", + "@libp2p/http-utils": "^2.0.0", + "@libp2p/interface": "^3.2.0", + "@libp2p/interface-internal": "^3.1.0", + "@libp2p/utils": "^7.0.15", + "@multiformats/multiaddr": "^13.0.3", + "multiformats": "^14.0.0", + "race-event": "^1.6.1", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "license": "MIT", - "peer": true, + "node_modules/@libp2p/http-websocket/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", - "license": "MIT", - "peer": true, + "node_modules/@libp2p/http-websocket/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" + "multiformats": "^14.0.0" } }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", - "license": "MIT", - "peer": true, + "node_modules/@libp2p/identify": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-4.1.12.tgz", + "integrity": "sha512-nNBnxSclPVsvRsaLlwX9cz7gO020KjWEsaSzqDryWz6VStbLLdcwJYkVUP3do7VoFHkkF8PMQkScW7XCA27ZOA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/peer-record": "^9.0.15", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "it-drain": "^3.0.10", + "it-parallel": "^3.0.13", + "main-event": "^1.0.1", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/identify/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@chainsafe/as-chacha20poly1305": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz", - "integrity": "sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew==" - }, - "node_modules/@chainsafe/as-sha256": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.4.2.tgz", - "integrity": "sha512-HJ8GZBRjLeWtRsAXf3EbNsNzmTGpzTFjfpSf4yHkLYC+E52DhT6hwz+7qpj6I/EmFzSUm5tYYvT9K8GZokLQCQ==" - }, - "node_modules/@chainsafe/is-ip": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.0.2.tgz", - "integrity": "sha512-ndGqEMG1W5WkGagaqOZHpPU172AGdxr+LD15sv3WIUvT5oCFUrG1Y0CW/v2Egwj4JXEvSibaIIIqImsm98y1nA==" - }, - "node_modules/@chainsafe/libp2p-noise": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-15.1.0.tgz", - "integrity": "sha512-84S/Uk7ZZRYpSlE5d1odMmQTl5g5Da8etgcf4QI7arTAHkvBs3il7yGHIPt4wV4EV0qIMG+XjdGIYihRXfI2/w==", + "node_modules/@libp2p/identify/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@chainsafe/as-chacha20poly1305": "^0.1.0", - "@chainsafe/as-sha256": "^0.4.1", - "@libp2p/crypto": "^4.0.0", - "@libp2p/interface": "^1.5.0", - "@libp2p/peer-id": "^4.0.0", - "@noble/ciphers": "^0.5.1", - "@noble/curves": "^1.1.0", - "@noble/hashes": "^1.3.1", - "it-length-prefixed": "^9.0.1", - "it-length-prefixed-stream": "^1.0.0", - "it-pair": "^2.0.6", - "it-pipe": "^3.0.1", - "it-stream-types": "^2.0.1", - "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0", - "wherearewe": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@chainsafe/libp2p-yamux": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-yamux/-/libp2p-yamux-6.0.2.tgz", - "integrity": "sha512-S5OkLHqYhEVMQQ4BTgnRANEIbGTQhaC23glCgBwGdeoTRtMpIozwDiPfljFLCm0RYWdCRJw9oFztO95KUHjptA==", + "node_modules/@libp2p/identify/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.1.3", - "@libp2p/utils": "^5.2.5", - "get-iterator": "^2.0.1", - "it-foreach": "^2.0.6", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "uint8arraylist": "^2.4.8" + "multiformats": "^14.0.0" } }, - "node_modules/@chainsafe/netmask": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", - "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", + "node_modules/@libp2p/interface": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", + "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1" + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^13.0.3", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0", + "uint8arraylist": "^3.0.2" } }, - "node_modules/@commitlint/cli": { - "version": "19.4.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.4.0.tgz", - "integrity": "sha512-sJX4J9UioVwZHq7JWM9tjT5bgWYaIN3rC4FP7YwfEwBYiIO+wMyRttRvQLNkow0vCdM0D67r9NEWU0Ui03I4Eg==", - "dev": true, + "node_modules/@libp2p/interface-internal": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-3.1.11.tgz", + "integrity": "sha512-lHJueWKdfkf/nrbVkGNUWrqDvF3T9Brn4hyONQmhTqG2mWyWKntRR/CkPjVgMRDSF2fuyzDJGg+n9tC0Gphk8g==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/format": "^19.3.0", - "@commitlint/lint": "^19.2.2", - "@commitlint/load": "^19.4.0", - "@commitlint/read": "^19.4.0", - "@commitlint/types": "^19.0.3", - "execa": "^8.0.1", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" - }, - "engines": { - "node": ">=v18" + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-collections": "^7.0.26", + "@multiformats/multiaddr": "^13.0.3", + "progress-events": "^1.0.1" } }, - "node_modules/@commitlint/config-conventional": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.2.2.tgz", - "integrity": "sha512-mLXjsxUVLYEGgzbxbxicGPggDuyWNkf25Ht23owXIH+zV2pv1eJuzLK3t1gDY5Gp6pxdE60jZnWUY5cvgL3ufw==", - "dev": true, + "node_modules/@libp2p/interface/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/types": "^19.0.3", - "conventional-changelog-conventionalcommits": "^7.0.2" - }, - "engines": { - "node": ">=v18" + "uint8arrays": "^6.0.0" } }, - "node_modules/@commitlint/config-validator": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.0.3.tgz", - "integrity": "sha512-2D3r4PKjoo59zBc2auodrSCaUnCSALCx54yveOFwwP/i2kfEAQrygwOleFWswLqK0UL/F9r07MFi5ev2ohyM4Q==", - "dev": true, + "node_modules/@libp2p/interface/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/types": "^19.0.3", - "ajv": "^8.11.0" - }, - "engines": { - "node": ">=v18" + "multiformats": "^14.0.0" } }, - "node_modules/@commitlint/ensure": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.0.3.tgz", - "integrity": "sha512-SZEpa/VvBLoT+EFZVb91YWbmaZ/9rPH3ESrINOl0HD2kMYsjvl0tF7nMHh0EpTcv4+gTtZBAe1y/SS6/OhfZzQ==", - "dev": true, + "node_modules/@libp2p/kad-dht": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-16.4.3.tgz", + "integrity": "sha512-i3FDNr9XLHcl8X27MRWrra3UyZw4o1esIen+66CNwFzZOTe85ndv6zNDOpgd2di1JT+E/xhKrghd+0wO3zfC6w==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/types": "^19.0.3", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" - }, - "engines": { - "node": ">=v18" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/ping": "^3.1.11", + "@libp2p/record": "^4.0.15", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "any-signal": "^4.1.1", + "interface-datastore": "^10.0.1", + "it-all": "^3.0.9", + "it-drain": "^3.0.10", + "it-length": "^3.0.9", + "it-map": "^3.1.4", + "it-merge": "^3.0.12", + "it-parallel": "^3.0.13", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-take": "^3.0.9", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.0.1", + "protons-runtime": "^7.0.0", + "race-signal": "^2.0.0", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@commitlint/execute-rule": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.0.0.tgz", - "integrity": "sha512-mtsdpY1qyWgAO/iOK0L6gSGeR7GFcdW7tIjcNFxcWkfLDF5qVbPHKuGATFqRMsxcO8OUKNj0+3WOHB7EHm4Jdw==", - "dev": true, - "engines": { - "node": ">=v18" + "node_modules/@libp2p/kad-dht/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@commitlint/format": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.3.0.tgz", - "integrity": "sha512-luguk5/aF68HiF4H23ACAfk8qS8AHxl4LLN5oxPc24H+2+JRPsNr1OS3Gaea0CrH7PKhArBMKBz5RX9sA5NtTg==", - "dev": true, + "node_modules/@libp2p/kad-dht/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/types": "^19.0.3", - "chalk": "^5.3.0" - }, - "engines": { - "node": ">=v18" + "uint8arrays": "^6.0.0" } }, - "node_modules/@commitlint/is-ignored": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.2.2.tgz", - "integrity": "sha512-eNX54oXMVxncORywF4ZPFtJoBm3Tvp111tg1xf4zWXGfhBPKpfKG6R+G3G4v5CPlRROXpAOpQ3HMhA9n1Tck1g==", - "dev": true, + "node_modules/@libp2p/kad-dht/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/types": "^19.0.3", - "semver": "^7.6.0" - }, - "engines": { - "node": ">=v18" + "multiformats": "^14.0.0" } }, - "node_modules/@commitlint/lint": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.2.2.tgz", - "integrity": "sha512-xrzMmz4JqwGyKQKTpFzlN0dx0TAiT7Ran1fqEBgEmEj+PU98crOFtysJgY+QdeSagx6EDRigQIXJVnfrI0ratA==", - "dev": true, + "node_modules/@libp2p/keychain": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-6.1.6.tgz", + "integrity": "sha512-G4xT3A8M5yiOJAcYT4YTnq9Gd3HOzHOIig1/KzcXzxaeuYCwXOmzreqUYabuEMmExkn966g5jiJ7RkCDQWO3UA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/is-ignored": "^19.2.2", - "@commitlint/parse": "^19.0.3", - "@commitlint/rules": "^19.0.3", - "@commitlint/types": "^19.0.3" - }, - "engines": { - "node": ">=v18" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@noble/hashes": "^2.0.1", + "asn1js": "^3.0.6", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.0", + "sanitize-filename": "^1.6.3", + "uint8arrays": "^6.1.1" } }, - "node_modules/@commitlint/load": { - "version": "19.4.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.4.0.tgz", - "integrity": "sha512-I4lCWaEZYQJ1y+Y+gdvbGAx9pYPavqZAZ3/7/8BpWh+QjscAn8AjsUpLV2PycBsEx7gupq5gM4BViV9xwTIJuw==", - "dev": true, + "node_modules/@libp2p/keychain/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/config-validator": "^19.0.3", - "@commitlint/execute-rule": "^19.0.0", - "@commitlint/resolve-extends": "^19.1.0", - "@commitlint/types": "^19.0.3", - "chalk": "^5.3.0", - "cosmiconfig": "^9.0.0", - "cosmiconfig-typescript-loader": "^5.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0" - }, - "engines": { - "node": ">=v18" + "multiformats": "^14.0.0" } }, - "node_modules/@commitlint/message": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.0.0.tgz", - "integrity": "sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==", - "dev": true, - "engines": { - "node": ">=v18" + "node_modules/@libp2p/logger": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.12.tgz", + "integrity": "sha512-FwCdJ5hGFDvWI/SULZ4Iez+k+JtdMAj4/EK6bWmkh08ORIztwKpt0IXCOe8nIfKhBKwLYJu00BnZDRsedX7+7Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.2.5", + "@multiformats/multiaddr": "^13.0.3", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.0", + "weald": "^1.1.0" } }, - "node_modules/@commitlint/parse": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.0.3.tgz", - "integrity": "sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==", - "dev": true, + "node_modules/@libp2p/mdns": { + "version": "12.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-12.0.29.tgz", + "integrity": "sha512-EiDvAm0rgxlGDTeWFpBBX7IL8tE/Yxq3GyqGpYYdySSHqtbkKLxU7v/kf1uBRqbItQN5xgag8kFTpkr+89xHZQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/types": "^19.0.3", - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-parser": "^5.0.0" - }, - "engines": { - "node": ">=v18" + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@types/multicast-dns": "^7.2.4", + "dns-packet": "^5.6.1", + "main-event": "^1.0.1", + "multicast-dns": "^7.2.5" } }, - "node_modules/@commitlint/read": { - "version": "19.4.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.4.0.tgz", - "integrity": "sha512-r95jLOEZzKDakXtnQub+zR3xjdnrl2XzerPwm7ch1/cc5JGq04tyaNpa6ty0CRCWdVrk4CZHhqHozb8yZwy2+g==", - "dev": true, + "node_modules/@libp2p/mplex": { + "version": "12.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-12.0.29.tgz", + "integrity": "sha512-28u86IgMqgs8Y8OfnhV8yVaThD1881PwTIAuuIlwbBRj92Y+LAZbPBxFUJeauIYartT+5u3/PuMJ7wurSocioA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/top-level": "^19.0.0", - "@commitlint/types": "^19.0.3", - "execa": "^8.0.1", - "git-raw-commits": "^4.0.0", - "minimist": "^1.2.8" - }, - "engines": { - "node": ">=v18" + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "it-pushable": "^3.2.3", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@commitlint/resolve-extends": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.1.0.tgz", - "integrity": "sha512-z2riI+8G3CET5CPgXJPlzftH+RiWYLMYv4C9tSLdLXdr6pBNimSKukYP9MS27ejmscqCTVA4almdLh0ODD2KYg==", - "dev": true, + "node_modules/@libp2p/mplex/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/config-validator": "^19.0.3", - "@commitlint/types": "^19.0.3", - "global-directory": "^4.0.1", - "import-meta-resolve": "^4.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=v18" + "uint8arrays": "^6.0.0" } }, - "node_modules/@commitlint/rules": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.0.3.tgz", - "integrity": "sha512-TspKb9VB6svklxNCKKwxhELn7qhtY1rFF8ls58DcFd0F97XoG07xugPjjbVnLqmMkRjZDbDIwBKt9bddOfLaPw==", - "dev": true, + "node_modules/@libp2p/mplex/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@commitlint/ensure": "^19.0.3", - "@commitlint/message": "^19.0.0", - "@commitlint/to-lines": "^19.0.0", - "@commitlint/types": "^19.0.3", - "execa": "^8.0.1" - }, - "engines": { - "node": ">=v18" + "multiformats": "^14.0.0" } }, - "node_modules/@commitlint/to-lines": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.0.0.tgz", - "integrity": "sha512-vkxWo+VQU5wFhiP9Ub9Sre0FYe019JxFikrALVoD5UGa8/t3yOJEpEhxC5xKiENKKhUkTpEItMTRAjHw2SCpZw==", - "dev": true, - "engines": { - "node": ">=v18" + "node_modules/@libp2p/multistream-select": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-7.0.26.tgz", + "integrity": "sha512-bbKMgu7W/J6eknQODGpySshZq9QWEYbIM/fMYrihbdIT/eS1h+8U6ko2qylfwbDyYlnUoYP45Cqi+oxoNwGH3Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "it-length-prefixed": "^11.0.1", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@commitlint/top-level": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.0.0.tgz", - "integrity": "sha512-KKjShd6u1aMGNkCkaX4aG1jOGdn7f8ZI8TR1VEuNqUOjWTOdcDSsmglinglJ18JTjuBX5I1PtjrhQCRcixRVFQ==", - "dev": true, + "node_modules/@libp2p/multistream-select/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "find-up": "^7.0.0" - }, - "engines": { - "node": ">=v18" + "uint8arrays": "^6.0.0" } }, - "node_modules/@commitlint/types": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.0.3.tgz", - "integrity": "sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==", - "dev": true, + "node_modules/@libp2p/multistream-select/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@types/conventional-commits-parser": "^5.0.0", - "chalk": "^5.3.0" - }, - "engines": { - "node": ">=v18" + "multiformats": "^14.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", + "node_modules/@libp2p/peer-collections": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-7.0.26.tgz", + "integrity": "sha512-hjJbC/Jo1TjxqaKph2W6ARRnrNoBCEbSQMUsGT17JXQktJc7ImVGLoFy2VsZoJGMX4RLN+gKp6f8McFGb5Vi1w==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "multiformats": "^14.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node_modules/@libp2p/peer-id": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.14.tgz", + "integrity": "sha512-tquBouwbUXgntPtT5q4vfaXbNWmSzh2zo4p2u8IzclVvggXOpmyBlAUNPXDiFqZvQpsxVBrTTemmn0+AwDwecg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "multiformats": "^14.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, + "node_modules/@libp2p/peer-id/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "multiformats": "^14.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, - "license": "MIT", + "node_modules/@libp2p/peer-record": { + "version": "9.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-9.0.15.tgz", + "integrity": "sha512-WZH9Xkdvpr6TlM6eDdE/KZJmk558jrAC/ufNHnbWXdEUxF/GTzx7RsrBgaC6cPGBTNTjzICfO6vZKR2US0Fl9Q==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", + "@multiformats/multiaddr": "^13.0.3", + "multiformats": "^14.0.0", + "protons-runtime": "^7.0.0", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/@libp2p/peer-record/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "peer": true - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "peer": true, + "node_modules/@libp2p/peer-record/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@hapi/hoek": "^9.0.0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@helia/bitswap": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-1.1.4.tgz", - "integrity": "sha512-BcmIc2FZ0NEjSYrwJxzFO5OapcOCBfcHmaBKaDoPNVh1egLLUkws2a7C3hPH5ZR7PXGQPXonH1YFz1CKYypzgA==", + "node_modules/@libp2p/peer-record/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@helia/interface": "^4.3.1", - "@helia/utils": "^0.3.3", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.5", - "@libp2p/peer-collections": "^5.1.6", - "@libp2p/utils": "^5.2.3", - "@multiformats/multiaddr": "^12.1.14", - "any-signal": "^4.1.1", - "interface-blockstore": "^5.2.9", - "interface-store": "^5.1.7", - "it-drain": "^3.0.5", - "it-length-prefixed": "^9.0.0", - "it-length-prefixed-stream": "^1.1.6", - "it-map": "^3.0.5", - "it-pipe": "^3.0.1", - "it-take": "^3.0.1", - "multiformats": "^13.0.1", - "p-defer": "^4.0.0", - "progress-events": "^1.0.0", - "protons-runtime": "^5.0.0", - "race-event": "^1.2.0", - "uint8-varint": "^2.0.3", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.1" + "multiformats": "^14.0.0" } }, - "node_modules/@helia/block-brokers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-3.0.4.tgz", - "integrity": "sha512-ymtV2cNrVeRoXsAOTpx558nDzfyOu87bLAewF98OVZ/NB28EUTy0c592GHLX3QcMaW/XfTzSGCi+te3fvVIuQg==", + "node_modules/@libp2p/peer-store": { + "version": "12.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-12.0.26.tgz", + "integrity": "sha512-VoLjd7SQrYN45w3pnGl8YQQn+9tzMsoZxdmSG7VWoMiQhMLyxCtkctsVi9OCdT1IkdelPRq84KHklkK0D4FG9w==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@helia/bitswap": "^1.1.4", - "@helia/interface": "^4.3.1", - "@helia/utils": "^0.3.3", - "@libp2p/interface": "^1.1.4", - "@libp2p/utils": "^5.2.6", - "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-matcher": "^1.2.0", - "@multiformats/multiaddr-to-uri": "^10.0.1", - "interface-blockstore": "^5.2.10", - "interface-store": "^5.1.8", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/peer-record": "^9.0.15", + "@multiformats/multiaddr": "^13.0.3", + "interface-datastore": "^10.0.1", + "it-all": "^3.0.9", + "main-event": "^1.0.1", + "mortice": "^3.3.1", + "multiformats": "^14.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/peer-store/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@helia/delegated-routing-v1-http-api-client": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-3.0.1.tgz", - "integrity": "sha512-Gkaw3B8IjgCCXtasa17j8wQaFdzph2s0RiLyrJTS2htmwVFWZEjLNLUDJeWsMHBoLBza8SrVGvQ9fiN3XD3rfg==", + "node_modules/@libp2p/peer-store/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.1.1", - "@libp2p/logger": "^4.0.4", - "@libp2p/peer-id": "^4.0.4", - "@multiformats/multiaddr": "^12.1.3", - "any-signal": "^4.1.1", - "browser-readablestream-to-it": "^2.0.3", - "ipns": "^9.0.0", - "it-first": "^3.0.3", - "it-map": "^3.0.4", - "it-ndjson": "^1.0.4", - "multiformats": "^13.0.0", - "p-defer": "^4.0.0", - "p-queue": "^8.0.1", - "uint8arrays": "^5.0.1" + "uint8arrays": "^6.0.0" } }, - "node_modules/@helia/interface": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-4.3.1.tgz", - "integrity": "sha512-jvuUUAQbLMhvMt+Lon7mJfJt5xJWiwn8aXtI9fHBpMNMsrKJOtefxAzwSr18xe+C9bMdOyRtocj2eFhs74bQ8w==", + "node_modules/@libp2p/peer-store/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.1.4", - "@multiformats/dns": "^1.0.1", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@helia/routers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@helia/routers/-/routers-1.1.1.tgz", - "integrity": "sha512-LsEkUAno4PiQ1cmwxFnYWy5xQicpzjXyYeRJEGLjAnAXin8hq0JPkEouZx8vTlmSf1Rde4RSaTu3+3H1qj5ccw==", + "node_modules/@libp2p/ping": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-3.1.11.tgz", + "integrity": "sha512-3ByJjpzxMAEJv8ZjWeLYv9k0Ba3eVxPRcWc8H79/RQhTQwY2zZvHYX4gQUmyqA9Sv0Q6peL4TwUW7E3wgN27Mw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.3.1", - "@libp2p/interface": "^1.1.4", - "@multiformats/uri-to-multiaddr": "^8.0.0", - "ipns": "^9.0.0", - "it-first": "^3.0.4", - "it-map": "^3.0.5", - "multiformats": "^13.1.0", - "uint8arrays": "^5.0.2" + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "p-event": "^7.0.0", + "race-signal": "^2.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@helia/unixfs": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-3.0.7.tgz", - "integrity": "sha512-5fBXYk9t6Iu/DnTQZ//sn0MhM00AnK+VYEkfXdReoBUdzVFrOFW/bihT4sgLfYCq86+TShdrU1aDLkbFiN6FnQ==", - "dependencies": { - "@helia/interface": "^4.3.1", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@multiformats/murmur3": "^2.1.8", - "hamt-sharding": "^3.0.6", - "interface-blockstore": "^5.2.10", - "ipfs-unixfs": "^11.1.3", - "ipfs-unixfs-exporter": "^13.5.0", - "ipfs-unixfs-importer": "^15.2.4", - "it-all": "^3.0.4", - "it-glob": "^3.0.0", - "it-last": "^3.0.4", - "it-pipe": "^3.0.1", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "sparse-array": "^1.3.2", - "uint8arrays": "^5.0.2" + "node_modules/@libp2p/ping/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" } }, - "node_modules/@helia/utils": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-0.3.3.tgz", - "integrity": "sha512-iCS8dJRULgltfx7aLLoqfjWUpqBWrTyouCToB9RgHpNs4lHcIsZn/VSbHyjC4NUF9nIWsxhH/3UAdvWl23HEgw==", + "node_modules/@libp2p/ping/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@helia/interface": "^4.3.1", - "@ipld/dag-cbor": "^9.2.0", - "@ipld/dag-json": "^10.2.0", - "@ipld/dag-pb": "^4.1.0", - "@libp2p/crypto": "^4.0.6", - "@libp2p/interface": "^1.1.4", - "@libp2p/logger": "^4.0.7", - "@libp2p/utils": "^5.2.6", - "@multiformats/dns": "^1.0.1", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "blockstore-core": "^4.4.1", - "cborg": "^4.0.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "interface-store": "^5.1.8", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-foreach": "^2.0.6", - "it-merge": "^3.0.3", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "murmurhash3js-revisited": "^3.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.2" + "multiformats": "^14.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, + "node_modules/@libp2p/record": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.15.tgz", + "integrity": "sha512-EaHFtQAlZuM9vCpd+0KnZhTVgV26Uzje7DkyUseGNKTy+Wit9Q94C/+wDThpZuRHnK1FH/CvPxJ4kLj7AoQ1WA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node_modules/@libp2p/record/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@libp2p/record/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" + } }, - "node_modules/@ipld/dag-cbor": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.1.tgz", - "integrity": "sha512-nyY48yE7r3dnJVlxrdaimrbloh4RokQaNRdI//btfTkcTEZbpmSrbYcBQ4VKTf8ZxXAOUJy4VsRpkJo+y9RTnA==", + "node_modules/@libp2p/record/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@ipld/dag-json": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.2.tgz", - "integrity": "sha512-NnU8HdHKwAoGyrW3S09NMa8aZw0tImLRyR64hoafpLpDpAbA9g1+fb24JsdlugbL4sXUQVwDVA+qK4Ud8V83lA==", + "node_modules/@libp2p/tcp": { + "version": "11.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-11.0.26.tgz", + "integrity": "sha512-OOLb1Ov6zQRo983fEPRUvfcWmFrm2oJlwPgf7M6pzLyQOsTxpgb2MV37Az8ANy5GEAG4JJ+KPZn3g7JfEp33xA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "main-event": "^1.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.0.1", + "uint8arraylist": "^3.0.2" } }, - "node_modules/@ipld/dag-pb": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.2.tgz", - "integrity": "sha512-BSztO4l3C+ya9HjCaQot26Y4AVsqIKtnn6+23ubc1usucnf6yoTBme18oCCdM6gKBMxuPqju5ye3lh9WEJsdeQ==", + "node_modules/@libp2p/tcp/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "multiformats": "^13.1.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@isaacs/cliui": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node_modules/@libp2p/tcp/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "peer": true, - "engines": { - "node": ">=12" + "node_modules/@libp2p/tls": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@libp2p/tls/-/tls-3.1.8.tgz", + "integrity": "sha512-Orch13vBdr6EgsNLMCnBWTW7ff0+LwgdC3PTQyubalTxzFiYGfC3BZgugYU+L36GSOKMRR3QML/GzMocKegRUQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "@peculiar/asn1-schema": "^2.4.0", + "@peculiar/asn1-x509": "^2.4.0", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^2.0.0", + "asn1js": "^3.0.6", + "p-event": "^7.0.0", + "protons-runtime": "^7.0.0", + "reflect-metadata": "^0.2.2", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/tls/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "peer": true, + "node_modules/@libp2p/tls/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/types": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "peer": true, + "node_modules/@libp2p/tls/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "peer": true, + "node_modules/@libp2p/upnp-nat": { + "version": "4.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-4.0.26.tgz", + "integrity": "sha512-WFWRwvAZVU437flozWEzjPqSNHwlkczmd/aoeR1VpQ0h4MFWJySlSTuUBvS/1dypDDRiSrtGAesMFW28Dc4buQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@achingbrain/nat-port-mapper": "^4.0.4", + "@chainsafe/is-ip": "^2.1.0", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "main-event": "^1.0.1", + "p-defer": "^4.0.1", + "race-signal": "^2.0.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "peer": true, + "node_modules/@libp2p/utils": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-7.3.2.tgz", + "integrity": "sha512-nVfJkTE3QMa/LVKVD5sKDB8birKua0BR0oKoqnLSXsX1ez89PvhR1zZjBfARSGVZdv5qUmblcdcvjJ76HD0CWA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@chainsafe/is-ip": "^2.1.0", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/interface": "^3.2.5", + "@libp2p/logger": "^6.2.12", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@sindresorhus/fnv1a": "^3.1.0", + "any-signal": "^4.1.1", + "cborg": "^6.0.0", + "delay": "^7.0.0", + "is-loopback-addr": "^2.0.2", + "it-length-prefixed": "^11.0.1", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "netmask": "^2.0.2", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.1.0", + "protons-runtime": "^7.0.0", + "race-signal": "^2.0.0", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "peer": true, + "node_modules/@libp2p/utils/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/@libp2p/utils/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "uint8arrays": "^6.0.0" } }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/@libp2p/utils/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/@libp2p/webrtc": { + "version": "6.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-6.0.29.tgz", + "integrity": "sha512-RzsDn4+d/PhHIplWu7rDWMMpZtkF6kFB+fZayUZQxsApxuL3TdNF/mBR93Y5nEY1GswmAERQzTPr0si91xTXOw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "peer": true, + "@chainsafe/is-ip": "^2.1.0", + "@chainsafe/libp2p-noise": "^17.0.0", + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/keychain": "^6.1.6", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^2.0.0", + "get-port": "^7.1.0", + "interface-datastore": "^10.0.1", + "it-length-prefixed": "^11.0.1", + "it-protobuf-stream": "^3.0.0", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "node-datachannel": "^0.32.3", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "p-timeout": "^7.0.0", + "p-wait-for": "^6.0.0", + "progress-events": "^1.0.1", + "protons-runtime": "^7.0.0", + "race-signal": "^2.0.0", + "react-native-webrtc": "^124.0.6", + "reflect-metadata": "^0.2.2", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/webrtc/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "peer": true, + "node_modules/@libp2p/webrtc/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "uint8arrays": "^6.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "peer": true, - "engines": { - "node": ">=6.0.0" + "node_modules/@libp2p/webrtc/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "peer": true, + "node_modules/@libp2p/websockets": { + "version": "10.1.19", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-10.1.19.tgz", + "integrity": "sha512-inY5dWhm+mCrnS/wcHIWtDZ908c1W7UkpXE9i6uvnJVapWbvonvX9GEsTLvh4NbL7SOfnOHGkwDVgx1M2RxcRA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@multiformats/multiaddr-to-uri": "^12.0.0", + "main-event": "^1.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.0.1", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1", + "ws": "^8.18.3" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "peer": true, + "node_modules/@libp2p/websockets/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "uint8arrays": "^6.0.0" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" - }, - "node_modules/@libp2p/autonat": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-1.1.4.tgz", - "integrity": "sha512-yt/sUisqBLZPM/0lvpRo0enIEIbdyuSraIu4xRlGkdqqAXSo9T6CJppXxNi9VKTmlmD39WYYNP41Ilt3SvQhRQ==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/multiaddr": "^12.2.3", - "it-first": "^3.0.6", - "it-length-prefixed": "^9.0.4", - "it-map": "^3.1.0", - "it-parallel": "^3.0.7", - "it-pipe": "^3.0.1", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8" + "node_modules/@libp2p/websockets/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/bootstrap": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-10.1.4.tgz", - "integrity": "sha512-bt4Tz/DMKnlvZhF4d9Nkv/K7qgnoDBM/N0zwjA/aQg1vSq54/Szc2K3cIDAv9/r9dM4ckDRmIZZENr/bwcIIKw==", + "node_modules/@multiformats/dns": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", + "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-id": "^4.2.3", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.3" + "@dnsquery/dns-packet": "^6.1.1", + "@libp2p/interface": "^3.1.0", + "hashlru": "^2.3.0", + "p-queue": "^9.0.0", + "progress-events": "^1.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/circuit-relay-v2": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-1.1.4.tgz", - "integrity": "sha512-WLiisIvRWVSJLzb4MO1aqDOD2A7s99OgpfeKKt9nxSaq34WeQS9aGN9YxSczYkum9hBTdnLJZZDQrd6SId6NbQ==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/peer-collections": "^5.2.8", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/peer-record": "^7.0.24", - "@libp2p/utils": "^5.4.8", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.3", - "any-signal": "^4.1.1", - "it-protobuf-stream": "^1.1.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "protons-runtime": "^5.4.0", - "race-signal": "^1.0.2", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "node_modules/@multiformats/dns/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/crypto": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.1.9.tgz", - "integrity": "sha512-8Cf2VKh0uC/rQLvTLSloIOMqUvf4jsSTHXgjWQRf47lDNJlNNI0wSv2S6gakT72GZsRV/jCjYwKPqRlsa5S0iA==", + "node_modules/@multiformats/multiaddr": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", + "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.7.0", - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.4.0", - "asn1js": "^3.0.5", - "multiformats": "^13.1.0", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "@chainsafe/is-ip": "^2.0.1", + "multiformats": "^14.0.0", + "uint8-varint": "^3.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/dcutr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-1.1.4.tgz", - "integrity": "sha512-NjIiAoi5q2Y2q0eON+VCdm8ec2Be1Q9q1vcL6z7RyBWwIkk6PPy8RvBaI6GcGKjk251jLrN6vz2yoL7vWRht2A==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "delay": "^6.0.0", - "it-protobuf-stream": "^1.1.3", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8" + "node_modules/@multiformats/multiaddr-matcher": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-3.0.2.tgz", + "integrity": "sha512-iphGQJliZxe2yKu57bdRDgeS+3znc5uXtMybDO1Wau3rIjas4zjrjlyxmFz3wqyUL9f3VDQwas/ZqA7N4QeSfw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/multiaddr": "^13.0.0" } }, - "node_modules/@libp2p/identify": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-2.1.5.tgz", - "integrity": "sha512-uVghY2KfZ3ffDkPmcivfiRDlq1h5rCcoHAW+Kb7JF2qrDfg6BgHAn6IRN4pe/DnYXOuJXIIm6+jjcReTPGBKBQ==", + "node_modules/@multiformats/multiaddr-to-uri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-12.0.0.tgz", + "integrity": "sha512-3uIEBCiy8tfzxYYBl81x1tISiNBQ7mHU4pGjippbJRoQYHzy/ZdZM/7JvTldr8pc/dzpkaNJxnsuxxlhsPOJsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.7.0", - "@libp2p/interface-internal": "^1.3.4", - "@libp2p/peer-id": "^4.2.4", - "@libp2p/peer-record": "^7.0.25", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "it-drain": "^3.0.7", - "it-parallel": "^3.0.7", - "it-protobuf-stream": "^1.1.3", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0", - "wherearewe": "^2.0.1" + "@multiformats/multiaddr": "^13.0.0" } }, - "node_modules/@libp2p/interface": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.7.0.tgz", - "integrity": "sha512-/zFyaIaIGW0aihhsH7/93vQdpWInUzFocxF11RO/029Y6h0SVjs24HHbils+DqaFDTqN+L7oNlBx2rM2MnmTjA==", + "node_modules/@multiformats/multiaddr/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" + "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/interface-internal": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-1.3.4.tgz", - "integrity": "sha512-8x/0sdeH8T16yZ9t/Cfja0ms6Ho9fF3riX56WhQrNxMU6C1sIgAFmzUNzHLxxOR+rkKyL9cyXIyB+RcBf4gzjA==", + "node_modules/@multiformats/murmur3": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.8.tgz", + "integrity": "sha512-CrsYnnoWbTYPkLl9D6zKaFPlRbnOekofvi93/1IrXc5eaT5QChRscEFV93kVqHwL2phi8u1hj9UnaeFOq2PYMQ==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.7.0", - "@libp2p/peer-collections": "^5.2.9", - "@multiformats/multiaddr": "^12.2.3", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" + "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/kad-dht": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-12.1.5.tgz", - "integrity": "sha512-n/Tdd3zVa2p1S4L6wRIBUAo3ctCbiEkp1aewpOUthL6rOwBh6U/RQ+dssiZNEDHCsF1ta/mZkREuXqxOPpplFQ==", + "node_modules/@multiformats/uri-to-multiaddr": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@multiformats/uri-to-multiaddr/-/uri-to-multiaddr-10.0.0.tgz", + "integrity": "sha512-QsmwLmY6iB1wDU1e1wyctqF0eP/2KD1QPLQ+APISuqETbCTSpaq159S/K/ssmWlBpSEkhH0SUfBUgGi014Ttfw==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^4.1.9", - "@libp2p/interface": "^1.7.0", - "@libp2p/interface-internal": "^1.3.4", - "@libp2p/peer-collections": "^5.2.9", - "@libp2p/peer-id": "^4.2.4", - "@libp2p/record": "^4.0.4", - "@libp2p/utils": "^5.4.9", - "@multiformats/multiaddr": "^12.2.3", - "any-signal": "^4.1.1", - "hashlru": "^2.3.0", - "interface-datastore": "^8.2.11", - "it-drain": "^3.0.7", - "it-length": "^3.0.6", - "it-length-prefixed": "^9.0.4", - "it-map": "^3.1.0", - "it-merge": "^3.0.5", - "it-parallel": "^3.0.7", - "it-pipe": "^3.0.1", - "it-protobuf-stream": "^1.1.3", - "it-take": "^3.0.5", - "multiformats": "^13.1.0", - "p-defer": "^4.0.1", - "p-event": "^6.0.1", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0", - "protons-runtime": "^5.4.0", - "race-signal": "^1.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "@multiformats/multiaddr": "^13.0.0", + "is-ip": "^5.0.0" } }, - "node_modules/@libp2p/keychain": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-4.1.4.tgz", - "integrity": "sha512-nYMJ1RAuKP7WqtVocZByVyro57ZqLoKrDhC2+VDleFtco1FrTFxqrF7I5Hk6vn9lz94eIcG9ZCh9geRKEs5ecg==", - "dependencies": { - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-id": "^4.2.3", - "interface-datastore": "^8.2.11", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.1.0" + "node_modules/@noble/ciphers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.3.0.tgz", + "integrity": "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@libp2p/logger": { - "version": "4.0.20", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.20.tgz", - "integrity": "sha512-TTh2dhHsOTAlMPxSa9ncFPHa/0jTt+0AQxwHdlxg/OGLAgc9VRhnrhHUbJZp07Crcw4T/MOfS4KhjlxgqYgJRw==", - "license": "Apache-2.0 OR MIT", + "node_modules/@noble/curves": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", + "license": "MIT", "dependencies": { - "@libp2p/interface": "^1.7.0", - "@multiformats/multiaddr": "^12.2.3", - "interface-datastore": "^8.2.11", - "multiformats": "^13.1.0", - "weald": "^1.0.2" + "@noble/hashes": "2.3.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@libp2p/mdns": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-10.1.4.tgz", - "integrity": "sha512-OlvfOGdNw9jPpTwgS9TYskNNn8kiqVDAIT42XOL9aLIXlX6Uqx1LlKphoWUxL01TsSIBUX5asKdnbGPpV/qqLA==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/multiaddr": "^12.2.3", - "@types/multicast-dns": "^7.2.4", - "dns-packet": "^5.6.1", - "multicast-dns": "^7.2.5" + "node_modules/@noble/hashes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@libp2p/mplex": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-10.1.4.tgz", - "integrity": "sha512-DxRAeixdbGVEGV+5ekp0C63NyVkKv0bOYkx/dVozcWDHmfClTD070KOhF3gIplKAVInC3FYaO3ahWzMdrD/J6g==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/utils": "^5.4.8", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@libp2p/multistream-select": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-5.1.16.tgz", - "integrity": "sha512-0IUecYYu++gimGRzG324A6+NE9Vk/6Wvmp2xegfLjUZRegpFbHup3UvFEGxTkpHmsoswJMMTPHQyCNJFga+Z7Q==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "it-length-prefixed": "^9.0.4", - "it-length-prefixed-stream": "^1.1.7", - "it-stream-types": "^2.0.1", - "p-defer": "^4.0.1", - "race-signal": "^1.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" } }, - "node_modules/@libp2p/peer-collections": { - "version": "5.2.9", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-5.2.9.tgz", - "integrity": "sha512-8gBmzQlCWjjb+FSQBKK33T25Y5Df/8FWCXFtJDsprVxVUzDOQoibQJ5Tb4Y+mb96HUhNzoaRWVEamB78MMB3DA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", "dependencies": { - "@libp2p/interface": "^1.7.0", - "@libp2p/peer-id": "^4.2.4", - "@libp2p/utils": "^5.4.9" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@libp2p/peer-id": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.2.4.tgz", - "integrity": "sha512-mvvsVxt4HkF14BrTNKbqr14VObW+KBJBWu1Oe6BFCoDttGMQLaI+PdduE1r6Tquntv5IONBqoITgD7ow5dQ+vQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@peculiar/asn1-cms": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.9.4.tgz", + "integrity": "sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw==", + "license": "MIT", "dependencies": { - "@libp2p/interface": "^1.7.0", - "multiformats": "^13.1.0", - "uint8arrays": "^5.1.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-attr": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/peer-id-factory": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id-factory/-/peer-id-factory-4.2.3.tgz", - "integrity": "sha512-4ryh3M4UH+nwlM1o3y3iCpW2jTxjj+Hgb6UNI1nFxZGLA6MI+En8kKiroa970xiVvsA+Xmas8B9gWeMFrZKBKg==", + "node_modules/@peculiar/asn1-csr": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.9.4.tgz", + "integrity": "sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw==", + "license": "MIT", "dependencies": { - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-id": "^4.2.3", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/peer-record": { - "version": "7.0.25", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-7.0.25.tgz", - "integrity": "sha512-b54P3cSeQniW/HPJjBVbeF3KaVUQkWa431gotuIFUS1PYgtz69uzkRrVY6Qt+RBb4R4fcmH4K4jWyZi3xyLGfQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@peculiar/asn1-ecc": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.9.4.tgz", + "integrity": "sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ==", + "license": "MIT", "dependencies": { - "@libp2p/crypto": "^4.1.9", - "@libp2p/interface": "^1.7.0", - "@libp2p/peer-id": "^4.2.4", - "@libp2p/utils": "^5.4.9", - "@multiformats/multiaddr": "^12.2.3", - "protons-runtime": "^5.4.0", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/peer-store": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-10.1.4.tgz", - "integrity": "sha512-D/lqEaWZV8xEBQ7b0ZEFYtaa6+LjXjs+PRrFpmnvyuz86o9KQedVoJPufT4g0nKIeQZUtdRfkzWNnwD+fru64g==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-collections": "^5.2.8", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/peer-record": "^7.0.24", - "@multiformats/multiaddr": "^12.2.3", - "interface-datastore": "^8.2.11", - "it-all": "^3.0.6", - "mortice": "^3.0.4", - "multiformats": "^13.1.0", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "node_modules/@peculiar/asn1-pfx": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.9.4.tgz", + "integrity": "sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-pkcs8": "^2.9.4", + "@peculiar/asn1-rsa": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/ping": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-1.1.6.tgz", - "integrity": "sha512-tzTL0BzS1JaHE8v4PhRZ5K8wQQQcTMXM/0baCkLTLIaSMe1fzhj+KHbFNoUrY3yni4yfsVY1uR0qchhc1/J9qg==", - "license": "Apache-2.0 OR MIT", + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.9.4.tgz", + "integrity": "sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ==", + "license": "MIT", "dependencies": { - "@libp2p/crypto": "^4.1.9", - "@libp2p/interface": "^1.7.0", - "@libp2p/interface-internal": "^1.3.4", - "@multiformats/multiaddr": "^12.2.3", - "it-first": "^3.0.6", - "it-pipe": "^3.0.1", - "uint8arrays": "^5.1.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/record": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.4.tgz", - "integrity": "sha512-wEEeHXGNIcc8HtGbgGMuSHbboUWMxKG7OxALFwkE+KACgfRJZTESOp6XIdZnyC0r9lfEFsjF01pFKBTzoBmWEQ==", + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.9.4.tgz", + "integrity": "sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ==", + "license": "MIT", "dependencies": { - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-pfx": "^2.9.4", + "@peculiar/asn1-pkcs8": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-attr": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/tcp": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-9.1.4.tgz", - "integrity": "sha512-0j/aYdMFLhLWUodtyuBt5+smzkmXCiczOxNOwKyTVUuoMMcAPCLxACqCy8213qiwpJ3qP5ZTTTs/JJMgWRC7ag==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.3", - "@types/sinon": "^17.0.3", - "progress-events": "^1.0.0", - "stream-to-it": "^1.0.1" + "node_modules/@peculiar/asn1-rsa": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.9.4.tgz", + "integrity": "sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@libp2p/upnp-nat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-1.2.4.tgz", - "integrity": "sha512-oI0yAg7GKpiPy4u2u/34wYPJ/CLBraIA5dUh21ZGy3TH88eaTOYfyHSfGw9AI0IT/012xxkpqbA+h5+V+qDoBQ==", - "dependencies": { - "@achingbrain/nat-port-mapper": "^1.0.13", - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/multiaddr": "^12.2.3", - "wherearewe": "^2.0.1" - } - }, - "node_modules/@libp2p/utils": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-5.4.9.tgz", - "integrity": "sha512-0fRdX98WqhTmXU2WEVLegLFxs/kKTtUHanHk5Lzs4oGsIzlPHR7zE6lj/U1WfsFA+Xo1eYQpNLiXEL29hG+Nyw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^4.1.9", - "@libp2p/interface": "^1.7.0", - "@libp2p/logger": "^4.0.20", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "@sindresorhus/fnv1a": "^3.1.0", - "@types/murmurhash3js-revisited": "^3.0.3", - "any-signal": "^4.1.1", - "delay": "^6.0.0", - "get-iterator": "^2.0.1", - "is-loopback-addr": "^2.0.2", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "murmurhash3js-revisited": "^3.0.0", - "netmask": "^2.0.2", - "p-defer": "^4.0.1", - "race-event": "^1.3.0", - "race-signal": "^1.0.2", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/webrtc": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-4.1.4.tgz", - "integrity": "sha512-vX6FctslugUzZiZNQ8+uNAYN4ZDunnqcecUog0KOonySI8XAJs6ZA9Y2giCoSbkmaAP80gSRp8gGfuri8puLYw==", - "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "detect-browser": "^5.3.0", - "it-length-prefixed": "^9.0.4", - "it-protobuf-stream": "^1.1.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "node-datachannel": "^0.11.0", - "p-defer": "^4.0.1", - "p-event": "^6.0.1", - "p-timeout": "^6.1.2", - "progress-events": "^1.0.0", - "protons-runtime": "^5.4.0", - "race-signal": "^1.0.2", - "react-native-webrtc": "^118.0.7", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/websockets": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-8.1.4.tgz", - "integrity": "sha512-VWVgIo8t4VTzTI+orfGQxeHSXNWQL6dF//nH2p3nI5Z0saR6lrv5pNTsXbIhrZukGRFGgSwqGsd8bGhEohKNrQ==", - "dependencies": { - "@libp2p/interface": "^1.6.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-to-uri": "^10.0.1", - "@types/ws": "^8.5.10", - "it-ws": "^6.1.1", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "race-signal": "^1.0.2", - "wherearewe": "^2.0.1", - "ws": "^8.17.0" - } - }, - "node_modules/@libp2p/webtransport": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@libp2p/webtransport/-/webtransport-4.1.4.tgz", - "integrity": "sha512-gbQmEZIrDgdHUMVp2NYKrnnK6slMMoB+k9Pd5uTd/mD6kvhgKBKWmWkEX++CUDlv4j78E6tgQEjQ4xNbnl79+A==", - "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@libp2p/interface": "^1.6.3", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/utils": "^5.4.8", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "race-signal": "^1.0.2", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@multiformats/dns": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", - "integrity": "sha512-nt/5UqjMPtyvkG9BQYdJ4GfLK3nMqGpFZOzf4hAmIa0sJh2LlS9YKXZ4FgwBDsaHvzZqR/rUFIywIc7pkHNNuw==", - "dependencies": { - "@types/dns-packet": "^5.6.5", - "buffer": "^6.0.3", - "dns-packet": "^5.6.1", - "hashlru": "^2.3.0", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" - } - }, - "node_modules/@multiformats/mafmt": { - "version": "12.1.6", - "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", - "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", - "dependencies": { - "@multiformats/multiaddr": "^12.0.0" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "license": "MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@multiformats/multiaddr-matcher": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.2.4.tgz", - "integrity": "sha512-GgpqzQFL4Mj8t7cLNHC5nuYUuSm0kTtSUyYswiyWwTSUY3XwRAMx0UiFWQg+ETk0u+/IvFaHxfnyEoH3tasvwg==", + "node_modules/@peculiar/asn1-x509": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.9.4.tgz", + "integrity": "sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q==", + "license": "MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@multiformats/multiaddr": "^12.0.0", - "multiformats": "^13.0.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@multiformats/multiaddr-to-uri": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.1.0.tgz", - "integrity": "sha512-ZNwSAx3ssBWwd4y0LKrOsq9xG7LBHboQxnUdSduNc2fTh/NS1UjA2slgUy6KHxH5k9S2DSus0iU2CoyJyN0/pg==", + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.9.4.tgz", + "integrity": "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==", + "license": "MIT", "dependencies": { - "@multiformats/multiaddr": "^12.3.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@multiformats/murmur3": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.8.tgz", - "integrity": "sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA==", + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", "dependencies": { - "multiformats": "^13.0.0", - "murmurhash3js-revisited": "^3.0.0" + "tslib": "^2.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=8.0.0" } }, - "node_modules/@multiformats/uri-to-multiaddr": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@multiformats/uri-to-multiaddr/-/uri-to-multiaddr-8.0.0.tgz", - "integrity": "sha512-86O+gY6JTnCv0O/IxTKV+1+GACoEBTr5Cfyh+FdzStWneviz2AZwLK8Hsys5dbfMgT//Vs7FolMiEHURlCel8w==", + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", "dependencies": { - "@multiformats/multiaddr": "^12.1.14", - "is-ip": "^5.0.0" - } - }, - "node_modules/@noble/ciphers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", - "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", - "funding": { - "url": "https://paulmillr.com/funding/" + "tslib": "^2.8.1" } }, - "node_modules/@noble/curves": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz", - "integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==", + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "license": "MIT", "dependencies": { - "@noble/hashes": "1.4.0" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=14.18.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@peculiar/x509": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-2.0.0.tgz", + "integrity": "sha512-r10lkuy6BNfRmyYdRAfgu6dq0HOmyIV2OLhXWE3gDEPBdX1b8miztJVyX/UxWhLwemNyDP3CLZHpDxDwSY0xaA==", + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" }, "engines": { - "node": ">= 8" + "node": ">=20.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@react-native/asset-utils": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/asset-utils/-/asset-utils-0.87.0.tgz", + "integrity": "sha512-XUSXMnBmQuBMuMWNWaXMGt4tLzb2yraCVO4b7TFCvEqiCg3ByhbQA0QNmPp+cdWgvmPmptCJwOrBWjPxppXgOA==", + "license": "MIT", + "peer": true, "engines": { - "node": ">= 8" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@react-native/codegen": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.87.0.tgz", + "integrity": "sha512-odcBGR0A9Ee83A/XvzOp9juGRkSQOQcI4ZYs+3H20MySMxvZJ34/2nVHoxdmj0YY0Mn6gF8BjLzYkL0axbHuCA==", + "license": "MIT", + "peer": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.1", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, - "funding": { - "url": "https://opencollective.com/unts" + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-community/cli": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.9.tgz", - "integrity": "sha512-hFJL4cgLPxncJJd/epQ4dHnMg5Jy/7Q56jFvA3MHViuKpzzfTCJCB+pGY54maZbtym53UJON9WTGpM3S81UfjQ==", + "node_modules/@react-native/codegen/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "peer": true, "dependencies": { - "@react-native-community/cli-clean": "13.6.9", - "@react-native-community/cli-config": "13.6.9", - "@react-native-community/cli-debugger-ui": "13.6.9", - "@react-native-community/cli-doctor": "13.6.9", - "@react-native-community/cli-hermes": "13.6.9", - "@react-native-community/cli-server-api": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "@react-native-community/cli-types": "13.6.9", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "rnc-cli": "build/bin.js" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@react-native-community/cli-clean": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-13.6.9.tgz", - "integrity": "sha512-7Dj5+4p9JggxuVNOjPbduZBAP1SUgNhLKVw5noBUzT/3ZpUZkDM+RCSwyoyg8xKWoE4OrdUAXwAFlMcFDPKykA==", + "node_modules/@react-native/codegen/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "peer": true, "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@react-native-community/cli-clean/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native/codegen/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@react-native/codegen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/@react-native/codegen/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", "peer": true, - "engines": { - "node": ">=10" + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/@react-native/codegen/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "peer": true, "engines": { - "node": ">=10.17.0" + "node": ">=12" } }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@react-native/community-cli-plugin": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.87.0.tgz", + "integrity": "sha512-Voaq6e6aSjLsFmOVofP1RHM0wD0Wo51AlIheIDAzXqgBKwEHEz2na4swNZ6sjWLZH/4Wm2ydy+8A/6RCyfht6w==", + "license": "MIT", "peer": true, + "dependencies": { + "@react-native/asset-utils": "0.87.0", + "@react-native/dev-middleware": "0.87.0", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.87.0", + "semver": "^7.1.3" + }, "engines": { - "node": ">=8" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.87.0" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/@react-native/debugger-frontend": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.87.0.tgz", + "integrity": "sha512-TSUVjiX1cYganE6DcWVblDxs67x96u8/KqwhiHHViR6hGGjSIfIdNQcRLOEdXRpucuHB0awIFepBlw+34nTSUg==", + "license": "BSD-3-Clause", "peer": true, "engines": { - "node": ">=6" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@react-native/debugger-shell": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.87.0.tgz", + "integrity": "sha512-8Om5Qm8Ln9zGZ93DJj7gZtlfT0y1wAFc/iWr3GFkIXSa6zwmC3DGzm4sm5eBTtdMl27EXmKACIOGsqztPH3Cvg==", + "license": "MIT", "peer": true, "dependencies": { - "path-key": "^3.0.0" + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" }, "engines": { - "node": ">=8" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native-community/cli-clean/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@react-native/dev-middleware": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.87.0.tgz", + "integrity": "sha512-Fy4RMTCcAn402KGgxQZ4CwoURJdVkoPVxUbN7OTf9xy9xCOmBsOQQ+Xs1n6ULkNMUGcL3ptajpb39ual6r5ixg==", + "license": "MIT", "peer": true, "dependencies": { - "mimic-fn": "^2.1.0" + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.87.0", + "@react-native/debugger-shell": "0.87.0", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native-community/cli-clean/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native-community/cli-clean/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", "peer": true, "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=8.3.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-config": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-13.6.9.tgz", - "integrity": "sha512-rFfVBcNojcMm+KKHE/xqpqXg8HoKl4EC7bFHUrahMJ+y/tZll55+oX/PGG37rzB8QzP2UbMQ19DYQKC1G7kXeg==", + "node_modules/@react-native/gradle-plugin": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.87.0.tgz", + "integrity": "sha512-CFOhPsxN4yS6vYjShSGKQiJHDPQFKmbiJG17zZfJuHTvJiaWz7JkpznE/P1RRKXdgvCmACrj31vxJiCHLlI7Gg==", + "license": "MIT", "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1" + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "node_modules/@react-native/normalize-colors": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.87.0.tgz", + "integrity": "sha512-7aEGyrrquUqpbp+aEgOCp2xr0cTLYhJFCMfUqI4IoJddfwosra9Y5qtbrJU5Z/vBLndCmSIzABrWnHK0lxpwcA==", + "license": "MIT", + "peer": true }, - "node_modules/@react-native-community/cli-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native/virtualized-lists": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.87.0.tgz", + "integrity": "sha512-rYQCtZupN7Iczm8fdGg3uKZUXhrxdZPRkg4BZK3RaQJ2HJ7n5tJNR2QPR3vSgpp5CATQggiGCuom1fNEC3T/8g==", + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=10" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "peer": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.87.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-config/node_modules/import-fresh": { + "node_modules/@simple-libs/child-process-utils": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "peer": true, + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", + "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==", + "dev": true, + "license": "MIT", "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "@simple-libs/stream-utils": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" } }, - "node_modules/@react-native-community/cli-config/node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "node_modules/@simple-libs/stream-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz", + "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "engines": { + "node": ">=22" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://ko-fi.com/dangreen" } }, - "node_modules/@react-native-community/cli-config/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "peer": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } + "node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "license": "MIT", + "peer": true }, - "node_modules/@react-native-community/cli-config/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "peer": true, + "node_modules/@sindresorhus/fnv1a": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", + "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.9.tgz", - "integrity": "sha512-TkN7IdFmGPPvTpAo3nCAH9uwGCPxWBEAwpqEZDrq0NWllI7Tdie8vDpGdrcuCcKalmhq6OYnkXzeBah7O1Ztpw==", - "peer": true, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", "dependencies": { - "serve-static": "^1.13.1" + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-doctor": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-13.6.9.tgz", - "integrity": "sha512-5quFaLdWFQB+677GXh5dGU9I5eg2z6Vg4jOX9vKnc9IffwyIFAyJfCZHrxLSRPDGNXD7biDQUdoezXYGwb6P/A==", - "peer": true, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", "dependencies": { - "@react-native-community/cli-config": "13.6.9", - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-platform-apple": "13.6.9", - "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "peer": true, - "engines": { - "node": ">=6" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, + "node_modules/@types/dns-packet": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", + "integrity": "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-doctor/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, - "engines": { - "node": ">=10.17.0" + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/@types/express-serve-static-core": { + "version": "4.19.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, - "engines": { - "node": ">=6" - } + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", "peer": true, "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", "peer": true, "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/istanbul-lib-report": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, - "engines": { - "node": ">=6" - } + "node_modules/@types/luxon": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.5.tgz", + "integrity": "sha512-jJ41Q4z6ZVO260MNDdHfW7+7a5iMiX8Mr6ZJHcmgrvhZha6dz5704o/lF2kKl6URjH6ivEL97w9xS/MgpJEphg==", + "license": "MIT" }, - "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/express": "*" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-13.6.9.tgz", - "integrity": "sha512-GvwiwgvFw4Ws+krg2+gYj8sR3g05evmNjAHkKIKMkDTJjZ8EdyxbkifRUs1ZCq3TMZy2oeblZBXCJVOH4W7ZbA==", - "peer": true, + "node_modules/@types/multicast-dns": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", + "integrity": "sha512-ib5K4cIDR4Ro5SR3Sx/LROkMDa0BHz0OPaCBL/OSPDsAXEGZ3/KQeS6poBKYVN7BfjXDL9lWNwzyHVgt/wkyCw==", + "license": "MIT", "dependencies": { - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6" + "@types/dns-packet": "*", + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "undici-types": "~7.18.0" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.9.tgz", - "integrity": "sha512-9KsYGdr08QhdvT3Ht7e8phQB3gDX9Fs427NJe0xnoBh+PDPTI2BD5ks5ttsH8CzEw8/P6H8tJCHq6hf2nxd9cw==", - "peer": true, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.2.4", - "logkitty": "^0.7.1" + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@types/yargs-parser": "*" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, - "engines": { - "node": ">=10" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "peerDependencies": { + "@typescript-eslint/parser": "^8.67.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10.17.0" + "node": ">= 4" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, - "engines": { - "node": ">=6" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.9.tgz", - "integrity": "sha512-KoeIHfhxMhKXZPXmhQdl6EE+jGKWwoO9jUVWgBvibpVmsNjo7woaG/tfJMEWfWF3najX1EkQAoJWpCDBMYWtlA==", - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.0.12", - "ora": "^5.4.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, + "node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, "engines": { - "node": ">=10.17.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, "engines": { - "node": ">=6" + "node": ">=6.5" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/abort-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", + "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, + "node_modules/acme-client": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/acme-client/-/acme-client-5.4.0.tgz", + "integrity": "sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==", + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "@peculiar/x509": "^1.11.0", + "asn1js": "^3.0.5", + "axios": "^1.7.2", + "debug": "^4.3.5", + "node-forge": "^1.3.1" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 16" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, + "node_modules/acme-client/node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, "engines": { - "node": ">=6" + "node": ">=20.0.0" } }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.9.tgz", - "integrity": "sha512-CiUcHlGs8vE0CAB4oi1f+dzniqfGuhWPNrDvae2nm8dewlahTBwIcK5CawyGezjcJoeQhjBflh9vloska+nlnw==", - "peer": true, - "dependencies": { - "@react-native-community/cli-platform-apple": "13.6.9" + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@react-native-community/cli-server-api": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-13.6.9.tgz", - "integrity": "sha512-W8FSlCPWymO+tlQfM3E0JmM8Oei5HZsIk5S0COOl0MRi8h0NmHI4WSTF2GCfbFZkcr2VI/fRsocoN8Au4EZAug==", - "peer": true, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { - "@react-native-community/cli-debugger-ui": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^6.2.2" + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", - "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "async-limiter": "~1.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-13.6.9.tgz", - "integrity": "sha512-OXaSjoN0mZVw3nrAwcY1PC0uMfyTd9fz7Cy06dh+EJc+h0wikABsVRzV8cIOPrVV+PPEEXE0DBrH20T2puZzgQ==", + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT", + "peer": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "peer": true, - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@react-native-community/cli-tools/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@react-native-community/cli-tools/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, + "node_modules/any-signal": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.2.0.tgz", + "integrity": "sha512-LndMvYuAPf4rC195lk7oSFuHOYFpOszIYrNYv0gHAvz+aEhE9qPZLhmrIz5pXP2BSsPOXvsuHDXEGaiQhIh9wA==", + "license": "Apache-2.0 OR MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "peer": true, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=10" + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@react-native-community/cli-tools/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/argue-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.1.0.tgz", + "integrity": "sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=22" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://ko-fi.com/dangreen" } }, - "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, - "engines": { - "node": ">=10.17.0" - } + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, - "node_modules/@react-native-community/cli-tools/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT", + "peer": true }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "peer": true, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", "dependencies": { - "p-locate": "^5.0.0" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12.0.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8.0.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.1.tgz", + "integrity": "sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==", + "license": "MIT", "peer": true, "dependencies": { - "mimic-fn": "^2.1.0" - }, + "hermes-parser": "0.36.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "18 || 20 || >=22" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.17.tgz", + "integrity": "sha512-KAUDn1OSS0fmPlGO+NOUMRcOQ/b/shUBH3OgkG73mPgdf+JD/BQ6fHboGxNOxnUmlwcq+lLq3dTkayRPuSfXwg==", + "license": "Apache-2.0", "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.0.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-tools/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/birnam": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/birnam/-/birnam-1.0.0.tgz", + "integrity": "sha512-lropqEwi52w2aoOQQ+V4MGc8KP5mVef9DaEKv73dkVXb8UN6moQ7j/f19NesXPYt/l+8MkkHtMBAQsBKR8BqgQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0", + "weald": "^1.1.2" } }, - "node_modules/@react-native-community/cli-tools/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native-community/cli-tools/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, - "engines": { - "node": ">=6" + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/@react-native-community/cli-tools/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/blockstore-core": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-7.0.1.tgz", + "integrity": "sha512-HgLzdH8oL6DrMC6J7snxmV0TXzJ/BMPSbskH4v7BBuUO9foDBOMEKUfoMX9yoBpxbH+r3Z9afXHDgXlTifMxHQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/logger": "^6.2.4", + "abort-error": "^1.0.2", + "interface-blockstore": "^7.0.0", + "interface-store": "^8.0.0", + "it-all": "^3.0.9", + "it-filter": "^3.1.4", + "it-merge": "^3.0.12", + "multiformats": "^14.0.0" } }, - "node_modules/@react-native-community/cli-types": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-13.6.9.tgz", - "integrity": "sha512-RLxDppvRxXfs3hxceW/mShi+6o5yS+kFPnPqZTaMKKR5aSg7LwDpLQW4K2D22irEG8e6RKDkZUeH9aL3vO2O0w==", - "peer": true, + "node_modules/blockstore-fs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-4.0.1.tgz", + "integrity": "sha512-UXTG85jT+b4xumiHwqyZBnAqfxCqGgGl5ZszSGpnje7yEIrv2pO3NzttAz9DHwzaZxE2nBPMaPlg+SJ8VGlPrg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "joi": "^17.2.1" + "abort-error": "^1.0.2", + "interface-blockstore": "^7.0.0", + "interface-store": "^8.0.0", + "it-glob": "^3.0.4", + "it-map": "^3.1.4", + "it-parallel-batch": "^3.0.9", + "multiformats": "^14.0.0", + "race-signal": "^2.0.0", + "steno": "^4.0.2" } }, - "node_modules/@react-native-community/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "peer": true, - "engines": { - "node": "^12.20.0 || >=14" + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": "20 || >=22" } }, - "node_modules/@react-native-community/cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "peer": true, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/browser-readablestream-to-it": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.12.tgz", + "integrity": "sha512-VDAcuM39JVtxZ7auqE2p0zHYk1fq+pac0cWLOQJ48MIChTZ1RjCR2PYCdL3kIisst7oGZCxYrJhfHlbNYIa0Tg==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/@react-native-community/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, "engines": { - "node": ">=8" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@react-native-community/cli/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", "peer": true, - "engines": { - "node": ">=10.17.0" + "dependencies": { + "node-int64": "^0.4.0" } }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/@react-native-community/cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "peer": true, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "p-locate": "^4.1.0" + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=10.16.0" } }, - "node_modules/@react-native-community/cli/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "peer": true, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/call-bind-apply-helpers": { + "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==", + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/@react-native-community/cli/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@react-native-community/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/cborg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-6.1.1.tgz", + "integrity": "sha512-Ci8+V1OMFtqea8EXicsATqV+3kEqCGXCicM+5MEzpKTxqy58WMzhJUjtUqAjfWpKPZ0wpG8F+u5mC8Xy/0Zevg==", + "license": "Apache-2.0", + "bin": { + "cborg": "lib/bin.js" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "peer": true, "dependencies": { - "p-limit": "^2.2.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native-community/cli/node_modules/path-exists": { + "node_modules/chalk/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "peer": true, "engines": { "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native-community/cli/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli/node_modules/supports-color": { + "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -5051,7262 +4421,3105 @@ "node": ">=8" } }, - "node_modules/@react-native/assets-registry": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", - "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", - "peer": true, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, "engines": { - "node": ">=18" + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.87.tgz", - "integrity": "sha512-+vJYpMnENFrwtgvDfUj+CtVJRJuUnzAUYT0/Pb68Sq9RfcZ5xdcCuUgyf7JO+akW2VTBoJY427wkcxU30qrWWw==", - "peer": true, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", "dependencies": { - "@react-native/codegen": "0.74.87" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=18" + "node": ">= 6" } }, - "node_modules/@react-native/babel-preset": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.87.tgz", - "integrity": "sha512-hyKpfqzN2nxZmYYJ0tQIHG99FQO0OWXp/gVggAfEUgiT+yNKas1C60LuofUsK7cd+2o9jrpqgqW4WzEDZoBlTg==", + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", "peer": true, "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.74.87", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" }, - "engines": { - "node": ">=18" + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" }, - "peerDependencies": { - "@babel/core": "*" + "engines": { + "node": ">=12.13.0" } }, - "node_modules/@react-native/codegen": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.87.tgz", - "integrity": "sha512-GMSYDiD+86zLKgMMgz9z0k6FxmRn+z6cimYZKkucW4soGbxWsbjUAZoZ56sJwt2FJ3XVRgXCrnOCgXoH/Bkhcg==", + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "license": "Apache-2.0", "peer": true, "dependencies": { - "@babel/parser": "^7.20.0", - "glob": "^7.1.1", - "hermes-parser": "0.19.1", - "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.87.tgz", - "integrity": "sha512-EgJG9lSr8x3X67dHQKQvU6EkO+3ksVlJHYIVv6U/AmW9dN80BEFxgYbSJ7icXS4wri7m4kHdgeq2PQ7/3vvrTQ==", - "peer": true, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT", + "peer": true + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", "dependencies": { - "@react-native-community/cli-server-api": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "@react-native/dev-middleware": "0.74.87", - "@react-native/metro-babel-transformer": "0.74.87", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "querystring": "^0.2.1", - "readline": "^1.3.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" + "is-regexp": "^3.0.0" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, - "engines": { - "node": ">=10.17.0" - } + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true }, - "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", "peer": true, "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "peer": true, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true - }, - "node_modules/@react-native/community-cli-plugin/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", "peer": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, "engines": { - "node": ">=6" + "node": ">= 0.10.0" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "peer": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "ms": "2.0.0" } }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.87.tgz", - "integrity": "sha512-MN95DJLYTv4EqJc+9JajA3AJZSBYJz2QEJ3uWlHrOky2vKrbbRVaW1ityTmaZa2OXIvNc6CZwSRSE7xCoHbXhQ==", + "node_modules/connect/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "peer": true, "engines": { - "node": ">=18" + "node": ">= 0.8" } }, - "node_modules/@react-native/dev-middleware": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.87.tgz", - "integrity": "sha512-7TmZ3hTHwooYgIHqc/z87BMe1ryrIqAUi+AF7vsD+EHCGxHFdMjSpf1BZ2SUPXuLnF2cTiTfV2RwhbPzx0tYIA==", + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", "peer": true, "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.87", - "@rnx-kit/chromium-edge-launcher": "^1.0.0", - "chrome-launcher": "^0.15.2", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, - "dependencies": { - "ms": "2.0.0" + "node": ">= 0.8" } }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { + "node_modules/connect/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", "peer": true }, - "node_modules/@react-native/dev-middleware/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", "peer": true, "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" + "ee-first": "1.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", - "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "peer": true, - "dependencies": { - "async-limiter": "~1.0.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.87.tgz", - "integrity": "sha512-T+VX0N1qP+U9V4oAtn7FTX7pfsoVkd1ocyw9swYXgJqU2fK7hC9famW7b3s3ZiufPGPr1VPJe2TVGtSopBjL6A==", - "peer": true, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.87.tgz", - "integrity": "sha512-M5Evdn76CuVEF0GsaXiGi95CBZ4IWubHqwXxV9vG9CC9kq0PSkoM2Pn7Lx7dgyp4vT7ccJ8a3IwHbe+5KJRnpw==", - "peer": true, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.87.tgz", - "integrity": "sha512-UsJCO24sNax2NSPBmV1zLEVVNkS88kcgAiYrZHtYSwSjpl4WZ656tIeedBfiySdJ94Hr3kQmBYLipV5zk0NI1A==", - "peer": true, + "node_modules/conventional-changelog-angular": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-9.4.0.tgz", + "integrity": "sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.74.87", - "hermes-parser": "0.19.1", - "nullthrows": "^1.1.1" + "@conventional-changelog/template": "^1.4.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">=22" } }, - "node_modules/@react-native/normalize-colors": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", - "integrity": "sha512-Xh7Nyk/MPefkb0Itl5Z+3oOobeG9lfLb7ZOY2DKpFnoCE1TzBmib9vMNdFaLdSxLIP+Ec6icgKtdzYg8QUPYzA==", - "peer": true - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.87.tgz", - "integrity": "sha512-lsGxoFMb0lyK/MiplNKJpD+A1EoEUumkLrCjH4Ht+ZlG8S0BfCxmskLZ6qXn3BiDSkLjfjI/qyZ3pnxNBvkXpQ==", - "peer": true, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.4.0.tgz", + "integrity": "sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==", + "dev": true, + "license": "ISC", "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" + "@conventional-changelog/template": "^1.4.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": "^18.2.6", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "node": ">=22" } }, - "node_modules/@rnx-kit/chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==", - "peer": true, + "node_modules/conventional-commits-parser": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz", + "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "^18.0.0", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" }, - "engines": { - "node": ">=14.15" - } - }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/@types/node": { - "version": "18.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", - "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", - "peer": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "peer": true, "bin": { - "mkdirp": "bin/cmd.js" + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=10" + "node": ">=22" } }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "peer": true - }, - "node_modules/@sideway/pinpoint": { + "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "peer": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT", "peer": true }, - "node_modules/@sindresorhus/fnv1a": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", - "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "peer": true, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "jiti": "2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" } }, - "node_modules/@types/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==", - "dev": true, + "node_modules/cron": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz", + "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==", + "license": "MIT", "dependencies": { - "@types/node": "*" + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" + }, + "engines": { + "node": ">=18.x" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/intcreator" } }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dev": true, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { - "@types/node": "*" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/dns-packet": { - "version": "5.6.5", - "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", - "integrity": "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==", + "node_modules/datastore-core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-12.0.1.tgz", + "integrity": "sha512-pfgIE5LG0Z5oyc6TxiLaF/0j4Gh1Yrs2+Ck/qssf9Oxw4s4YKC3zpqifuzThnKakAFWONr/vRv+66Koc0yJvyA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@types/node": "*" + "@libp2p/logger": "^6.2.4", + "abort-error": "^1.0.2", + "interface-datastore": "^10.0.0", + "interface-store": "^8.0.0", + "it-drain": "^3.0.10", + "it-filter": "^3.1.4", + "it-map": "^3.1.4", + "it-merge": "^3.0.12", + "it-pipe": "^3.0.1", + "it-sort": "^3.0.9", + "it-take": "^3.0.9" } }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, + "node_modules/datastore-fs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-12.0.1.tgz", + "integrity": "sha512-e9Hp6h/STYxaUFIbJHTavXze2kMGeKHHVqfdqqjzolB0K7CspBsgqZ9ePEGSyghWzeZMlLSqmSCLUokkVj9p0A==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "abort-error": "^1.0.2", + "datastore-core": "^12.0.0", + "interface-datastore": "^10.0.0", + "interface-store": "^8.0.0", + "it-glob": "^3.0.4", + "it-map": "^3.1.4", + "it-parallel-batch": "^3.0.9", + "race-signal": "^2.0.0", + "steno": "^4.0.2" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", - "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" } }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "peer": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "peer": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "peer": true, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/luxon": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", - "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } }, - "node_modules/@types/multer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.2.0.tgz", - "integrity": "sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, + "license": "MIT" + }, + "node_modules/delay": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-7.0.0.tgz", + "integrity": "sha512-C3vaGs818qzZjCvVJ98GQUMVyWeg7dr5w2Nwwb2t5K8G98jOyyVO2ti2bKYk5yoYElqH3F2yA53ykuEnwD6MCg==", "license": "MIT", "dependencies": { - "@types/express": "*" + "random-int": "^3.1.0", + "unlimited-timeout": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/multicast-dns": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@types/multicast-dns/-/multicast-dns-7.2.4.tgz", - "integrity": "sha512-ib5K4cIDR4Ro5SR3Sx/LROkMDa0BHz0OPaCBL/OSPDsAXEGZ3/KQeS6poBKYVN7BfjXDL9lWNwzyHVgt/wkyCw==", - "dependencies": { - "@types/dns-packet": "*", - "@types/node": "*" + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@types/murmurhash3js-revisited": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.3.tgz", - "integrity": "sha512-QvlqvYtGBYIDeO8dFdY4djkRubcrc+yTJtBc7n8VZPlJDUS/00A+PssbvERM8f9bYRmcaSEHPZgZojeQj7kzAA==" - }, - "node_modules/@types/node": { - "version": "20.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", - "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", - "dependencies": { - "undici-types": "~5.26.4" + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "peer": true, - "dependencies": { - "@types/node": "*" + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@types/sinon": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", - "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "@types/sinonjs__fake-timers": "*" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", - "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==" + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.412", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz", + "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==", + "license": "ISC", + "peer": true }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", "peer": true }, - "node_modules/@types/ws": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", - "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", - "dependencies": { - "@types/node": "*" + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "peer": true, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "once": "^1.4.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "peer": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", - "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/type-utils": "8.59.1", - "@typescript-eslint/utils": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.59.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">=6" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", - "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "is-arrayish": "^0.2.1" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", - "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", - "dev": true, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/utils": "8.59.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, + "stackframe": "^1.3.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", - "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", - "dev": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", - "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", - "dev": true, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.1", - "@typescript-eslint/tsconfig-utils": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "es-errors": "^1.3.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", - "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", - "dev": true, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "eslint-visitor-keys": "^5.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/es-toolkit": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.51.0.tgz", + "integrity": "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==", "dev": true, "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, "engines": { - "node": "20 || >=22" + "node": ">=6" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/eslint": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.0.tgz", + "integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", + "workspaces": [ + "packages/*" + ], "dependencies": { - "brace-expansion": "^5.0.5" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "18 || 20 || >=22" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", - "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "bin": { + "eslint-config-prettier": "bin/cli.js" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint-config-prettier" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "eslint": ">=7.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", - "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", - "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", - "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/project-service": "8.59.1", - "@typescript-eslint/tsconfig-utils": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", - "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", - "peer": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "eslint-visitor-keys": "^5.0.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=0.10" } }, - "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-2-Clause", "dependencies": { - "balanced-match": "^4.0.2" + "estraverse": "^5.2.0" }, "engines": { - "node": "20 || >=22" + "node": ">=4.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "BSD-2-Clause", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=4.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "brace-expansion": "^5.0.5" - }, + "license": "BSD-2-Clause", "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", - "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", - "dev": true, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.1", - "@typescript-eslint/types": "^8.59.1", - "debug": "^4.4.3" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 0.6" } }, - "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", - "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", - "dev": true, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", - "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", - "dev": true, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">=0.8.x" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "peer": true }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.10.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://opencollective.com/express" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", - "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", - "dev": true, + "node_modules/express-rate-limit": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz", + "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1" + "debug": "^4.4.3", + "ip-address": "^10.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", - "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "express": ">= 4.11" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", - "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", - "dev": true, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 0.6" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", - "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", - "dev": true, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.1", - "@typescript-eslint/tsconfig-utils": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "ms": "2.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", - "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/@typescript-eslint/utils/node_modules/balanced-match": { + "node_modules/fast-copy": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.4.tgz", + "integrity": "sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA==", + "license": "MIT" }, - "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8.6.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.5" + "is-glob": "^4.0.1" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 6" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "license": "MIT" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/abort-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", - "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "peer": true - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "peer": true, - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-fragments/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ansi-fragments/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "peer": true - }, - "node_modules/ansi-fragments/node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "peer": true - }, - "node_modules/ansi-fragments/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-fragments/node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-signal": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.1.1.tgz", - "integrity": "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "peer": true - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "peer": true - }, - "node_modules/asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", - "dependencies": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/ast-types": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", - "peer": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "peer": true - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "peer": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "peer": true, - "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.17", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.17.tgz", - "integrity": "sha512-KAUDn1OSS0fmPlGO+NOUMRcOQ/b/shUBH3OgkG73mPgdf+JD/BQ6fHboGxNOxnUmlwcq+lLq3dTkayRPuSfXwg==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/blockstore-core": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.4.1.tgz", - "integrity": "sha512-peXfL9ZLx1cb84QALocMjhT8CsQ4JsreI/AitlN1inipSdC/G+jcYVJCqeCD5ecSTv/0LMpg8NlAPH/eBYZLjA==", - "dependencies": { - "@libp2p/logger": "^4.0.6", - "err-code": "^3.0.1", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.0", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-merge": "^3.0.3", - "it-pushable": "^3.2.3", - "multiformats": "^13.0.1" - } - }, - "node_modules/blockstore-fs": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/blockstore-fs/-/blockstore-fs-1.1.11.tgz", - "integrity": "sha512-tySmmketfD9CsAeJCdsHTUdy6I2AxLdAlbcOx+KdnA8J7UJ7uKfXxQYNzmx6ZRpXVp/7ccNUWSugwoUstoW83A==", - "dependencies": { - "blockstore-core": "^4.0.0", - "fast-write-atomic": "^0.2.1", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.0", - "it-glob": "^3.0.1", - "it-map": "^3.0.5", - "it-parallel-batch": "^3.0.4", - "multiformats": "^13.0.1" - } - }, - "node_modules/body-parser": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", - "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-readablestream-to-it": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.7.tgz", - "integrity": "sha512-g1Aznml3HmqTLSXylZhGwdfnAa67+vlNAYhT9ROJZkAxY7yYmWusND10olvCMPe4sVhZyVwn5tPkRzOg85kBEg==" - }, - "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "peer": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", - "dev": true, - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "peer": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "peer": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001809", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", - "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0", - "peer": true - }, - "node_modules/cborg": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.2.3.tgz", - "integrity": "sha512-XBFbEJ6WMfn9L7woc2t+EzOxF8vGqddoopKBbrhIvZBt2WIUgSlT8xLmM6Aq1xv8eWt4yOSjwxWjYeuHU3CpJA==", - "bin": { - "cborg": "lib/bin.js" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "peer": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "peer": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "peer": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-regexp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", - "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", - "dependencies": { - "is-regexp": "^3.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "peer": true - }, - "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "peer": true - }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "peer": true, - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "peer": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "peer": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true - }, - "node_modules/connect/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "peer": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/connect/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", - "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", - "dev": true, - "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "peer": true - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-js-compat": { - "version": "3.38.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz", - "integrity": "sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==", - "peer": true, - "dependencies": { - "browserslist": "^4.23.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "peer": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz", - "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==", - "dev": true, - "dependencies": { - "jiti": "^1.19.1" - }, - "engines": { - "node": ">=v16" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=8.2", - "typescript": ">=4" - } - }, - "node_modules/cron": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", - "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", - "dependencies": { - "@types/luxon": "~3.4.0", - "luxon": "~3.4.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/datastore-core": { - "version": "9.2.9", - "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-9.2.9.tgz", - "integrity": "sha512-wraWTPsbtdE7FFaVo3pwPuTB/zXsgwGGAm8BgBYwYAuzZCTS0MfXmd/HH1vR9s0/NFFjOVmBkGiWCvKxZ+QjVw==", - "dependencies": { - "@libp2p/logger": "^4.0.6", - "err-code": "^3.0.1", - "interface-datastore": "^8.0.0", - "interface-store": "^5.0.0", - "it-drain": "^3.0.5", - "it-filter": "^3.0.4", - "it-map": "^3.0.5", - "it-merge": "^3.0.3", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-sort": "^3.0.4", - "it-take": "^3.0.4" - } - }, - "node_modules/datastore-fs": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/datastore-fs/-/datastore-fs-9.1.9.tgz", - "integrity": "sha512-uCU1BaNENmpjduYaDHQFC+u9YgQG7piPaWaFIvEtoXIYvmnvLmHJFDLsgTEB04TPdvlNLD/YlO/9zEDklE5tYg==", - "dependencies": { - "datastore-core": "^9.0.0", - "fast-write-atomic": "^0.2.1", - "interface-datastore": "^8.0.0", - "interface-store": "^5.0.0", - "it-glob": "^3.0.1", - "it-map": "^3.0.5", - "it-parallel-batch": "^3.0.4" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/dayjs": { - "version": "1.11.12", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", - "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==", - "peer": true - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-7.2.2.tgz", - "integrity": "sha512-AD7TrdNNPXRZIGw63dw+lnGmT4v7ggZC5NHNJgAYWm5njrwoze1q5JSAW9YuLy2tjnoLUG/r8FEB93MCh9QJPg==", - "dependencies": { - "execa": "^7.1.1" - }, - "engines": { - "node": ">= 16" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/default-gateway/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "peer": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delay": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", - "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "peer": true - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-browser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", - "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==" - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.412", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz", - "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==", - "license": "ISC", - "peer": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/envinfo": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", - "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", - "peer": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "peer": true, - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "peer": true, - "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-n": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", - "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", - "dev": true, - "dependencies": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/chalk": { - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-iterator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", - "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==" - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "peer": true - }, - "node_modules/express": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", - "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.15.1", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz", - "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-write-atomic": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz", - "integrity": "sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==" - }, - "node_modules/fast-xml-parser": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.7.tgz", - "integrity": "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "peer": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "peer": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", - "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", - "dev": true, - "dependencies": { - "locate-path": "^7.2.0", - "path-exists": "^5.0.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "peer": true - }, - "node_modules/flow-parser": { - "version": "0.243.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.243.0.tgz", - "integrity": "sha512-HCDBfH+kZcY5etWYeAqatjW78gkIryzb9XixRsA8lGI1uyYc7aCpElkkO4H+KIpoyQMiY0VAZPI4cyac3wQe8w==", - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/freeport-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/freeport-promise/-/freeport-promise-2.0.0.tgz", - "integrity": "sha512-dwWpT1DdQcwrhmRwnDnPM/ZFny+FtzU+k50qF2eid3KxaQDsMiBrwo1i0G3qSugkN5db6Cb0zgfc68QeTOpEFg==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function-timeout": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", - "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", - "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-raw-commits": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "dev": true, - "dependencies": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "node_modules/glob": { - "version": "7.2.3", - "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", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, - "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/go-get-folder-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/go-get-folder-size/-/go-get-folder-size-0.5.5.tgz", - "integrity": "sha512-UB8PlOhxUXWPXvGivkYWhFWXXIDMxxKNXsu+W27APjNgfE11Gc+6+UODqUwC41neE829zQyhqmeY1p1AEBGXcg==", - "hasInstallScript": true, - "dependencies": { - "std-env": "^3.7.0" - }, - "bin": { - "go-get-folder-size": "bin/cli.js" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "peer": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/gts": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/gts/-/gts-5.3.1.tgz", - "integrity": "sha512-P9F+krJkGOkisUX+P9pfUas1Xy+U+CxBFZT62uInkJbgvZpnW1ug/pIcMJJmLOthMq1J88lpQUGhXDC9UTvVcw==", - "dev": true, - "dependencies": { - "@typescript-eslint/eslint-plugin": "5.62.0", - "@typescript-eslint/parser": "5.62.0", - "chalk": "^4.1.2", - "eslint": "8.57.0", - "eslint-config-prettier": "9.1.0", - "eslint-plugin-n": "15.7.0", - "eslint-plugin-prettier": "5.1.3", - "execa": "^5.0.0", - "inquirer": "^7.3.3", - "json5": "^2.1.3", - "meow": "^9.0.0", - "ncp": "^2.0.0", - "prettier": "3.2.5", - "rimraf": "3.0.2", - "write-file-atomic": "^4.0.0" - }, - "bin": { - "gts": "build/src/cli.js" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "typescript": ">=3" - } - }, - "node_modules/gts/node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/gts/node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/gts/node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/gts/node_modules/chalk": { - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/gts/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/gts/node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/gts/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/gts/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gts/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/gts/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gts/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gts/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/gts/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gts/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/gts/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gts/node_modules/supports-color": { - "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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gts/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gts/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/hamt-sharding": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.6.tgz", - "integrity": "sha512-nZeamxfymIWLpVcAN0CRrb7uVq3hCOGj9IcL6NMA6VVCVWqj+h9Jo/SmaWuS92AEDf1thmHsM5D5c70hM3j2Tg==", - "dependencies": { - "sparse-array": "^1.3.1", - "uint8arrays": "^5.0.1" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/helia": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.6.tgz", - "integrity": "sha512-qa/ADpLRRqzQHchd0XqpC4cVj9+OC4BA4QldN6dxBO0OLuGoUEo6kGrbpkygqNQ8RupA1ViFBWP7TqX6OIrppg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/libp2p-noise": "^15.0.0", - "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/block-brokers": "^3.0.4", - "@helia/delegated-routing-v1-http-api-client": "^3.0.0", - "@helia/interface": "^4.3.1", - "@helia/routers": "^1.1.1", - "@helia/utils": "^0.3.3", - "@libp2p/autonat": "^1.0.13", - "@libp2p/bootstrap": "^10.0.16", - "@libp2p/circuit-relay-v2": "^1.0.16", - "@libp2p/dcutr": "^1.0.13", - "@libp2p/identify": "^2.0.0", - "@libp2p/interface": "^1.1.4", - "@libp2p/kad-dht": "^12.0.8", - "@libp2p/keychain": "^4.0.9", - "@libp2p/logger": "^4.0.7", - "@libp2p/mdns": "^10.0.16", - "@libp2p/mplex": "^10.0.16", - "@libp2p/ping": "^1.0.12", - "@libp2p/tcp": "^9.0.16", - "@libp2p/upnp-nat": "^1.0.14", - "@libp2p/webrtc": "^4.0.20", - "@libp2p/websockets": "^8.0.16", - "@libp2p/webtransport": "^4.0.20", - "@multiformats/dns": "^1.0.1", - "blockstore-core": "^4.4.0", - "datastore-core": "^9.2.9", - "interface-blockstore": "^5.2.10", - "interface-datastore": "^8.2.11", - "ipns": "^9.0.0", - "libp2p": "^1.3.0", - "multiformats": "^13.1.0" - } - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "dev": true - }, - "node_modules/hermes-estree": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", - "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==", - "peer": true - }, - "node_modules/hermes-parser": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", - "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", - "peer": true, - "dependencies": { - "hermes-estree": "0.19.1" - } - }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "peer": true, - "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/husky": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", - "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", - "dev": true, - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/image-size": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", - "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", - "license": "MIT", - "peer": true, - "dependencies": { - "queue": "6.0.2" - }, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.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.", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/interface-blockstore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.3.0.tgz", - "integrity": "sha512-MsOE2H9duZdpxXk7KVOQLaQnX4LfqHegshKxSr4b/YD44t13koT5Lg4lphZ0FwB5uvtvqQPm7LNcS0T/5Z2YUQ==", - "dependencies": { - "interface-store": "6.0.0", - "multiformats": "^13.0.1" - } - }, - "node_modules/interface-blockstore/node_modules/interface-store": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", - "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" - }, - "node_modules/interface-datastore": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.0.tgz", - "integrity": "sha512-RM/rTSmRcnoCwGZIHrPm+nlGYVoT4R0lcFvNnDyhdFT4R6BuHHhfFP47UldVEjs98SfxLuMhaNMsyjI918saHw==", - "dependencies": { - "interface-store": "6.0.0", - "uint8arrays": "^5.0.2" - } - }, - "node_modules/interface-datastore/node_modules/interface-store": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", - "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" - }, - "node_modules/interface-store": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.8.tgz", - "integrity": "sha512-7na81Uxkl0vqk0CBPO5PvyTkdaJBaezwUJGsMOz7riPOq0rJt+7W31iaopaMICWea/iykUsvNlPx/Tc+MxC3/w==" - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ip-address": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", - "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ip-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", - "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" }, - "node_modules/ipfs-unixfs": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.4.tgz", - "integrity": "sha512-RE4nyx5qgG2w7JOLj0Y0D7SfAR1ZkEdramNaBx0OSD4DlQ2Y2NORgc4FHfej3Pgy31v+QISDVP1pQJhdv3bUUg==", + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", "dependencies": { - "err-code": "^3.0.1", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8" + "reusify": "^1.0.4" } }, - "node_modules/ipfs-unixfs-exporter": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-exporter/-/ipfs-unixfs-exporter-13.5.0.tgz", - "integrity": "sha512-s1eWXzoyhQFNEAB1p+QE3adjhW+lBdgpORmmjiCLiruHs5z7T5zsAgRVcWpM8LWYhq2flRtJHObb7Hg73J+oLQ==", - "dependencies": { - "@ipld/dag-cbor": "^9.0.0", - "@ipld/dag-json": "^10.1.7", - "@ipld/dag-pb": "^4.0.0", - "@multiformats/murmur3": "^2.0.0", - "err-code": "^3.0.1", - "hamt-sharding": "^3.0.0", - "interface-blockstore": "^5.0.0", - "ipfs-unixfs": "^11.0.0", - "it-filter": "^3.0.2", - "it-last": "^3.0.2", - "it-map": "^3.0.3", - "it-parallel": "^3.0.0", - "it-pipe": "^3.0.1", - "it-pushable": "^3.1.0", - "multiformats": "^13.0.0", - "p-queue": "^8.0.1", - "progress-events": "^1.0.0" + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", + "peer": true, + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" } }, - "node_modules/ipfs-unixfs-importer": { - "version": "15.2.5", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.2.5.tgz", - "integrity": "sha512-K8tybgmhOthQ/ThPcjohYnTaVhv1hi3YKo+gPREBXcMUUG7Yd2HYfz1jExuOfhaMzICl0hDKwUMJsaU6FXMJew==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@ipld/dag-pb": "^4.1.0", - "@multiformats/murmur3": "^2.1.8", - "err-code": "^3.0.1", - "hamt-sharding": "^3.0.6", - "interface-blockstore": "^5.2.10", - "interface-store": "^5.1.8", - "ipfs-unixfs": "^11.0.0", - "it-all": "^3.0.4", - "it-batch": "^3.0.4", - "it-first": "^3.0.4", - "it-parallel-batch": "^3.0.4", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "rabin-wasm": "^0.1.5", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.3" + "bser": "2.1.1" } }, - "node_modules/ipns": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-9.1.0.tgz", - "integrity": "sha512-up2o1Qx9tSSfh73k69j3/Acacua6JbffTe5xA8+/fv6ibkQyhriMPHlgae1896DwmQkJrusKgBs7EAOi3yrO2w==", - "dependencies": { - "@libp2p/crypto": "^4.0.0", - "@libp2p/interface": "^1.1.0", - "@libp2p/logger": "^4.0.3", - "@libp2p/peer-id": "^4.0.3", - "cborg": "^4.0.1", - "err-code": "^3.0.1", - "interface-datastore": "^8.1.0", - "multiformats": "^13.0.0", - "protons-runtime": "^5.2.1", - "timestamp-nano": "^1.0.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.0.1" + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "to-regex-range": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "peer": true, - "bin": { - "is-docker": "cli.js" + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=16" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "peer": true, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT", + "peer": true + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/is-ip": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", - "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", "dependencies": { - "ip-regex": "^5.0.0", - "super-regex": "^0.2.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/is-loopback-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", - "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">= 0.6" } }, - "node_modules/is-obj": { + "node_modules/freeport-promise": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, + "resolved": "https://registry.npmjs.org/freeport-promise/-/freeport-promise-2.0.0.tgz", + "integrity": "sha512-dwWpT1DdQcwrhmRwnDnPM/ZFny+FtzU+k50qF2eid3KxaQDsMiBrwo1i0G3qSugkN5db6Cb0zgfc68QeTOpEFg==", + "license": "Apache-2.0 OR MIT", "engines": { - "node": ">=8" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "node_modules/function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "peer": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/is-text-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", - "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", - "dev": true, - "dependencies": { - "text-extensions": "^2.0.0" - }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { - "node": ">=8" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "peer": true, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "peer": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "peer": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "node_modules/get-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", + "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==", + "license": "MIT" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "peer": true, + "node_modules/get-port": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.2.0.tgz", + "integrity": "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/it-all": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.6.tgz", - "integrity": "sha512-HXZWbxCgQZJfrv5rXvaVeaayXED8nTKx9tj9fpBhmcUJcedVZshMMMqTj0RG2+scGypb9Ut1zd1ifbf3lA8L+Q==" - }, - "node_modules/it-batch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.6.tgz", - "integrity": "sha512-pQAAlSvJ4aV6xM/6LRvkPdKSKXxS4my2fGzNUxJyAQ8ccFdxPmK1bUuF5OoeUDkcdrbs8jtsmc4DypCMrGY6sg==" - }, - "node_modules/it-byte-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-1.1.0.tgz", - "integrity": "sha512-WWponBWdKEa6o2U3NX+wGMY8X1EkWXcQvpC+3CUqKb4ZzK30q3EPqiTjFxLf9tNVgdF/MNAtx/XclpVfgaz9KQ==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { - "it-queueless-pushable": "^1.0.0", - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.4.8" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/it-drain": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.7.tgz", - "integrity": "sha512-vy6S1JKjjHSIFHgBpLpD1zhkCRl3z1zYWUxE14+kAYf+BL9ssWSFImJfhl361IIcwr0ofw8etzg11VqqB+ntUA==" + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" }, - "node_modules/it-filter": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.1.tgz", - "integrity": "sha512-TOXmVuaSkxlLp2hXKoMTra0WMZMKVFxE3vSsbIA+PbADNCBAHhjJ/lM31vBOUTddHMO34Ku++vU8T9PLlBxQtg==", + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "it-peekable": "^3.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/it-first": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.6.tgz", - "integrity": "sha512-ExIewyK9kXKNAplg2GMeWfgjUcfC1FnUXz/RPfAvIXby+w7U4b3//5Lic0NV03gXT8O/isj5Nmp6KiY0d45pIQ==" - }, - "node_modules/it-foreach": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.1.tgz", - "integrity": "sha512-ID4Gxnavk/LVQLQESAQ9hR6dR63Ih6X+8VdxEktX8rpz2dCGAbZpey/eljTNbMfV2UKXHiu6UsneoNBZuac97g==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", "dependencies": { - "it-peekable": "^3.0.0" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/it-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-3.0.1.tgz", - "integrity": "sha512-IUWE9f6XVUJLugK7pQmQPqTWj4BiQJhufnvxfsCmNIGEDQEkKVs3Ld9gFZq/Vude6g/OpndhsiuFrA730Bc59A==", + "node_modules/global-directory": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", + "dev": true, + "license": "MIT", "dependencies": { - "fast-glob": "^3.3.2" + "ini": "6.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/it-last": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.6.tgz", - "integrity": "sha512-M4/get95O85u2vWvWQinF8SJUc/RPC5bWTveBTYXvlP2q5TF9Y+QhT3nz+CRCyS2YEc66VJkyl/da6WrJ0wKhw==" - }, - "node_modules/it-length": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-length/-/it-length-3.0.6.tgz", - "integrity": "sha512-R7bxHAzpRzYz7vghc2DDH7x4KXvEkeLfN/h316++jzbkEHIRXbEPLbE20p5yrqqBdOeK6/FRUDuHlTJ0H1hysw==" - }, - "node_modules/it-length-prefixed": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.1.0.tgz", - "integrity": "sha512-kx2UTJuy7/lsT3QUzf50NjfxU1Z4P4wlvYp6YnR5Nc61P8XKfy+QtiJi1VLojA+Kea7vMbB4002rIij1Ol9hcw==", - "dependencies": { - "it-reader": "^6.0.1", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.1", - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.1" - }, + "node_modules/globals": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/it-length-prefixed-stream": { + "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-1.2.0.tgz", - "integrity": "sha512-vX7dzSl/2UMYYsAr0FQdPNVR5xYEETaeboZ+eXxNBjgARuvxnWA6OedW8lC5/J3ebMTC98JhA3eH76eTijUOsA==", - "dependencies": { - "it-byte-stream": "^1.0.0", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8" + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/it-map": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.1.tgz", - "integrity": "sha512-9bCSwKD1yN1wCOgJ9UOl+46NQtdatosPWzxxUk2NdTLwRPXLh+L7iwCC9QKsbgM60RQxT/nH8bKMqm3H/o8IHQ==", - "dependencies": { - "it-peekable": "^3.0.0" - } + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "peer": true }, - "node_modules/it-merge": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.5.tgz", - "integrity": "sha512-2l7+mPf85pyRF5pqi0dKcA54E5Jm/2FyY5GsOaN51Ta0ipC7YZ3szuAsH8wOoB6eKY4XsU4k2X+mzPmFBMayEA==", + "node_modules/hamt-sharding": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.8.tgz", + "integrity": "sha512-pEh4hYa4ZyDJ2jWBTz7MVqTTGC3va3iEIMDJfbCKskR9rzHRd8HVwUyH5nLiLxK6VCFWIJ4vnlAEn4Hwx49Rbg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "it-pushable": "^3.2.3" + "sparse-array": "^1.3.1", + "uint8arrays": "^6.1.1" } }, - "node_modules/it-ndjson": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.7.tgz", - "integrity": "sha512-V3IskT5RCVtov1u6sC9gkg0uD02qe8yPoVzBZVaRx+YkuMdpFd6opiAwfYovNd/NEbqo9mBN8wJLtw6vD0Xiqg==" - }, - "node_modules/it-pair": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/it-pair/-/it-pair-2.0.6.tgz", - "integrity": "sha512-5M0t5RAcYEQYNG5BV7d7cqbdwbCAp5yLdzvkxsZmkuZsLbTdZzah6MQySYfaAQjNDCq6PUnDt0hqBZ4NwMfW6g==", + "node_modules/hamt-sharding/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/it-parallel": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.8.tgz", - "integrity": "sha512-URLhs6eG4Hdr4OdvgBBPDzOjBeSSmI+Kqex2rv/aAyYClME26RYHirLVhZsZP5M+ZP6M34iRlXk8Wlqtezuqpg==", - "dependencies": { - "p-defer": "^4.0.1" + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/it-parallel-batch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.6.tgz", - "integrity": "sha512-3wgiQGvMMHy65OXScrtrtmY+bJSF7P6St1AP+BU+SK83fEr8NNk/MrmJKrtB1+MahYX2a8I+pOGKDj8qVtuV0Q==", - "dependencies": { - "it-batch": "^3.0.0" + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/it-peekable": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.5.tgz", - "integrity": "sha512-JWQOGMt6rKiPcY30zUVMR4g6YxkpueTwHVE7CMs/aGqCf4OydM6w+7ZM3PvmO1e0TocjuR4aL8xyZWR46cTqCQ==" - }, - "node_modules/it-pipe": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", - "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "it-merge": "^3.0.0", - "it-pushable": "^3.1.2", - "it-stream-types": "^2.0.1" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/it-protobuf-stream": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-1.1.5.tgz", - "integrity": "sha512-H70idW45As3cEbU4uSoZ9IYHUIV3YM69/2mmXYR7gOlPabWjuyNi3/abK11geiiq3la27Sos/mXr68JljjKtEQ==", - "dependencies": { - "it-length-prefixed-stream": "^1.0.0", - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.4.8" - } + "node_modules/hashlru": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", + "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", + "license": "MIT" }, - "node_modules/it-pushable": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", - "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", "dependencies": { - "p-defer": "^4.0.0" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/it-queueless-pushable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-1.0.0.tgz", - "integrity": "sha512-HbcAbcuQj7a9EBxiRCZ+77FxWutgs/pY5ZvEyQnylWPGNFojCLAUwhcZjf5OuEQ9+y+vSa7w1GQBe8xJdmIn5A==", + "node_modules/helia": { + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/helia/-/helia-7.1.10.tgz", + "integrity": "sha512-umZgmIQpWRXSIZ5rbG1m5tGoxsZj3MSyRDp/A4tNvyPujupwJvw4JikB2n54kkYDEY5HiUUw8Mkl8CEDmL5enQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "p-defer": "^4.0.1", - "race-signal": "^1.0.2" - } - }, - "node_modules/it-reader": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-6.0.4.tgz", - "integrity": "sha512-XCWifEcNFFjjBHtor4Sfaj8rcpt+FkY0L6WdhD578SCDhV4VUm7fCkF3dv5a+fTcfQqvN9BsxBTvWbYO6iCjTg==", + "@helia/bitswap": "^4.0.14", + "@helia/http": "^4.0.6", + "@helia/interface": "^7.1.1", + "@helia/libp2p": "^2.1.3", + "@helia/utils": "^3.0.5", + "@ipld/dag-cbor": "^10.0.1", + "@ipld/dag-json": "^11.0.0", + "@ipld/dag-pb": "^4.1.7", + "@ipshipyard/crypto": "^1.1.0", + "@ipshipyard/keychain": "^1.0.2", + "@libp2p/interface": "^3.2.4", + "@libp2p/utils": "^7.2.3", + "@multiformats/dns": "^1.0.15", + "@multiformats/multiaddr": "^13.0.3", + "abort-error": "^1.0.2", + "any-signal": "^4.2.0", + "birnam": "^1.0.0", + "blockstore-core": "^7.0.1", + "cborg": "^6.1.1", + "datastore-core": "^12.0.1", + "interface-blockstore": "^7.0.1", + "interface-datastore": "^10.0.1", + "it-drain": "^3.0.12", + "it-filter": "^3.1.6", + "it-foreach": "^2.1.7", + "it-merge": "^3.0.14", + "main-event": "^1.0.4", + "mortice": "^3.3.1", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/helia/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/it-sort": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.6.tgz", - "integrity": "sha512-aNrlZAXB8vWBd42tCpaXGL6CJVJNDW3OLczmdt6g0k/s9Z6evkTdgU2LjwW5SNNeX41sF+C8MjV+OcVf93PsPw==", + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/hermes-compiler": { + "version": "250829098.0.16", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.16.tgz", + "integrity": "sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw==", + "license": "MIT", + "peer": true + }, + "node_modules/hermes-estree": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", + "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", + "license": "MIT", + "peer": true + }, + "node_modules/hermes-parser": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", + "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", + "license": "MIT", + "peer": true, "dependencies": { - "it-all": "^3.0.0" + "hermes-estree": "0.36.1" } }, - "node_modules/it-stream-types": { + "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.1.tgz", - "integrity": "sha512-6DmOs5r7ERDbvS4q8yLKENcj6Yecr7QQTqWApbZdfAUTEC947d+PEha7PCqhm//9oxaLYL7TWRekwhoXl2s6fg==", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/it-take": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.6.tgz", - "integrity": "sha512-uqw3MRzf9to1SOLxaureGa73lK8k8ZB/asOApTAkvrzUqCznGtKNgPFH7uYIWlt4UuWq/hU6I+U4Fm5xpjN8Vg==" - }, - "node_modules/it-ws": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.5.tgz", - "integrity": "sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "dependencies": { - "@types/ws": "^8.2.2", - "event-iterator": "^2.0.0", - "it-stream-types": "^2.0.1", - "uint8arrays": "^5.0.0", - "ws": "^8.4.0" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">= 6" } }, - "node_modules/jackspeak": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", - "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^9.0.0" + "license": "MIT", + "bin": { + "husky": "bin.js" }, "engines": { - "node": "20 || >=22" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "peer": true, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "peer": true, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 4" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=16.x" } }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-message-util/node_modules/has-flag": { + "node_modules/import-fresh/node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.8.19" } }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "peer": true, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "peer": true + "node_modules/interface-blockstore": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-7.0.1.tgz", + "integrity": "sha512-a3NsgRXpRppVWtQnJjmZ9jxvjcttY1+WNWqHFWwgKhRcNMMGwWGtFNYpQmcZ+mEnIt5NTJVYzNwLCcALMWPNsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "interface-store": "^8.0.0", + "multiformats": "^14.0.0" + } }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/interface-datastore": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-10.0.1.tgz", + "integrity": "sha512-DYMj/Og5Cz1Qwkx6/x5KRvR8SYEX7rVAv3KKCm2NzTwWSfpNAC4PahjcYbHyoZBP6zPWrhQv5n5wE+vaDdgSAg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "abort-error": "^1.0.2", + "interface-store": "^8.0.0", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/interface-datastore/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "peer": true, + "node_modules/interface-store": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-8.0.0.tgz", + "integrity": "sha512-e2+s3EEROzM+Wlas4hU3zveTUscvVMf1BOvdsJfpzFm19SoEXLVadpACjWOnM491HqGpvtfFnevyiaN8W+I6Eg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "abort-error": "^1.0.2" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip-address": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", + "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 12" } }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "license": "MIT", "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/ipfs-unixfs": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-13.0.0.tgz", + "integrity": "sha512-5Q9fw95zKA0h/LxRVkQDfp+5lRaDD6i4fzXt70f38b0CRkrbhuj2eUXDjEohfBwBeWcB7UJPW5DzAmgoSNEb8A==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2" } }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "peer": true, + "node_modules/ipfs-unixfs-exporter": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-exporter/-/ipfs-unixfs-exporter-16.0.2.tgz", + "integrity": "sha512-VK0G/fwy9VOwV4o7WyAaKhPl1iBLyUzugHRWHsgD4kmuJzQtWuNV0EwYJFdWmyXkal0NYMn6nnCoeG52XslbRg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@ipld/dag-cbor": "^10.0.0", + "@ipld/dag-json": "^11.0.0", + "@ipld/dag-pb": "^4.1.7", + "@multiformats/murmur3": "^2.2.5", + "hamt-sharding": "^3.0.8", + "interface-blockstore": "^7.0.1", + "ipfs-unixfs": "^13.0.0", + "it-last": "^3.0.11", + "it-map": "^3.1.6", + "it-parallel": "^3.0.15", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.4", + "it-to-buffer": "^5.0.0", + "multiformats": "^14.0.0", + "p-queue": "^9.2.0", + "progress-events": "^1.1.0" } }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/ipfs-unixfs-importer": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-17.0.1.tgz", + "integrity": "sha512-4MC1itlM3OOAQDgyr/htPJ13p9FulXEc+pe0yw4Gx3hCYxyQibTd6EZfefglkzcCURsYoqm5neMFdVrTMQL4tQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@ipld/dag-pb": "^4.1.7", + "@multiformats/murmur3": "^2.2.5", + "blockstore-core": "^7.0.1", + "hamt-sharding": "^3.0.8", + "interface-blockstore": "^7.0.1", + "ipfs-unixfs": "^13.0.0", + "it-all": "^3.0.11", + "it-batch": "^3.0.11", + "it-first": "^3.0.11", + "it-parallel-batch": "^3.0.11", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0", + "rabin-wasm": "^0.1.5", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/ipfs-unixfs-importer/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" } }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "peer": true, + "node_modules/ipfs-unixfs-importer/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node_modules/ipfs-unixfs/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "peer": true - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, + "node_modules/ipfs-unixfs/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "uint8arrays": "^6.0.0" } }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "peer": true, + "node_modules/ipfs-unixfs/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" }, - "node_modules/joi": { - "version": "17.13.6", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.6.tgz", - "integrity": "sha512-ImNZaq/LSysofih+xIGYfR0WUXMA9GLUNB//YTCSrZptoRmVgaNAdJyi6K1kXi9pkLEoSkoI8I4UwtNiu/D7nw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "is-extglob": "^2.1.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jsc-android": { - "version": "250231.0.0", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", - "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", - "peer": true - }, - "node_modules/jsc-safe-url": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", - "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "peer": true - }, - "node_modules/jscodeshift": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", - "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", - "peer": true, + "node_modules/is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "license": "MIT", "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.21.0", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" + "engines": { + "node": ">=14.16" }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jscodeshift/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/is-loopback-addr": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.1.0.tgz", + "integrity": "sha512-77bB59Coq+vGTibZlnuYVU1bUuiboGZ62ZJ7TqmgYKb+hko9dSlqS5OfqvmPZhwJys48At4y4D1UJP3GuvoXtQ==", + "license": "MIT" + }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jscodeshift/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.12.0" } }, - "node_modules/jscodeshift/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "peer": true + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/jscodeshift/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "is-docker": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "peer": true, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/it-all": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.11.tgz", + "integrity": "sha512-Gvqj6MO4GMLnFdtE68HZRpGBskNC+9+GQ+JevTGNYLyhjUuPhjDLU3jN1LpBemXJDW1bRSkczqA/qGyKlPKrcQ==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/it-batch": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.11.tgz", + "integrity": "sha512-2bvKErkXhtwKYDLKAGdEgveOs8wB0i3RItbaroP/6cC/QlrM7j+HAq/yJq6ci4J7KnzdRi76GyipKlafdOTBgw==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/it-byte-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-3.0.0.tgz", + "integrity": "sha512-dpTm5CaRFuz7Wp8VRo8TLkhVNx4KCNT7fAtVorAz/CxrwBJwyz6a6Au9inPxqDN6kN7VVN3WUweJCziG39T48w==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "abort-error": "^1.0.2", + "it-queueless-pushable": "^2.0.0", + "it-stream-types": "^2.0.2", + "race-signal": "^2.0.0", + "uint8arraylist": "^3.0.1" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" + "node_modules/it-byte-stream/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "node_modules/it-byte-stream/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "peer": true + "node_modules/it-drain": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.12.tgz", + "integrity": "sha512-RaFA9X1PF2Pf1Jlqhgf5PlXLgf6CaZt7tSzhia+EkEVcAJRKa0Uhr8UnjVv0GmOA3Air9jDJfIX2KIvz5hZ1Ag==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "node_modules/it-filter": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.6.tgz", + "integrity": "sha512-yXiGPAvJn/exXjVFSCMQc3+J/7RLpOMwKoY2DH1yMhF4lYkdRoAdOwU0vnDACAlRAexf7AZvESZIc9mzhEoi/A==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-peekable": "^3.0.0" + } }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "node_modules/it-first": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.11.tgz", + "integrity": "sha512-0ig8DKpg09V1o7JBagm3oPx3VY7WYfU5w3lpbLbqzijnfMPSvMGoMZuLm17h/RgOJXKP+9mt7vsCNiU2TW8TkQ==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "node_modules/it-foreach": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.7.tgz", + "integrity": "sha512-HoZgIF7DGU1X/8svRuJ7aPl6sge8W6MQxmMomkeAABNXJXoiXEU0xnvulzncRdd013Kh9SubXWhx6YjYw6lu5A==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-peekable": "^3.0.0" + } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" + "node_modules/it-glob": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-3.0.6.tgz", + "integrity": "sha512-dFNeW4izM08QuB4uuIr+sVKUSo8ftVD/E1RnYidiUZx/i9h9mmwDSBl3kPv/TCah6HI0y1sgfHVCbrwA9FjoaQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "fast-glob": "^3.3.3" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node_modules/it-last": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.11.tgz", + "integrity": "sha512-Fg571l81nPzhZsiYjkw4dkhRqAK4oqIamTPEfAOnXI/5pYXz+dIfMVYmh9ncZs58oFNMkdF3bYFuCBTw/xJK0w==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/it-length": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-length/-/it-length-3.0.11.tgz", + "integrity": "sha512-j0uukHdr3zoLm4dcozDoyv5khQrOI8dfXMSSEqaxorfOtleh1KwRVbdnTmzj6HVkHgTM1jcx+bhAcnTkbpHl+g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/it-length-prefixed": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-11.0.1.tgz", + "integrity": "sha512-0Cy4RHFiL2CH060Lkigq0N37VaPg7oSylG6YjXErq+ccJFYcNEWNzxNjbqceio/sY1C+q84vZXOCE6/C0flYfQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-reader": "^7.0.0", + "it-stream-types": "^2.0.1", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.1", + "uint8arrays": "^6.1.0" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] + "node_modules/it-length-prefixed-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-3.0.1.tgz", + "integrity": "sha512-XffBM6F8exghmr9jLQa2TdnwYm/Y5a1zC/v9NFFIiGrDb4lgJfo3YBlXHwjtCqbOJBQ4idO5agY8uUM9VUEb8Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "abort-error": "^1.0.2", + "it-byte-stream": "^3.0.0", + "it-stream-types": "^2.0.2", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.1" + } }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, + "node_modules/it-length-prefixed-stream/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" + "uint8arrays": "^6.0.0" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "node_modules/it-length-prefixed-stream/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "json-buffer": "3.0.1" + "multiformats": "^14.0.0" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" + "node_modules/it-length-prefixed/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "peer": true, - "engines": { - "node": ">=6" + "node_modules/it-length-prefixed/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "peer": true, - "engines": { - "node": ">=6" + "node_modules/it-map": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.6.tgz", + "integrity": "sha512-wCix0FXImtIPIxhCnbz35RqWs00e/CReSZX9nZq1j46JcAzBBp57ob9/2l1WnDYEaUURIR8xCyg2NsWbOwBJFQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-peekable": "^3.0.0" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "node_modules/it-merge": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.15.tgz", + "integrity": "sha512-rUskoyECUdJsUwpfBtiLXddm56J+vukMztq+PpfTss1mRl/aHv1YJs+6Q2XnpvDThFC2y4Xm636X0xh6IecPGg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" + "it-queueless-pushable": "^2.0.0" } }, - "node_modules/libp2p": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-1.8.3.tgz", - "integrity": "sha512-IpHKhR/gjiRcm3UqaHC3bEFsnBS62hTwXy60L8Uie8NZvvliVL6skmIBAODfma0+C7zvJhNKA8453SOmTeVTwQ==", - "dependencies": { - "@libp2p/crypto": "^4.1.8", - "@libp2p/interface": "^1.6.3", - "@libp2p/interface-internal": "^1.3.3", - "@libp2p/logger": "^4.0.19", - "@libp2p/multistream-select": "^5.1.16", - "@libp2p/peer-collections": "^5.2.8", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/peer-id-factory": "^4.2.3", - "@libp2p/peer-store": "^10.1.4", - "@libp2p/utils": "^5.4.8", - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "any-signal": "^4.1.1", - "datastore-core": "^9.2.9", - "interface-datastore": "^8.2.11", - "it-merge": "^3.0.5", - "it-parallel": "^3.0.7", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "p-defer": "^4.0.1", - "progress-events": "^1.0.0", - "race-event": "^1.3.0", - "race-signal": "^1.0.2", - "uint8arrays": "^5.1.0" + "node_modules/it-ndjson": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-2.0.0.tgz", + "integrity": "sha512-6QOU+X9zBSolfcYGXY5CSJchHb57MgkoDgVJA5Tdh1CwzUHKKJYS0lTzdVR9hefVpiQNhTB/glEU8i37LpP7qw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arraylist": "^3.0.1" } }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "peer": true, + "node_modules/it-ndjson/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "uint8arrays": "^6.0.0" } }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, + "node_modules/it-ndjson/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "ms": "2.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "node_modules/it-parallel": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.16.tgz", + "integrity": "sha512-qr3nTgj4fraSfr6Ix9JkHsy/Yb/4vmS3QIAu3fsX52r2SOTLbohXoixKp6AIhTJcorpKwjP0VFcpHr5V54jivA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "p-defer": "^4.0.1" + } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" + "node_modules/it-parallel-batch": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.11.tgz", + "integrity": "sha512-n2gvHRVx14COn25jkio+fOHuKWaA5EVf9f0GbdWgVe8gXRIMYCai+CbmQACIv7o2Jn3ZAUOXD9Ob1Ftod0qtJQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-batch": "^3.0.0" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "node_modules/it-peekable": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.10.tgz", + "integrity": "sha512-2E6+p1pelZOhzp69aaiiBuEybWzAl10uYbIdCR3Pxy8bFNnS/kgpbLtGbNbIZ6RVdU7yHHkmATYwjy52GfFEKA==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/lint-staged": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", - "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", - "dev": true, - "license": "MIT", + "node_modules/it-pipe": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", + "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "chalk": "^5.4.1", - "commander": "^13.1.0", - "debug": "^4.4.0", - "execa": "^8.0.1", - "lilconfig": "^3.1.3", - "listr2": "^8.2.5", - "micromatch": "^4.0.8", - "pidtree": "^0.6.0", - "string-argv": "^0.3.2", - "yaml": "^2.7.0" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" + "it-merge": "^3.0.0", + "it-pushable": "^3.1.2", + "it-stream-types": "^2.0.1" }, "engines": { - "node": ">=18.12.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", - "dev": true, - "license": "MIT", + "node_modules/it-protobuf-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-3.0.1.tgz", + "integrity": "sha512-lKjTCPwL+aKUDovZUt4CyUAsYlOv9z09zyNnnGVWaQmbO6f0n6vV12F4hg0YwWyk9Er6UVWdu1EM7d6P62JnHw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" + "abort-error": "^1.0.2", + "it-length-prefixed-stream": "^3.0.0", + "it-stream-types": "^2.0.2", + "uint8arraylist": "^3.0.1" } }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, + "node_modules/it-protobuf-stream/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "uint8arrays": "^6.0.0" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT" + "node_modules/it-protobuf-stream/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true + "node_modules/it-pushable": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.4.tgz", + "integrity": "sha512-WSD7Ss4oCRfDZJT4ldLWr0Bom/muY90xxoJ5PQnU3uSKf0kxCOeehqZtiJX1ARqn+ymXGh1bxpDW9bDNHp2ivQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "p-defer": "^4.0.0" + } }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "peer": true + "node_modules/it-queue": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/it-queue/-/it-queue-1.1.4.tgz", + "integrity": "sha512-cGH0+YfMPmwQ+lV+MhJcOXqOgE04h0zQCXvDUmvGGTyW0VqxJZbi7V+PQFEuPVN/5dHQg5RY9aVjMCgmxXaE5Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "abort-error": "^1.0.2", + "it-pushable": "^3.2.3", + "main-event": "^1.0.1", + "race-event": "^1.6.1", + "race-signal": "^2.0.0" + } }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true + "node_modules/it-queueless-pushable": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-2.0.5.tgz", + "integrity": "sha512-BaKqGLL1AQMR1AEaxiM09vzJQVXHHhfhh9UV0qPqORw/8Rm8igDQqT1qHregfHIb1NIW9jxQ/aXBibHJyuivuQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "abort-error": "^1.0.2", + "p-defer": "^4.0.1", + "race-signal": "^2.0.0" + } }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true + "node_modules/it-reader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-7.0.0.tgz", + "integrity": "sha512-bTWQPHH1if8N1K9XeidDjhTDm51ALbOksMa9uCZVVsQkoIPXP0XVKM88/Ynqr7HS18La/P1Kvu7C73j4rRoKWQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-stream-types": "^2.0.1", + "uint8arraylist": "^3.0.1" + } }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true + "node_modules/it-reader/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" + } }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "peer": true + "node_modules/it-reader/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true + "node_modules/it-sort": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.11.tgz", + "integrity": "sha512-eZ22LAoNLx4i4gVV44tJPoUYf/o+mHKa6+OigdVH/hmsdA2qoJN6MNPvKZyZKBf6+S/8PBE44zyvkzdYGkRhbA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-all": "^3.0.0" + } }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true + "node_modules/it-stream-types": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.4.tgz", + "integrity": "sha512-tsX+klvMQ53J4Jm2B52vCIs7WD609ck+VS9X2TKMEv7VPY9VwaYKmSWyHek5QS0wHBtP0bWj9KMqCtAHgVKiXw==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "peer": true, + "node_modules/it-take": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.11.tgz", + "integrity": "sha512-zvoeEjLViGFyhYT5KNCgmcIH90Si8lCve4aTMvgej/ZQRfB9YzrcJW3UHIJjbQ9TiAnsT4vsWDImEFQNk5xmnA==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/it-to-browser-readablestream": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/it-to-browser-readablestream/-/it-to-browser-readablestream-2.0.14.tgz", + "integrity": "sha512-YyTLGvX5ufvukf05ZQCBEQO0ZyFmI9gxOEZ5yO1oQCnAL8Zlwmep7ty8RN2qg27oe1eu/qBQG5P79qS59Az8HA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "get-iterator": "^2.0.1" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "node_modules/it-to-buffer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/it-to-buffer/-/it-to-buffer-5.0.0.tgz", + "integrity": "sha512-DyinWj+79wxFDQNiPZcmV8Fz2PJFOcM3KRRN4GiZiIdEfxO11cCkrZJMfUMrxryy+rLoNhAfh1bpoVUFHPR6pQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "uint8arrays": "^6.1.0" } }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" + "node_modules/it-to-buffer/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "license": "MIT", + "peer": true, "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", + "peer": true, "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "license": "MIT", - "engines": { - "node": ">=12" + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "license": "MIT", + "peer": true, "dependencies": { - "restore-cursor": "^5.0.0" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/log-update/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", + "peer": true, "dependencies": { - "mimic-function": "^5.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=10" } }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD", + "peer": true + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "peer": true, - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, "bin": { - "logkitty": "bin/logkitty.js" + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, - "node_modules/logkitty/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "peer": true, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, "engines": { "node": ">=6" } }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "peer": true, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "json-buffer": "3.0.1" } }, - "node_modules/logkitty/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/logkitty/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "peer": true, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/logkitty/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, + "node_modules/libp2p": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-3.3.8.tgz", + "integrity": "sha512-CMDYzEflxjtDkRV4vx8u4H93VxnPJOnntqd1pumrb5meorpe3hppxvwvgUc/eQ/5+ZwRQCgFi1rUfaaQt0zy0g==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@chainsafe/is-ip": "^2.1.0", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/logger": "^6.2.12", + "@libp2p/multistream-select": "^7.0.26", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/peer-store": "^12.0.26", + "@libp2p/utils": "^7.3.2", + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "any-signal": "^4.1.1", + "datastore-core": "^12.0.1", + "interface-datastore": "^10.0.1", + "it-merge": "^3.0.12", + "it-parallel": "^3.0.13", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "p-retry": "^8.0.0", + "progress-events": "^1.1.0", + "race-signal": "^2.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/logkitty/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "peer": true, + "node_modules/libp2p/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "multiformats": "^14.0.0" } }, - "node_modules/logkitty/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", "peer": true, - "engines": { - "node": ">=8" + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" } }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" + "ms": "2.0.0" } }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", "peer": true }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "peer": true, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.3.0.tgz", + "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==", + "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "picomatch": "^4.0.5", + "string-argv": "^0.3.2", + "tinyexec": "^1.2.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=8" + "node": ">=22.22.1" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + }, + "optionalDependencies": { + "yaml": "^2.9.0" } }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "peer": true, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT", + "peer": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -12319,66 +7532,42 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "peer": true, "dependencies": { "yallist": "^3.0.2" } }, "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", "engines": { "node": ">=12" } }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "peer": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "peer": true, - "bin": { - "semver": "bin/semver" - } + "node_modules/main-event": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.5.tgz", + "integrity": "sha512-4l9z8r7Q446mhhVTwdHmfPksOYBwN2xa7ewEW2yxPVNQapIaAf57ORFLMB91SpMEMa4wXowect7fq3uMLjDnGA==", + "license": "Apache-2.0 OR MIT" }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", "peer": true, "dependencies": { "tmpl": "1.0.5" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "license": "Apache-2.0", "peer": true }, "node_modules/math-intrinsics": { @@ -12394,6 +7583,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12402,20 +7592,9 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT", "peer": true }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -12429,6 +7608,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", "dependencies": { "is-plain-obj": "^2.1.0" }, @@ -12436,15 +7616,27 @@ "node": ">=10" } }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT", + "peer": true }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -12453,56 +7645,54 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/metro": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.10.tgz", - "integrity": "sha512-FDPi0X7wpafmDREXe1lgg3WzETxtXh6Kpq8+IwsG35R2tMyp2kFIqDdshdohuvDt1J/qDARcEPq7V/jElTb1kA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.87.0.tgz", + "integrity": "sha512-fRqFhSzQhLNQSCvJFeuRzBRXAOOKXf1O8d2cvmMtG6yFR0jCllQ7vBsXoLP18yuqtf+N1XwWXTPF11eWy9q6dQ==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "accepts": "^1.3.7", - "chalk": "^4.0.0", + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", + "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.23.0", + "hermes-parser": "0.36.1", "image-size": "^1.0.2", "invariant": "^2.2.4", - "jest-worker": "^29.6.3", + "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.10", - "metro-cache": "0.80.10", - "metro-cache-key": "0.80.10", - "metro-config": "0.80.10", - "metro-core": "0.80.10", - "metro-file-map": "0.80.10", - "metro-resolver": "0.80.10", - "metro-runtime": "0.80.10", - "metro-source-map": "0.80.10", - "metro-symbolicate": "0.80.10", - "metro-transform-plugins": "0.80.10", - "metro-transform-worker": "0.80.10", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", + "metro-babel-transformer": "0.87.0", + "metro-cache": "0.87.0", + "metro-cache-key": "0.87.0", + "metro-config": "0.87.0", + "metro-core": "0.87.0", + "metro-file-map": "0.87.0", + "metro-resolver": "0.87.0", + "metro-runtime": "0.87.0", + "metro-source-map": "0.87.0", + "metro-symbolicate": "0.87.0", + "metro-transform-plugins": "0.87.0", + "metro-transform-worker": "0.87.0", + "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" @@ -12511,429 +7701,391 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-babel-transformer": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.10.tgz", - "integrity": "sha512-GXHueUzgzcazfzORDxDzWS9jVVRV6u+cR6TGvHOfGdfLzJCj7/D0PretLfyq+MwN20twHxLW+BUXkoaB8sCQBg==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.87.0.tgz", + "integrity": "sha512-IEn1K1FyY4J1sA5y6zqDjf2OkfmpTEqhZOeP6MJX8HepSW0cuHGw1m8bYOdv2adkG3XUE9dtM0csUs0gP/Xa5w==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/core": "^7.20.0", + "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.23.0", + "hermes-parser": "0.36.1", + "metro-cache-key": "0.87.0", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", - "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", - "peer": true - }, - "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", - "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", - "peer": true, - "dependencies": { - "hermes-estree": "0.23.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-cache": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.10.tgz", - "integrity": "sha512-8CBtDJwMguIE5RvV3PU1QtxUG8oSSX54mIuAbRZmcQ0MYiOl9JdrMd4JCBvIyhiZLoSStph425SMyCSnjtJsdA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.87.0.tgz", + "integrity": "sha512-146vS1BMSKcp99jddOhFBfHwzUEWN35NrsnSJDF2sQQ0ZT5OsBcOjd574PM233TWEZISRJ5DOK+vokD+1ubx+w==", + "license": "MIT", "peer": true, "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", - "metro-core": "0.80.10" + "https-proxy-agent": "^7.0.5", + "metro-core": "0.87.0" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-cache-key": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.10.tgz", - "integrity": "sha512-57qBhO3zQfoU/hP4ZlLW5hVej2jVfBX6B4NcSfMj4LgDPL3YknWg80IJBxzQfjQY/m+fmMLmPy8aUMHzUp/guA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.87.0.tgz", + "integrity": "sha512-Q+MPt6jl0zQogr4Q02WaJK6HY+GtE5A0nzj8kIV1Owgrx6OMNvm6scPTr1SM/R4LpCE8EH/Y5qfbXQ84GHTr0Q==", + "license": "MIT", "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-config": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.10.tgz", - "integrity": "sha512-0GYAw0LkmGbmA81FepKQepL1KU/85Cyv7sAiWm6QWeV6AcVCpsKg6jGLqGHJ0LLPL60rWzA4TV1DQAlzdJAEtA==", - "peer": true, - "dependencies": { - "connect": "^3.6.5", - "cosmiconfig": "^5.0.5", - "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.6.3", - "metro": "0.80.10", - "metro-cache": "0.80.10", - "metro-core": "0.80.10", - "metro-runtime": "0.80.10" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/metro-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/metro-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "node_modules/metro-cache/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", "peer": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, "engines": { - "node": ">=4" + "node": ">= 14" } }, - "node_modules/metro-config/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "node_modules/metro-cache/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "peer": true, "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=4" + "node": ">= 14" } }, - "node_modules/metro-config/node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "node_modules/metro-config": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.87.0.tgz", + "integrity": "sha512-yZ9QAIzWH9MxwrzwRlX/CBGRWOT14l7klSDYg8hdtSdnoUs5A7MQRdHE2KB9iHVzGQW5wgWM5aXJswNeWbSQPA==", "license": "MIT", "peer": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/metro-config/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "peer": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.87.0", + "metro-cache": "0.87.0", + "metro-core": "0.87.0", + "metro-runtime": "0.87.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "peer": true, - "engines": { - "node": ">=4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-core": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.10.tgz", - "integrity": "sha512-nwBB6HbpGlNsZMuzxVqxqGIOsn5F3JKpsp8PziS7Z4mV8a/jA1d44mVOgYmDa2q5WlH5iJfRIIhdz24XRNDlLA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.87.0.tgz", + "integrity": "sha512-yW57+pCOHRC/CJZ99GA2PTd+30dORwDAjUPRCokj91IWW5In9Jwtt2FB5wACrGO8P0GHTyVHdTwDNyZsNndUbA==", + "license": "MIT", "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.10" + "metro-resolver": "0.87.0" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-file-map": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.10.tgz", - "integrity": "sha512-ytsUq8coneaN7ZCVk1IogojcGhLIbzWyiI2dNmw2nnBgV/0A+M5WaTTgZ6dJEz3dzjObPryDnkqWPvIGLCPtiw==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.87.0.tgz", + "integrity": "sha512-Dc57t8jsINwA90bbVlqaeDlxf1rVGgj5SmOEnOMbaHNUM/HCYYTJxPV8SRdOBwh7qTz/biO9vaQvBTjRBgbbsg==", + "license": "MIT", "peer": true, "dependencies": { - "anymatch": "^3.0.3", - "debug": "^2.2.0", + "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", - "jest-worker": "^29.6.3", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "node-abort-controller": "^3.1.1", "nullthrows": "^1.1.1", "walker": "^1.0.7" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/metro-file-map/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, - "dependencies": { - "ms": "2.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/metro-file-map/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true - }, "node_modules/metro-minify-terser": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.10.tgz", - "integrity": "sha512-Xyv9pEYpOsAerrld7cSLIcnCCpv8ItwysOmTA+AKf1q4KyE9cxrH2O2SA0FzMCkPzwxzBWmXwHUr+A89BpEM6g==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.87.0.tgz", + "integrity": "sha512-tPa0O983PDutFu3LXbArRH5NduogcKrvW6fs9VHhksTKUA1iqDyoD1ZSj/Me52xJ6T9/9pOwIVyj9ulKc/zMkg==", + "license": "MIT", "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-resolver": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.10.tgz", - "integrity": "sha512-EYC5CL7f+bSzrqdk1bylKqFNGabfiI5PDctxoPx70jFt89Jz+ThcOscENog8Jb4LEQFG6GkOYlwmPpsi7kx3QA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.87.0.tgz", + "integrity": "sha512-Xl3M9R3KToaHJvXlI2lSOxtYHitzxite+195DSi00HL9PcS7Xik5+3xlRjfkKb3FA86SZxYPj+WJwlcfgaZoxg==", + "license": "MIT", "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-runtime": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.10.tgz", - "integrity": "sha512-Xh0N589ZmSIgJYAM+oYwlzTXEHfASZac9TYPCNbvjNTn0EHKqpoJ/+Im5G3MZT4oZzYv4YnvzRtjqS5k0tK94A==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.87.0.tgz", + "integrity": "sha512-XsXZkgEwI0ZMYSBfvOMAbenzwa60XlObXJ27g6/Khgrz9ESbiBbAsd7hR62G2jRBYOhGAzG61Gk22dpRJj/mdw==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-source-map": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.10.tgz", - "integrity": "sha512-EyZswqJW8Uukv/HcQr6K19vkMXW1nzHAZPWJSEyJFKIbgp708QfRZ6vnZGmrtFxeJEaFdNup4bGnu8/mIOYlyA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.87.0.tgz", + "integrity": "sha512-31BrYqu1c2co93rF1LN9Pw+7g+BrfDyxJkNQWrYm+pfA/+eVYVumF7tFMHbXLePLmHfhvSgVvbK6su1Oyiw1ng==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.10", + "metro-symbolicate": "0.87.0", "nullthrows": "^1.1.1", - "ob1": "0.80.10", + "ob1": "0.87.0", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-symbolicate": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.10.tgz", - "integrity": "sha512-qAoVUoSxpfZ2DwZV7IdnQGXCSsf2cAUExUcZyuCqGlY5kaWBb0mx2BL/xbMFDJ4wBp3sVvSBPtK/rt4J7a0xBA==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.87.0.tgz", + "integrity": "sha512-uOpTxAXu74N+RSujUZ78L6gjI6bDdnz6XuW+AIUNuubZDEQPIpaX0StzIb0GMeQWP6zfHOHwFrWPP5Iquu1GXw==", + "license": "MIT", "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.80.10", + "metro-source-map": "0.87.0", "nullthrows": "^1.1.1", "source-map": "^0.5.6", - "through2": "^2.0.1", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-symbolicate/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-transform-plugins": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.10.tgz", - "integrity": "sha512-leAx9gtA+2MHLsCeWK6XTLBbv2fBnNFu/QiYhWzMq8HsOAP4u1xQAU0tSgPs8+1vYO34Plyn79xTLUtQCRSSUQ==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.87.0.tgz", + "integrity": "sha512-i8keUe9+BaSwMuQM26DGheElCpTtflAKIrSwJAm8ZsgDb50RAUQus+e6zt2suaJXJ1OxZa7vqgtqoZxdniM6Fw==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-transform-worker": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.10.tgz", - "integrity": "sha512-zNfNLD8Rz99U+JdOTqtF2o7iTjcDMMYdVS90z6+81Tzd2D0lDWVpls7R1hadS6xwM+ymgXFQTjM6V6wFoZaC0g==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.87.0.tgz", + "integrity": "sha512-YftLzNJxCTYxEN5k4AzR8KYwiENTEuz30L+4QeoMrtDd+U8mDThZg/ArR3JVRd8LaikwPOjVAS5SP3xPJN0AaA==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", - "metro": "0.80.10", - "metro-babel-transformer": "0.80.10", - "metro-cache": "0.80.10", - "metro-cache-key": "0.80.10", - "metro-minify-terser": "0.80.10", - "metro-source-map": "0.80.10", - "metro-transform-plugins": "0.80.10", + "metro": "0.87.0", + "metro-babel-transformer": "0.87.0", + "metro-cache": "0.87.0", + "metro-cache-key": "0.87.0", + "metro-minify-terser": "0.87.0", + "metro-source-map": "0.87.0", + "metro-transform-plugins": "0.87.0", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/metro/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.6" + } + }, + "node_modules/metro/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "peer": true + "node_modules/metro/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } }, - "node_modules/metro/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "peer": true, "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/metro/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/metro/node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", "peer": true, + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/metro/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/metro/node_modules/hermes-estree": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", - "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", - "peer": true - }, - "node_modules/metro/node_modules/hermes-parser": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", - "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", + "node_modules/metro/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "peer": true, "dependencies": { - "hermes-estree": "0.23.0" - } - }, - "node_modules/metro/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true - }, - "node_modules/metro/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "peer": true, + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/metro/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/metro/node_modules/ws": { @@ -12958,6 +8110,35 @@ } } }, + "node_modules/metro/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -12971,22 +8152,35 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "peer": true, + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4.0.0" + "node": ">=4" } }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12995,6 +8189,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -13002,34 +8197,11 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -13037,98 +8209,76 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.8" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minimist-options/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, "bin": { "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" }, "node_modules/mortice": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/mortice/-/mortice-3.0.4.tgz", - "integrity": "sha512-MUHRCAztSl4v/dAmK8vbYi5u1n9NZtQu4H3FsqS7qgMFQIAFw9lTpHiErd9kJpapqmvEdD1L3dUmiikifAvLsQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/mortice/-/mortice-3.3.1.tgz", + "integrity": "sha512-t3oESfijIPGsmsdLEKjF+grHfrbnKSXflJtgb1wY14cjxZpS6GnhHRXTxxzCAoCCnq1YYfpEPwY3gjiCPhOufQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "observable-webworkers": "^2.0.1", - "p-queue": "^8.0.1", - "p-timeout": "^6.0.0" + "abort-error": "^1.0.0", + "it-queue": "^1.1.0", + "main-event": "^1.0.0" } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/multer": { "version": "2.2.0", @@ -13153,6 +8303,7 @@ "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -13162,85 +8313,65 @@ } }, "node_modules/multiformats": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", - "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", + "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/murmurhash3js-revisited": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", - "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", + "node_modules/nanoid": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-6.0.1.tgz", + "integrity": "sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, "engines": { - "node": ">=8.0.0" + "node": "^22 || ^24 || >=26" } }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", "dev": true, - "bin": { - "ncp": "bin/ncp" - } + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "peer": true - }, "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "peer": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/node-abi": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", - "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -13248,59 +8379,24 @@ "node": ">=10" } }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "peer": true - }, "node_modules/node-datachannel": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.11.0.tgz", - "integrity": "sha512-8/vAMms32XxgJ9FIRDXbfmmH1ROm0HBdsa/XteIcUWN4VTQN38UITTkuu6YsfQzN/CQp8YhhnfAEzEadQJ2c6Q==", + "version": "0.32.3", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.32.3.tgz", + "integrity": "sha512-Aok1ZhLsll472lRefgWYuWJ0070jh0ecHravTdRyZEmoESumebMEQV8Y+poBwSW2ZbEwAokAOGsK5Cu8pDDT2g==", "hasInstallScript": true, + "license": "MPL 2.0", "dependencies": { - "node-domexception": "^2.0.1", - "prebuild-install": "^7.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "peer": true, - "dependencies": { - "minimatch": "^3.0.2" + "prebuild-install": "^7.1.3" }, "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-2.0.1.tgz", - "integrity": "sha512-M85rnSC7WQ7wnfQTARPT4LrK7nwCHLdDFOCcItZMhTQjyCebJH8GciKqYJNgaOFZs9nFmTmd/VMyi3OW5jA47w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=16" + "node": ">=18.20.0" } }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -13321,7 +8417,6 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "license": "(BSD-3-Clause OR GPL-2.0)", - "peer": true, "engines": { "node": ">= 6.13.0" } @@ -13330,6 +8425,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT", "peer": true }, "node_modules/node-releases": { @@ -13342,29 +8438,17 @@ "node": ">=18" } }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "peer": true, - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, "node_modules/nodemon": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", - "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -13383,76 +8467,41 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT", "peer": true }, "node_modules/ob1": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.10.tgz", - "integrity": "sha512-dJHyB0S6JkMorUSfSGcYGkkg9kmq3qDUu3ygZUKIfkr47XOPuG35r2Sk6tbwtHXbdKIXmcMvM8DF2CwgdyaHfQ==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.87.0.tgz", + "integrity": "sha512-8Q8sKCiUwsxgSmjDtVWyRgmxsgeJXXam3oQH6Id8ADfNaJMV6GZKyeAl8+pGVVdgwAZabfk5+aExle7AP/nZiA==", + "license": "MIT", "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13469,19 +8518,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/observable-webworkers": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/observable-webworkers/-/observable-webworkers-2.0.1.tgz", - "integrity": "sha512-JI1vB0u3pZjoQKOK1ROWzp0ygxSi7Yb0iR+7UNsw4/Zn4cQ0P3R7XL38zac/Dy2tEA7Lg88/wIJTjF8vYXZ0uw==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -13490,6 +8531,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -13497,57 +8539,30 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", "peer": true, "dependencies": { - "is-wsl": "^1.1.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { "node": ">=8" - } - }, - "node_modules/open/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "peer": true, - "engines": { - "node": ">=4" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optionator": { @@ -13555,6 +8570,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -13567,128 +8583,11 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "peer": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "peer": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/ora/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-defer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -13697,94 +8596,120 @@ } }, "node_modules/p-event": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", - "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-7.1.0.tgz", + "integrity": "sha512-/lkPs5W1aC3cp6vqZefpdosOn65J571sWodyfOQiF0+tmDCpU+H8Atwpu0vQROCVUlZuToDN5eyTLsMLLc54mg==", + "license": "MIT", "dependencies": { - "p-timeout": "^6.1.2" + "p-timeout": "^7.0.1" }, "engines": { - "node": ">=16.17" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^4.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz", - "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", + "license": "MIT", "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz", + "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.3.0" + }, + "engines": { + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", - "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/p-wait-for": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-6.0.0.tgz", + "integrity": "sha512-2kKzMtjS8TVcpCOU/gr3vZ4K/WIyS1AsEFXFWapM/0lERCdyTbB6ZeuCIp+cL1aeLZfQoMdZFCBTHiK4I9UtOw==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -13797,6 +8722,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -13814,61 +8740,53 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -13879,15 +8797,6 @@ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -13895,255 +8804,103 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pino": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.3.2.tgz", - "integrity": "sha512-WtARBjgZ7LNEkrGWxMBN/jvlFiE17LTbBoH0konmBU684Kd0uIiDwBXlcTCW7iJnA6HfIKwUssS/2AC6cDEanw==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", "dependencies": { + "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", + "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/pino-abstract-transport/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "split2": "^4.0.0" } }, "node_modules/pino-http": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.2.0.tgz", - "integrity": "sha512-am03BxnV3Ckx68OkbH0iZs3indsrH78wncQ6w1w51KroIbvJZNImBKX2X1wjdY8lSyaJ0UrX/dnO2DY3cTeCRw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-11.0.0.tgz", + "integrity": "sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==", + "license": "MIT", "dependencies": { "get-caller-file": "^2.0.5", - "pino": "^9.0.0", + "pino": "^10.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^3.0.0" + "process-warning": "^5.0.0" } }, "node_modules/pino-pretty": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.2.tgz", - "integrity": "sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A==", - "dev": true, + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "license": "MIT", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", + "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", + "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", + "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", - "strip-json-comments": "^3.1.1" + "strip-json-comments": "^5.0.2" }, "bin": { "pino-pretty": "bin.js" } }, - "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/pino-pretty/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" - }, - "node_modules/pino/node_modules/process-warning": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", - "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==" - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "peer": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "peer": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "peer": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "peer": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "peer": true, - "engines": { - "node": ">=4" - } + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" }, "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", + "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", @@ -14163,15 +8920,17 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -14182,155 +8941,101 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/pretty-format/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", "peer": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/pretty-format/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "peer": true - }, "node_modules/process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, "node_modules/progress-events": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", - "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.1.0.tgz", + "integrity": "sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==", + "license": "Apache-2.0 OR MIT" }, "node_modules/promise": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", "peer": true, "dependencies": { "asap": "~2.0.6" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "peer": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/protons-runtime": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.5.0.tgz", - "integrity": "sha512-EsALjF9QsrEk6gbCx3lmfHxVN0ah7nG3cY7GySD4xf4g8cr7g543zB88Foh897Sr1RQJ9yDCUsoT1i1H/cVUFA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.6.0.tgz", + "integrity": "sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg==", + "license": "Apache-2.0 OR MIT", "dependencies": { "uint8-varint": "^2.0.2", "uint8arraylist": "^2.4.3", "uint8arrays": "^5.0.1" } }, + "node_modules/protons-runtime/node_modules/uint8-varint": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", + "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arraylist": "^2.0.0", + "uint8arrays": "^5.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -14339,16 +9044,27 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -14359,24 +9075,27 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pvtsutils": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", - "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.1" + "tslib": "^2.8.1" } }, "node_modules/pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" } }, "node_modules/qs": { @@ -14395,20 +9114,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "peer": true, - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", "peer": true, "dependencies": { "inherits": "~2.0.3" @@ -14431,26 +9141,20 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" }, "node_modules/rabin-wasm": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", "integrity": "sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==", + "license": "MIT", "dependencies": { "@assemblyscript/loader": "^0.9.4", "bl": "^5.0.0", @@ -14467,6 +9171,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -14476,20 +9181,38 @@ "node": ">= 6" } }, - "node_modules/race-event": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.3.0.tgz", - "integrity": "sha512-kaLm7axfOnahIqD3jQ4l1e471FIFcEGebXEnhxyLscuUzV8C94xVHtWEqDDXxll7+yu/6lW0w1Ff4HbtvHvOHg==" - }, - "node_modules/race-signal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.1.0.tgz", - "integrity": "sha512-VqsW1uzCXfKBd2DhA3K3NhQlqQr04+5WQ7+kHpf1HzT01Q+ePSFWZdQHXKZPuLmm2eXTZM1XLO76cq15ZRAaEA==" - }, + "node_modules/race-event": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.6.1.tgz", + "integrity": "sha512-vi7WH5g5KoTFpu2mme/HqZiWH14XSOtg5rfp6raBskBHl7wnmy3F/biAIyY5MsK+BHWhoPhxtZ1Y2R7OHHaWyQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "abort-error": "^1.0.1" + } + }, + "node_modules/race-signal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.2.tgz", + "integrity": "sha512-IuNNNY5NLriMFEGl3BVei2xq9NIcpkuAklhPWamhu5tRJrJz0w9sloUMz73YXpmUluE2EZocmJm9rpQqXmCPiA==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/random-int": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/random-int/-/random-int-3.1.0.tgz", + "integrity": "sha512-h8CRz8cpvzj0hC/iH/1Gapgcl2TQ6xtnCpyOI5WvWfXf/yrDx2DOU+tD9rX23j36IF11xg1KqB9W11Z18JPMdw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14513,6 +9236,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -14526,32 +9250,33 @@ "node_modules/rc/node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-devtools-core": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.3.1.tgz", - "integrity": "sha512-7FSb9meX0btdBQLwdFOwt6bGqvRPabmVMMslv8fgoSPqXyuGpgQe36kx8gR86XPw7aV1yVouTp6fyZ0EH+NfUw==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "license": "MIT", "peer": true, "dependencies": { "shell-quote": "^1.6.1", @@ -14581,64 +9306,59 @@ } }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT", "peer": true }, "node_modules/react-native": { - "version": "0.74.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.5.tgz", - "integrity": "sha512-Bgg2WvxaGODukJMTZFTZBNMKVaROHLwSb8VAGEdrlvKwfb1hHg/3aXTUICYk7dwgAnb+INbGMwnF8yeAgIUmqw==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.87.0.tgz", + "integrity": "sha512-e7CeZOm1wBksIISI5oLNmH4/GJCqWrfNphF7PuBsLY2qUM6mtzqL23N7iLTW6U7LF45qYi+a4iAp5Y1kSk1vVw==", + "license": "MIT", "peer": true, "dependencies": { - "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "13.6.9", - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native/assets-registry": "0.74.87", - "@react-native/codegen": "0.74.87", - "@react-native/community-cli-plugin": "0.74.87", - "@react-native/gradle-plugin": "0.74.87", - "@react-native/js-polyfills": "0.74.87", - "@react-native/normalize-colors": "0.74.87", - "@react-native/virtualized-lists": "0.74.87", - "abort-controller": "^3.0.0", + "@react-native/asset-utils": "0.87.0", + "@react-native/codegen": "0.87.0", + "@react-native/community-cli-plugin": "0.87.0", + "@react-native/gradle-plugin": "0.87.0", + "@react-native/normalize-colors": "0.87.0", + "@react-native/virtualized-lists": "0.87.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.36.1", "base64-js": "^1.5.1", - "chalk": "^4.0.0", - "event-target-shim": "^5.0.1", + "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.16", "invariant": "^2.2.4", - "jest-environment-node": "^29.6.3", - "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.80.3", - "metro-source-map": "^0.80.3", - "mkdirp": "^0.5.1", + "metro-runtime": "^0.87.0", + "metro-source-map": "^0.87.0", "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", + "pretty-format": "^29.7.0", "promise": "^8.3.0", - "react-devtools-core": "^5.0.0", + "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", - "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.24.0-canary-efb381bbf-20230505", + "scheduler": "0.27.0", + "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "react-native": "cli.js" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { - "@types/react": "^18.2.6", - "react": "18.2.0" + "@types/react": "^19.1.1", + "react": "^19.2.3" }, "peerDependenciesMeta": { "@types/react": { @@ -14647,13 +9367,13 @@ } }, "node_modules/react-native-webrtc": { - "version": "118.0.7", - "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.7.tgz", - "integrity": "sha512-odgd4CNSGQmI8n/pEbxlUtJBTJ8uqE51B1/NUEAvO1AQbeXsyFNHEG0H2T27eMefo5u0GKcRpNkZpXi6fctTkQ==", + "version": "124.0.8", + "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.8.tgz", + "integrity": "sha512-uuQxvmk+mvnk5U0tr+1N42sKZqgm41fJrBA+fmCvML9J9P4roSh2So82t5RHAlu/vE9vxu5AKgivAiH61clCBg==", + "license": "MIT", "dependencies": { "base64-js": "1.5.1", - "debug": "4.3.4", - "event-target-shim": "6.0.2" + "debug": "4.3.4" }, "peerDependencies": { "react-native": ">=0.60.0" @@ -14663,6 +9383,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -14675,395 +9396,204 @@ } } }, - "node_modules/react-native-webrtc/node_modules/event-target-shim": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", - "integrity": "sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==", - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } + "node_modules/react-native-webrtc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" }, - "node_modules/react-native/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/react-native/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-native/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/react-native/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/react-native/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", - "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "node_modules/react-native/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "peer": true, "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "peer": true, - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, + "node_modules/react-native/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", + "peer": true, "engines": { - "node": ">=6" + "node": ">=8.3.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/react-native/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", "peer": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "peer": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "peer": true - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "engines": { - "node": ">= 12.13.0" + "node": ">=12" } }, - "node_modules/recast": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", - "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "node_modules/react-native/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "peer": true, - "dependencies": { - "ast-types": "0.15.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, "engines": { - "node": ">= 4" + "node": ">=12" } }, - "node_modules/recast/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "peer": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "peer": true, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", "dependencies": { - "regenerate": "^1.4.2" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "peer": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "peer": true, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.8.4" + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "peer": true, - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 12.13.0" } }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "peer": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - } + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "peer": true }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15073,155 +9603,49 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "peer": true - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "node_modules/retimeable-signal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/retimeable-signal/-/retimeable-signal-1.0.1.tgz", + "integrity": "sha512-Cy26CYfbWnYu8HMoJeDhaMpW/EYFIbne3vMf6G9RSrOyWYXbPehja/BEdzpqmM84uy2bfBD7NPZhoQ4GZEtgvg==", + "license": "Apache-2.0 OR MIT" }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" }, "bin": { - "glob": "dist/esm/bin.mjs" + "rimraf": "dist/esm/bin.mjs" }, "engines": { "node": "20 || >=22" @@ -15230,31 +9654,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -15273,28 +9672,11 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -15312,12 +9694,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", "engines": { "node": ">=10" } @@ -15325,53 +9709,54 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "license": "WTFPL OR ISC", "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/scheduler": { - "version": "0.24.0-canary-efb381bbf-20230505", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", - "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true }, "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "peer": true, - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15419,37 +9804,11 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serialize-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" @@ -15461,51 +9820,26 @@ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "peer": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "peer": true, - "dependencies": { - "kind-of": "^6.0.2" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -15517,6 +9851,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { "node": ">=8" } @@ -15606,18 +9941,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -15635,7 +9958,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/simple-get": { "version": "4.0.1", @@ -15655,6 +9979,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -15666,6 +9991,7 @@ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -15673,71 +9999,30 @@ "node": ">=10" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "peer": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/sonic-boom": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", - "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "peer": true, "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -15748,6 +10033,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "peer": true, "engines": { "node": ">=0.10.0" @@ -15756,85 +10042,30 @@ "node_modules/sparse-array": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", - "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true + "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==", + "license": "ISC" }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", "engines": { "node": ">= 10.x" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "peer": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "peer": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT", "peer": true }, "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", "peer": true, "dependencies": { "type-fest": "^0.7.1" @@ -15843,15 +10074,6 @@ "node": ">=6" } }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -15861,17 +10083,16 @@ "node": ">= 0.8" } }, - "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" - }, - "node_modules/stream-to-it": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-1.0.1.tgz", - "integrity": "sha512-AqHYAYPHcmvMrcLNgncE/q0Aj/ajP6A4qGhxP6EVn7K3YTNs0bJpJyk57wc2Heb7MUL64jurvmnmui8D9kjZgA==", - "dependencies": { - "it-stream-types": "^2.0.1" + "node_modules/steno": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", + "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, "node_modules/streamsearch": { @@ -15883,110 +10104,87 @@ } }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.19" } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "peer": true - }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "peer": true - }, "node_modules/super-regex": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "license": "MIT", "dependencies": { "clone-regexp": "^3.0.0", "function-timeout": "^0.1.0", @@ -16004,6 +10202,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -16011,37 +10210,10 @@ "node": ">=4" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", - "dev": true, - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -16054,6 +10226,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -16069,6 +10242,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -16093,6 +10267,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -16102,6 +10277,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16111,48 +10287,15 @@ "node": ">= 6" } }, - "node_modules/temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "peer": true, - "dependencies": { - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/terser": { - "version": "5.31.5", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.5.tgz", - "integrity": "sha512-YPmas0L0rE1UyLL/llTWA0SiDOqIcAQYLeUj7cJYzXHlRTAnMSg9pPe4VJ5PlKvTrPQsdVFuiRiwyeNlYgwh2Q==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.50.0.tgz", + "integrity": "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==", + "license": "BSD-2-Clause", "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -16167,65 +10310,45 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", "peer": true }, - "node_modules/text-extensions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", - "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", "dependencies": { - "real-require": "^0.2.0" + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" } }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "peer": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "peer": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } + "license": "MIT", + "peer": true }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" }, "node_modules/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "license": "MIT", "dependencies": { "convert-hrtime": "^5.0.0" }, @@ -16236,19 +10359,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/timestamp-nano": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", - "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 4.5.0" + "node": ">=18" } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -16261,59 +10385,18 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause", "peer": true }, "node_modules/to-regex-range": { "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==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -16325,6 +10408,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -16334,6 +10418,7 @@ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -16341,21 +10426,14 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "license": "WTFPL", "dependencies": { "utf8-byte-length": "^1.0.1" } @@ -16374,35 +10452,34 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", "dependencies": { - "tslib": "^1.8.1" + "tslib": "^1.9.3" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": ">= 6.0.0" } }, - "node_modules/tsutils/node_modules/tslib": { + "node_modules/tsyringe/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -16415,6 +10492,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16422,31 +10500,21 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -16462,10 +10530,11 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16474,107 +10543,121 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/uint8-varint": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", - "integrity": "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-3.0.0.tgz", + "integrity": "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" + "uint8arraylist": "^3.0.1", + "uint8arrays": "^6.1.0" + } + }, + "node_modules/uint8-varint/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" + } + }, + "node_modules/uint8-varint/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, "node_modules/uint8arraylist": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.8.tgz", - "integrity": "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==", + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", + "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", + "license": "Apache-2.0 OR MIT", "dependencies": { "uint8arrays": "^5.0.1" } }, "node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", + "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { "multiformats": "^13.0.0" } }, + "node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "peer": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "dev": true, + "license": "MIT" }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "peer": true, + "node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=22.19.0" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "peer": true, - "engines": { - "node": ">=4" - } + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" }, - "node_modules/unicorn-magic": { + "node_modules/unlimited-timeout": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/unlimited-timeout/-/unlimited-timeout-0.1.0.tgz", + "integrity": "sha512-D4g+mxFeQGQHzCfnvij+R35ukJ0658Zzudw7j16p4tBBbNasKkKM4SocYxqhwT5xA7a9JYWDzKkEFyMlRi5sng==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16615,6 +10698,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -16622,35 +10706,35 @@ "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==" + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/utf8-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", + "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", + "license": "MIT" }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16659,69 +10743,81 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT", "peer": true }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", "peer": true, "dependencies": { "makeerror": "1.0.12" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "peer": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/weald": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.0.2.tgz", - "integrity": "sha512-iG5cIuBwsPe1ZcoGGd4X6QYlepU1vLr4l4oWpzQWqeJPSo9B8bxxyE6xlnj3TCmThtha7gyVL+uuZgUFkPyfDg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.3.tgz", + "integrity": "sha512-vMWtNbYuPb58NeG2+0sKA0Een4VMDwzf+3oHqh68buWRSOMUBlUeRb11LhV28czV+DUpJHRykifijZDuS9bInA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "ms": "^3.0.0-canary.1", - "supports-color": "^9.4.0" + "ms": "^4.0.0-nightly.202508271359", + "supports-color": "^10.0.0" } }, "node_modules/weald/node_modules/ms": { - "version": "3.0.0-canary.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", - "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "version": "4.0.0-nightly.202508271359", + "resolved": "https://registry.npmjs.org/ms/-/ms-4.0.0-nightly.202508271359.tgz", + "integrity": "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==", + "license": "MIT", "engines": { - "node": ">=12.13" + "node": ">=20" } }, "node_modules/weald/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT", "peer": true }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -16731,6 +10827,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/wherearewe/-/wherearewe-2.0.1.tgz", "integrity": "sha512-XUguZbDxCA2wBn2LoFtcEhXL6AXo+hVjGonwhSTTTU9SzbWG8Xu3onNIpzf9j/mYUcJQ0f+m37SzG77G851uFw==", + "license": "Apache-2.0 OR MIT", "dependencies": { "is-electron": "^2.2.0" }, @@ -16743,6 +10840,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -16753,17 +10851,12 @@ "node": ">= 8" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "peer": true - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -16786,19 +10879,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", @@ -16837,45 +10917,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/ws": { "version": "8.21.3", @@ -16902,6 +10948,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -16914,23 +10961,16 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", "engines": { "node": ">=4.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } @@ -16939,13 +10979,16 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC", "peer": true }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, "license": "ISC", + "optional": true, "bin": { "yaml": "bin.mjs" }, @@ -16957,37 +11000,41 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^8.2.1", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index c33b6a6..9f65715 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,14 @@ "build": "rimraf dist && tsc", "start": "node dist/index.js", "dev": "nodemon", - "lint": "eslint src test scripts --ext .ts,.mjs", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --check .", + "format:fix": "prettier --write .", "test:build": "rimraf dist-test && tsc -p tsconfig.test.json", - "test": "npm run test:build && node --test dist-test/test/*.test.js", + "test": "npm run test:build && npm run test:unit && npm run test:integration", + "test:unit": "IPFS_NODE_CONFIG=test node --test \"dist-test/test/*.test.js\"", + "test:integration": "IPFS_NODE_CONFIG=test node --test \"dist-test/test/integration/*.test.js\"", "prepare": "husky", "bump-patch": "npm version patch", "bump-minor": "npm version minor", @@ -24,47 +29,52 @@ "author": "ADAMANT Foundation ", "license": "GPLv3", "devDependencies": { - "@commitlint/cli": "^19.4.0", - "@commitlint/config-conventional": "^19.2.2", + "@commitlint/cli": "^21.2.2", + "@commitlint/config-conventional": "^21.0.0", + "@eslint/js": "^10.0.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/multer": "^2.2.0", - "@types/node": "^20.14.8", - "@typescript-eslint/eslint-plugin": "^8.59.1", - "eslint": "^8.57.0", - "gts": "^5.3.1", - "husky": "^9.1.4", - "lint-staged": "^15.2.8", - "nodemon": "^3.1.4", - "pino-pretty": "^11.2.2", - "prettier": "^3.3.3", - "rimraf": "^6.0.1", - "typescript": "^5.5.4" + "@types/node": "^24.13.3", + "eslint": "^10.9.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.11.0", + "husky": "^9.1.7", + "lint-staged": "^17.3.0", + "nodemon": "^3.1.14", + "prettier": "^3.9.6", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.67.0" }, "dependencies": { - "@chainsafe/libp2p-noise": "^15.1.0", - "@chainsafe/libp2p-yamux": "^6.0.2", - "@helia/unixfs": "^3.0.7", - "@libp2p/bootstrap": "^10.1.4", - "@libp2p/identify": "^2.1.5", - "@libp2p/interface": "^1.7.0", - "@libp2p/peer-id": "^4.2.3", - "@libp2p/ping": "^1.1.6", - "@libp2p/tcp": "^9.1.4", - "@multiformats/multiaddr": "^12.5.1", - "blockstore-fs": "^1.1.11", - "cors": "^2.8.5", - "cron": "^3.1.7", - "datastore-fs": "^9.1.9", + "@chainsafe/libp2p-noise": "^17.0.0", + "@chainsafe/libp2p-yamux": "^8.0.1", + "@helia/bitswap": "^4.0.12", + "@helia/libp2p": "^2.1.1", + "@helia/unixfs": "^8.0.5", + "@libp2p/bootstrap": "^12.0.29", + "@libp2p/identify": "^4.1.12", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/ping": "^3.1.11", + "@libp2p/tcp": "^11.0.26", + "@multiformats/multiaddr": "^13.0.3", + "blockstore-fs": "^4.0.1", + "cors": "^2.8.6", + "cron": "^4.4.0", + "datastore-fs": "^12.0.1", "express": "^4.22.2", "express-rate-limit": "^8.6.2", - "go-get-folder-size": "^0.5.5", - "helia": "^4.2.5", + "helia": "^7.1.8", + "interface-blockstore": "^7.0.1", + "interface-datastore": "^10.0.1", "json5": "^2.2.3", "multer": "^2.2.0", - "multiformats": "^13.2.2", - "pino": "^9.3.2", - "pino-http": "^10.2.0" + "multiformats": "^14.0.5", + "pino": "^10.3.1", + "pino-http": "^11.0.0", + "pino-pretty": "^13.1.3" }, "lint-staged": { "*.(ts|js)": [ @@ -73,6 +83,6 @@ ] }, "engines": { - "node": ">=20.0.0" + "node": ">=24.0.0" } } diff --git a/scripts/audit-production.mjs b/scripts/audit-production.mjs index 4de2cda..8d6c094 100644 --- a/scripts/audit-production.mjs +++ b/scripts/audit-production.mjs @@ -1,8 +1,5 @@ import { spawnSync } from 'node:child_process' -const acceptedAdvisory = 'https://github.com/advisories/GHSA-32mq-hpph-xfvr' -const acceptedPackages = new Set(['@libp2p/kad-dht', 'helia']) - const result = spawnSync('npm', ['audit', '--omit=dev', '--omit=peer', '--json'], { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 @@ -32,27 +29,13 @@ if ( } const blocking = [] -const accepted = [] for (const [name, vulnerability] of Object.entries(report.vulnerabilities || {})) { - if (!['high', 'critical'].includes(vulnerability.severity)) { - continue - } - - if (isAcceptedDhtFinding(name, vulnerability)) { - accepted.push(name) - } else { + if (['high', 'critical'].includes(vulnerability.severity)) { blocking.push(name) } } -if (accepted.length > 0) { - process.stdout.write( - `Accepted until #21: ${accepted.join(', ')} (${acceptedAdvisory}). ` + - 'The vulnerable DHT service is installed by Helia 4 but is replaced by the explicit service map in src/helia.ts.\n' - ) -} - if (blocking.length > 0) { throw new Error(`Blocking high/critical production vulnerabilities: ${blocking.join(', ')}`) } @@ -60,25 +43,3 @@ if (blocking.length > 0) { process.stdout.write( 'No unaccepted high or critical production dependency vulnerabilities found.\n' ) - -function isAcceptedDhtFinding(name, vulnerability) { - if (!acceptedPackages.has(name)) { - return false - } - - if (name === 'helia') { - return ( - Array.isArray(vulnerability.via) && - vulnerability.via.length === 1 && - vulnerability.via[0] === '@libp2p/kad-dht' - ) - } - - return ( - Array.isArray(vulnerability.via) && - vulnerability.via.length === 1 && - typeof vulnerability.via[0] === 'object' && - vulnerability.via[0] && - vulnerability.via[0].url === acceptedAdvisory - ) -} diff --git a/src/api/debug.ts b/src/api/debug.ts index 2b0c800..05f9e8e 100644 --- a/src/api/debug.ts +++ b/src/api/debug.ts @@ -1,5 +1,5 @@ import { Router } from 'express' -import { pino } from '../utils/logger.js' +import { logger } from '../utils/logger.js' import { getNodesList } from '../utils/utils.js' import { helia } from '../helia.js' @@ -8,18 +8,18 @@ const router = Router() router.get('/autopeering', async (req, res, next) => { try { const nodes = getNodesList([helia.libp2p.peerId.toString()]) - pino.logger.info(`Peering nodes list: ${nodes.map((node) => node.name)}`) + logger.info(`Peering nodes list: ${nodes.map((node) => node.name)}`) const successPeers: string[] = [] for await (const node of nodes) { - pino.logger.info(`Start peering ${node.name} node (${node.multiAddr})...`) + logger.info(`Start peering ${node.name} node (${node.multiAddr})...`) try { await helia.libp2p.dial(node.multiAddr) - pino.logger.info(`Successfully peered with ${node.name}`) + logger.info(`Successfully peered with ${node.name}`) successPeers.push(node.name) } catch (err) { - pino.logger.info(`Peering with ${node.name} failed. Error: ${err.message}`) + logger.info(`Peering with ${node.name} failed. Error: ${err.message}`) } } diff --git a/src/api/file.ts b/src/api/file.ts index 53fff4b..6201949 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -3,7 +3,7 @@ import { CID } from 'multiformats/cid' import { multerStorage } from '../multer.js' import { config } from '../config.js' import { helia } from '../helia.js' -import { pino } from '../utils/logger.js' +import { logger } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' import { downloadFile, getFileStats } from '../utils/file.js' @@ -28,22 +28,22 @@ router.post('/upload', uploadLimiter, multerStorage.array('files'), async (req, try { const files = flatFiles(req.files as UnixFsMulterFile[]) - pino.logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) + logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) const cids: CID[] = [] for (const file of files) { - pino.logger.info(`Adding ${file.originalname} to IPFS`) + logger.info(`Adding ${file.originalname} to IPFS`) const { cid } = file - pino.logger.info(`Successfully added file ${cid}`) + logger.info(`Successfully added file ${cid}`) cids.push(cid) const isPinned = await helia.pins.isPinned(cid) if (isPinned) { - pino.logger.info(`File already pinned ${cid}`) + logger.info(`File already pinned ${cid}`) } else { for await (const pinned of helia.pins.add(cid)) { - pino.logger.info(`Filed pinned: ${pinned}`) + logger.info(`Filed pinned: ${pinned}`) } } } @@ -66,9 +66,9 @@ router.get('/:cid', readLimiter, async (req, res, next) => { sendDownloadStream( stream, res, - { cid: cid.toString(), fileSize: fileStats.fileSize }, + { cid: cid.toString(), fileSize: fileStats.size }, next, - (error) => pino.logger.error(error) + (error) => logger.error(error) ) } catch (error) { next(error) diff --git a/src/api/helia.ts b/src/api/helia.ts index eb9328f..be7d424 100644 --- a/src/api/helia.ts +++ b/src/api/helia.ts @@ -1,7 +1,7 @@ import { Router } from 'express' import { Pin } from 'helia' import { helia } from '../helia.js' -import { pino } from '../utils/logger.js' +import { logger } from '../utils/logger.js' import { pinLimiter, readLimiter } from '../middleware/rateLimiter.js' import { parseCid } from '../utils/cid.js' @@ -12,7 +12,7 @@ router.get('/pins', readLimiter, async (req, res, next) => { const pins: Pin[] = [] for await (const pin of helia.pins.ls()) { - pino.logger.info('PIN LS', pin) + logger.info(`Pinned CID: ${pin.cid.toString()}`) pins.push(pin) } @@ -25,8 +25,9 @@ router.get('/pins', readLimiter, async (req, res, next) => { router.post('/pin/:cid', pinLimiter, async (req, res, next) => { try { const cid = parseCid(req.params.cid) - for await (const pin of helia.pins.add(cid)) { - pino.logger.info('PINNED', pin) + // `pins.add` yields the CID of every block it walks while pinning the DAG + for await (const pinnedCid of helia.pins.add(cid)) { + logger.info(`Pinned block: ${pinnedCid.toString()}`) } res.send({ pinned: true, cid: cid.toString() }) } catch (err) { @@ -49,22 +50,4 @@ router.get('/pins/isPinned/:cid', readLimiter, async (req, res, next) => { } }) -router.get('/routing/findProviders/:cid', readLimiter, async (req, res, next) => { - try { - const cid = parseCid(req.params.cid) - - const providers: string[] = [] - for await (const provider of helia.routing.findProviders(cid)) { - pino.logger.info(`Found provider of CID:${cid.toString()}, PeerId:${provider.id.toString()}`) - providers.push(provider.id.toString()) - } - - res.send({ - providers - }) - } catch (err) { - next(err) - } -}) - export default router diff --git a/src/api/libp2p.ts b/src/api/libp2p.ts index bd89406..301bb88 100644 --- a/src/api/libp2p.ts +++ b/src/api/libp2p.ts @@ -1,22 +1,42 @@ -import { PeerId } from '@libp2p/interface' +import { Connection, PeerId } from '@libp2p/interface' import { peerIdFromString } from '@libp2p/peer-id' -import type { PingService } from '@libp2p/ping' import { multiaddr, Multiaddr } from '@multiformats/multiaddr' import { type NextFunction, Request, Response, Router } from 'express' import { PeerIdDto } from '../dto/peer-id.dto.js' import { helia } from '../helia.js' -import { pino } from '../utils/logger.js' +import { logger } from '../utils/logger.js' import { InvalidRequestError } from '../security/errors.js' const router = Router() +/** + * Reduce a libp2p connection to a JSON-serialisable summary. + * + * libp2p `Connection` objects hold streams, loggers and multiaddr instances and + * cannot be passed to `res.send()` directly — serialising one throws + * "toJSON not set". + */ +function serializeConnection(connection: Connection) { + return { + id: connection.id, + remotePeer: connection.remotePeer.toString(), + remoteAddr: connection.remoteAddr.toString(), + direction: connection.direction, + status: connection.status, + multiplexer: connection.multiplexer, + encryption: connection.encryption, + streams: connection.streams.length, + timeline: connection.timeline + } +} + router.get( '/services/ping', async (req: Request, res: Response, next: NextFunction) => { try { const peerId = peerIdFromString(req.query.peerId || '') - const pingService = helia.libp2p.services.ping as PingService - const pong = await pingService.ping(peerId) + // Round-trip time in milliseconds; the `ping` service is registered in `createIpfsNode` + const pong = await helia.libp2p.services.ping.ping(peerId) res.send({ pong @@ -104,9 +124,9 @@ router.get( } if (multiAddr) { - pino.logger.info(`Peering by multiAddress: ${multiAddr}`) + logger.info(`Peering by multiAddress: ${multiAddr}`) } else if (peerId) { - pino.logger.info(`Peering by PeerID: ${peerId}`) + logger.info(`Peering by PeerID: ${peerId}`) } const peer = multiAddr || peerId @@ -117,7 +137,7 @@ router.get( try { const connection = await helia.libp2p.dial(peer) - res.send({ success: true, connection }) + res.send({ success: true, connection: serializeConnection(connection) }) } catch (err) { next(err) } @@ -133,7 +153,7 @@ router.get( res.send({ length: connections.length, - connections + connections: connections.map(serializeConnection) }) } catch (err) { next(err) diff --git a/src/config.ts b/src/config.ts index 8701147..0c3f864 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,15 +3,243 @@ import { fileURLToPath } from 'url' import { dirname, join } from 'path' import JSON5 from 'json5' import { validateSecurityConfig } from './security/config.js' +import type { RateLimitPolicy } from './security/rateLimit.js' +import type { TrustProxySetting } from './security/trustProxy.js' + +/** + * Locate the repository root by walking up to the nearest `package.json`. + * + * The compiled entry point sits at `dist/config.js`, but the test build emits + * to `dist-test/src/config.js`, so a fixed number of `..` segments resolves the + * root correctly for only one of them. + */ +function findRootDir(start: string): string { + let dir = start + + for (;;) { + if (fs.existsSync(join(dir, 'package.json'))) { + return dir + } + + const parent = dirname(dir) + if (parent === dir) { + throw new ConfigError(`Cannot locate the project root above ${start}`) + } + dir = parent + } +} const currDir = dirname(fileURLToPath(import.meta.url)) -const rootDir = join(currDir, '..') -export const CONFIG_FILE_NAME = 'config.json5' +/** Log levels accepted by `pino`, ordered from least to most verbose. */ +const LOG_LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'] as const + +export type LogLevel = (typeof LOG_LEVELS)[number] + +/** A known ADAMANT IPFS node this node peers with. */ +export interface ConfigNode { + name: string + multiAddr: string +} + +export interface Config { + /** Known ADAMANT IPFS nodes. Their multiaddrs are also the connection manager allow list. */ + nodes: ConfigNode[] + /** File storage directory, resolved from the user's home directory. */ + storeFolder: string + logLevel: LogLevel + peerDiscovery: { + /** Multiaddrs dialled on startup to join the ADAMANT peer set. */ + bootstrap: string[] + /** Multiaddrs libp2p listens on. */ + listen: string[] + } + /** API server port. */ + serverPort: number + /** Disk space scanning period in cron format. */ + diskUsageScanPeriod: string + /** Maximum size of a single uploaded file, in bytes. */ + uploadLimitSizeBytes: number + /** Maximum number of files accepted per upload request. */ + maxFileCount: number + /** Time limit, in milliseconds, for locating a file on the IPFS network. */ + findFileTimeout: number + cors: { + /** Exact origins and left-most subdomain wildcards; see `src/security/cors.ts`. */ + allowedOrigins: string[] + } + trustProxy: TrustProxySetting + rateLimits?: Partial> + /** Administrative API key. An empty value makes administrative routes fail closed. */ + adminApiKey: string + enableDebugApi: boolean +} + +/** + * Build the config file name for an optional config suffix. + * + * Passing `test1` selects `config.test1.json5`; passing nothing selects `config.json5`. + * This mirrors the documented `node dist/index.js ` invocation. + * + * @param name Config suffix taken from `IPFS_NODE_CONFIG` or the first CLI argument + */ +export function configFileName(name?: string): string { + return name != null && name !== '' ? `config.${name}.json5` : 'config.json5' +} + +/** Thrown when the config file is missing, unparseable, or fails validation. */ +export class ConfigError extends Error { + constructor(message: string) { + super(message) + this.name = 'ConfigError' + } +} + +/** Repository root; config files and `package.json` are read from here. */ +const rootDir = findRootDir(currDir) + +function fail(path: string, expectation: string): never { + throw new ConfigError(`Invalid config: "${path}" ${expectation}`) +} + +function requireObject(value: unknown, path: string): Record { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + fail(path, 'must be an object') + } + return value as Record +} + +function requireString(value: unknown, path: string): string { + if (typeof value !== 'string' || value.trim() === '') { + fail(path, 'must be a non-empty string') + } + return value +} + +function requireStringArray(value: unknown, path: string): string[] { + if (!Array.isArray(value)) { + fail(path, 'must be an array of strings') + } + return value.map((item, index) => requireString(item, `${path}[${index}]`)) +} + +/** + * Validate a positive integer. + * + * @param min Smallest accepted value; used to reject zero or negative limits + */ +function requireInteger(value: unknown, path: string, min: number): number { + if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < min) { + fail(path, `must be an integer >= ${min}`) + } + return value +} + +/** + * Validate an untrusted parsed config object and return it as a typed `Config`. + * + * The HTTP security surface — CORS, trusted proxies, the administrative key, + * rate limits, and the upload limits — is validated by `validateSecurityConfig` + * so that this module does not duplicate those rules. Everything else, the + * fields the IPFS node and its cron jobs read, is validated here. + * + * Unknown keys are ignored so that config files can carry deployment-specific + * extras without breaking startup. + * + * @param raw Value parsed from a JSON5 config file + */ +export function validateConfig(raw: unknown): Config { + const root = requireObject(raw, 'config') + + if (!Array.isArray(root.nodes)) { + fail('nodes', 'must be an array') + } + const nodes = root.nodes.map((node, index) => { + const entry = requireObject(node, `nodes[${index}]`) + return { + name: requireString(entry.name, `nodes[${index}].name`), + multiAddr: requireString(entry.multiAddr, `nodes[${index}].multiAddr`) + } + }) + + const logLevel = requireString(root.logLevel, 'logLevel') + if (!(LOG_LEVELS as readonly string[]).includes(logLevel)) { + fail('logLevel', `must be one of: ${LOG_LEVELS.join(', ')}`) + } + + const peerDiscovery = requireObject(root.peerDiscovery, 'peerDiscovery') + const listen = requireStringArray(peerDiscovery.listen, 'peerDiscovery.listen') + if (listen.length === 0) { + fail('peerDiscovery.listen', 'must contain at least one multiaddr') + } + + const storeFolder = requireString(root.storeFolder, 'storeFolder') + const serverPort = requireInteger(root.serverPort, 'serverPort', 1) + const diskUsageScanPeriod = requireString(root.diskUsageScanPeriod, 'diskUsageScanPeriod') + const findFileTimeout = requireInteger(root.findFileTimeout, 'findFileTimeout', 1) + const bootstrap = requireStringArray(peerDiscovery.bootstrap, 'peerDiscovery.bootstrap') + + // Owns cors, trustProxy, adminApiKey, enableDebugApi, rateLimits, + // uploadLimitSizeBytes and maxFileCount. Throws with its own message. + validateSecurityConfig(root) + + const cors = requireObject(root.cors, 'cors') + + return { + nodes, + storeFolder, + logLevel: logLevel as LogLevel, + peerDiscovery: { bootstrap, listen }, + serverPort, + diskUsageScanPeriod, + uploadLimitSizeBytes: root.uploadLimitSizeBytes as number, + maxFileCount: root.maxFileCount as number, + findFileTimeout, + cors: { allowedOrigins: cors.allowedOrigins as string[] }, + trustProxy: (root.trustProxy ?? false) as TrustProxySetting, + rateLimits: root.rateLimits as Config['rateLimits'], + adminApiKey: (root.adminApiKey ?? '') as string, + enableDebugApi: root.enableDebugApi === true + } +} + +/** + * Read and validate a config file from the repository root. + * + * @param fileName Config file name, see {@link configFileName} + */ +export function loadConfig(fileName: string): Config { + const configPath = join(rootDir, fileName) + + let contents: string + try { + contents = fs.readFileSync(configPath, 'utf8') + } catch (err) { + throw new ConfigError(`Cannot read config file ${configPath}: ${(err as Error).message}`) + } + + let parsed: unknown + try { + parsed = JSON5.parse(contents) + } catch (err) { + throw new ConfigError(`Cannot parse config file ${configPath}: ${(err as Error).message}`) + } + + return validateConfig(parsed) +} -const configPath = join(rootDir, CONFIG_FILE_NAME) +/** + * Config file used by the running node. + * + * The name comes from `IPFS_NODE_CONFIG` when set, otherwise from the first CLI + * argument as documented in `README.md`. The environment variable takes + * priority so that the config can be selected when the process is started by a + * tool that owns the argument list, such as the test runner. + */ +export const CONFIG_FILE_NAME = configFileName(process.env.IPFS_NODE_CONFIG ?? process.argv[2]) -export const config = JSON5.parse(fs.readFileSync(configPath, 'utf8')) -validateSecurityConfig(config) +export const config = loadConfig(CONFIG_FILE_NAME) -export const packageJson = JSON5.parse(fs.readFileSync(join(rootDir, 'package.json'), 'utf8')) +export const packageJson = JSON.parse( + fs.readFileSync(join(rootDir, 'package.json'), 'utf8') +) as Record & { version: string } diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index 0f18a5d..f052232 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -2,7 +2,7 @@ import { blockstorePath, datastorePath } from './store.js' import { dirSize, availableStorageSize } from './utils/utils.js' import { CronJob } from 'cron' import { config } from './config.js' -import { pino } from './utils/logger.js' +import { logger } from './utils/logger.js' const oneMb = 1048576 @@ -15,13 +15,13 @@ export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { if (!started) { started = true scan() - .catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) + .catch((err) => logger.error(`${err.message}\n${err.stack}`)) .finally(() => (started = false)) } }) async function scan() { - pino.logger.info('[Cron] Running "diskUsage" cronjob.') + logger.info('[Cron] Running "diskUsage" cronjob.') const start = Date.now() const blockstoreSize = await dirSize(blockstorePath) @@ -35,10 +35,10 @@ async function scan() { } availableSizeInMb = Number((await availableStorageSize(blockstorePath)) / BigInt(oneMb)) - pino.logger.info(`Check folder size took ${Date.now() - start} ms.`) + logger.info(`Check folder size took ${Date.now() - start} ms.`) } -scan().catch((err) => pino.logger.error(`${err.message}\n${err.stack}`)) +scan().catch((err) => logger.error(`${err.message}\n${err.stack}`)) export function getDiskUsageStats() { return { diff --git a/src/helia.ts b/src/helia.ts index 706303d..5b234af 100644 --- a/src/helia.ts +++ b/src/helia.ts @@ -1,68 +1,45 @@ -import { unixfs } from '@helia/unixfs' -import { bootstrap } from '@libp2p/bootstrap' -import { createHelia } from 'helia' -import { tcp } from '@libp2p/tcp' -import { yamux } from '@chainsafe/libp2p-yamux' -import { noise } from '@chainsafe/libp2p-noise' -import { identify } from '@libp2p/identify' -import { blockstore, datastore } from './store.js' -import { getAllowNodesMultiaddrs } from './utils/utils.js' import { config } from './config.js' -import { pino } from './utils/logger.js' +import { createIpfsNode, createUnixFs } from './ipfs-node.js' +import { blockstore, datastore, openStores } from './store.js' +import { logger } from './utils/logger.js' +import { getAllowNodesMultiaddrs } from './utils/utils.js' -export const helia = await createHelia({ - datastore, +await openStores() + +/** + * The Helia node backing the HTTP API. + * + * Created at import time so that the API routers can share a single node. See + * `createIpfsNode` for the composition and why the Helia defaults are avoided. + */ +export const helia = await createIpfsNode({ blockstore, - libp2p: { - datastore, - addresses: { - listen: config.peerDiscovery.listen - }, - transports: [tcp()], - connectionEncryption: [noise()], - streamMuxers: [yamux()], - peerDiscovery: [ - bootstrap({ - list: config.peerDiscovery.bootstrap - }) - ], - services: { - identify: identify() - }, - connectionManager: { - maxConnections: 100, - allow: getAllowNodesMultiaddrs() - } - } + datastore, + listen: config.peerDiscovery.listen, + bootstrap: config.peerDiscovery.bootstrap, + allow: getAllowNodesMultiaddrs() }) helia.libp2p.getMultiaddrs().forEach((addr) => { - pino.logger.info(`Listening on ${addr.toString()}`) + logger.info(`Listening on ${addr.toString()}`) }) helia.libp2p.addEventListener('peer:discovery', (evt) => { - const peer = evt.detail - pino.logger.info(`Discovered peer: ${peer.id}`) + logger.info(`Discovered peer: ${evt.detail.id.toString()}`) }) helia.libp2p.addEventListener('peer:connect', (evt) => { - const peerId = evt.detail - pino.logger.info(`Peer connected: ${peerId}`) + logger.info(`Peer connected: ${evt.detail.toString()}`) }) helia.libp2p.addEventListener('peer:disconnect', (evt) => { - const peerId = evt.detail - pino.logger.info(`Peer disconnected: ${peerId}`) -}) - -helia.libp2p.addEventListener('start', () => { - pino.logger.info('Libp2p node started') + logger.info(`Peer disconnected: ${evt.detail.toString()}`) }) -helia.libp2p.addEventListener('stop', () => { - pino.logger.info('Libp2p node stopped') +helia.events.addEventListener('stop', () => { + logger.info('Helia node stopped') }) -pino.logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) +logger.info(`Helia is running! PeerID: ${helia.libp2p.peerId.toString()}`) -export const ifs = unixfs(helia) +export const ifs = createUnixFs(helia) diff --git a/src/index.ts b/src/index.ts index 7627130..75a5280 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import express from 'express' -import { pino } from './utils/logger.js' +import { httpLogger, logger } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' import cors from 'cors' @@ -10,7 +10,7 @@ import { createApiKeyAuth } from './security/apiKey.js' import { createCorsOriginDelegate } from './security/cors.js' import { parseTrustProxy } from './security/trustProxy.js' -pino.logger.info(`Using config file: ${CONFIG_FILE_NAME}`) +logger.info(`Using config file: ${CONFIG_FILE_NAME}`) diskUsageCron.start() @@ -21,13 +21,13 @@ const trustProxy = parseTrustProxy(config.trustProxy) app.set('trust proxy', trustProxy) if (trustProxy === false) { - pino.logger.warn( + logger.warn( 'trustProxy is false. If requests arrive through a reverse proxy, all clients will share ' + 'the proxy IP for rate limiting until exact trusted proxy addresses are configured.' ) } -app.use(pino) +app.use(httpLogger) app.use( cors({ @@ -49,8 +49,8 @@ app.use(notFoundHandler) app.use(errorHandler) app.listen(PORT, () => { - pino.logger.info(`Server is running on http://localhost:${PORT}`) - pino.logger.warn( + logger.info(`Server is running on http://localhost:${PORT}`) + logger.warn( 'TLS is not handled at the application level. ' + 'Ensure this service is deployed behind a TLS-terminating reverse proxy (e.g. nginx, Caddy).' ) diff --git a/src/ipfs-node.ts b/src/ipfs-node.ts new file mode 100644 index 0000000..6b99194 --- /dev/null +++ b/src/ipfs-node.ts @@ -0,0 +1,110 @@ +import { withBitswap } from '@helia/bitswap' +import { withLibp2pLight, type HeliaWithLibp2p } from '@helia/libp2p' +import { unixfs, type UnixFS } from '@helia/unixfs' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { bootstrap } from '@libp2p/bootstrap' +import { identify, type Identify } from '@libp2p/identify' +import { ping, type Ping } from '@libp2p/ping' +import { tcp } from '@libp2p/tcp' +import { createHeliaLight } from 'helia' +import type { Blockstore } from 'interface-blockstore' +import type { Datastore } from 'interface-datastore' +import type { ComponentLogger } from '@libp2p/interface' + +/** + * libp2p services this node registers. + * + * The set is deliberately minimal: ADAMANT nodes form a private mesh of known + * peers, so content routing (DHT), NAT traversal and relay services provided by + * the Helia defaults are neither needed nor wanted here. + * + * - `identify` lets peers exchange supported protocols and observed addresses + * - `ping` backs `GET /api/libp2p/services/ping` + */ +export interface IpfsNodeServices extends Record { + identify: Identify + ping: Ping +} + +export type IpfsNode = HeliaWithLibp2p + +export interface IpfsNodeOptions { + blockstore: Blockstore + datastore: Datastore + /** Multiaddrs to listen on. */ + listen: string[] + /** Multiaddrs dialled on startup. An empty list disables bootstrap discovery. */ + bootstrap?: string[] + /** Multiaddrs always allowed to connect, even past `maxConnections`. */ + allow?: string[] + maxConnections?: number + logger?: ComponentLogger +} + +/** Default connection ceiling, kept from the pre-migration configuration. */ +export const DEFAULT_MAX_CONNECTIONS = 100 + +/** + * Create a Helia node configured for the ADAMANT topology. + * + * The node is composed explicitly out of `createHeliaLight`, `withLibp2pLight` + * and `withBitswap` rather than `createHelia`. `createHelia` merges the Helia + * default libp2p configuration (mDNS, the public IPFS bootstrap list, kad-DHT, + * AutoNAT, AutoTLS, UPnP, circuit relay and WebRTC/WebSocket transports) into + * whatever is passed in, which would silently widen the peer topology. The + * light constructors apply exactly the configuration given here, which also + * keeps the vulnerable kad-DHT service out of the process. + * + * Bitswap is kept because it is how known peers exchange blocks. HTTP gateway + * routing is not registered, so block requests never leave the ADAMANT peer set. + * + * The node is returned started. The libp2p private key is loaded from — or + * created in — `datastore` under `/pkcs8/self`, which keeps the peer identity + * stable across restarts. + */ +export async function createIpfsNode(options: IpfsNodeOptions): Promise { + const { blockstore, datastore, listen, logger } = options + const bootstrapList = options.bootstrap ?? [] + + const peerDiscovery: Array> = [] + // `bootstrap()` throws on an empty list, so only register it when there is one + if (bootstrapList.length > 0) { + peerDiscovery.push(bootstrap({ list: bootstrapList })) + } + + const node = withBitswap( + withLibp2pLight, IpfsNodeServices>( + createHeliaLight({ blockstore, datastore, logger }), + { + addresses: { listen }, + transports: [tcp()], + connectionEncrypters: [noise()], + streamMuxers: [yamux()], + peerDiscovery, + services: { + identify: identify(), + ping: ping() + }, + connectionManager: { + maxConnections: options.maxConnections ?? DEFAULT_MAX_CONNECTIONS, + allow: options.allow ?? [] + }, + logger + } + ) + ) + + await node.start() + + return node +} + +/** + * Create the UnixFS interface used to add and read files. + * + * @param node A node returned by {@link createIpfsNode} + */ +export function createUnixFs(node: IpfsNode): UnixFS { + return unixfs(node) +} diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index 1e29d22..abd216f 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -1,6 +1,6 @@ import type { ErrorRequestHandler, RequestHandler } from 'express' import { getPublicError } from '../security/errors.js' -import { pino } from '../utils/logger.js' +import { logger } from '../utils/logger.js' /** Return a generic response for routes outside the documented API surface. */ export const notFoundHandler: RequestHandler = (req, res): void => { @@ -16,7 +16,7 @@ export const errorHandler: ErrorRequestHandler = (err, req, res, next): void => return } - pino.logger.error(err) + logger.error(err) const publicError = getPublicError(err) res.status(publicError.status).send(publicError.body) } diff --git a/src/multer.ts b/src/multer.ts index 2d6d6c8..37a59cd 100644 --- a/src/multer.ts +++ b/src/multer.ts @@ -5,10 +5,6 @@ import { UnixfsMulterStorage } from './utils/unixfs-multer.storage.js' import { createMultipartLimits } from './security/uploadLimits.js' export const multerStorage = multer({ - storage: new UnixfsMulterStorage({ - unixfs: ifs, - destination: () => '/', - filename: (_req, file) => file.originalname - }), + storage: new UnixfsMulterStorage({ unixfs: ifs }), limits: createMultipartLimits(config.uploadLimitSizeBytes, config.maxFileCount) }) diff --git a/src/store.ts b/src/store.ts index c5bdd42..f4c5b5b 100644 --- a/src/store.ts +++ b/src/store.ts @@ -11,3 +11,15 @@ export const datastorePath = join(userHomeDir, config.storeFolder, 'datastore') export const blockstore = new FsBlockstore(blockstorePath) export const datastore = new FsDatastore(datastorePath) + +/** + * Create the store directories if they are missing. + * + * Both stores default to `createIfMissing`, so this is safe to call on an + * existing store and makes a first run on a clean machine deterministic instead + * of relying on the first write to create the directory tree. + */ +export async function openStores(): Promise { + await blockstore.open() + await datastore.open() +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 43fb69d..7ef664e 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,9 +1,15 @@ +import { pino } from 'pino' import { pinoHttp } from 'pino-http' import { config } from '../config.js' -export const pino = pinoHttp({ - level: config.logLevel || 'info', - useLevel: 'debug', +/** + * Application logger. + * + * `pino-pretty` is loaded as a transport at runtime, so it is a runtime + * dependency rather than a development one. + */ +export const logger = pino({ + level: config.logLevel, transport: { target: 'pino-pretty', options: { @@ -13,3 +19,14 @@ export const pino = pinoHttp({ } } }) + +/** + * Express middleware that logs requests through {@link logger}. + * + * Request logs are emitted at `debug` level to keep them out of the default + * `info` output while still sharing the application logger's transport. + */ +export const httpLogger = pinoHttp({ + logger, + useLevel: 'debug' +}) diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index b9be662..6285f1a 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -1,33 +1,38 @@ import { StorageEngine } from 'multer' import * as e from 'express' import { UnixFS } from '@helia/unixfs' -import { pino } from './logger.js' +import { logger } from './logger.js' import { UnixFsMulterFile } from './types.js' import { sanitizeFilename } from './sanitizeFilename.js' export interface UnixfsStorageOptions { unixfs: UnixFS - destination: (req: e.Request, file: Express.Multer.File) => string - filename: (req: e.Request, file: Express.Multer.File) => string } export class UnixfsMulterStorage implements StorageEngine { constructor(private readonly options: UnixfsStorageOptions) {} + /** + * Import an uploaded file into the blockstore and attach its CID to the + * multer file record. + * + * `addByteStream` is used rather than `addFile`: since `@helia/unixfs` v4, + * `addFile` wraps the content in a UnixFS directory and returns the directory + * CID, which would change the CIDs this node has always issued. Adding the + * raw byte stream reproduces the pre-migration CIDs exactly. + * + * The filename never reaches the DAG, but it is still sanitized because it is + * echoed back in the upload response and written to the log. + */ _handleFile( req: e.Request, file: Express.Multer.File, callback: (error?: Error, info?: Partial) => void ): void { - const safeFilename = sanitizeFilename(file.originalname) - file.originalname = safeFilename - const folder = this.options.destination(req, file) - const filename = this.options.filename(req, file) + file.originalname = sanitizeFilename(file.originalname) + this.options.unixfs - .addFile({ - path: `${folder}/${filename}`, - content: file.stream - }) + .addByteStream(file.stream) .then((cid) => { callback(undefined, { ...file, cid }) }) @@ -41,7 +46,7 @@ export class UnixfsMulterStorage implements StorageEngine { file: Express.Multer.File, callback: (error: Error | null) => void ): void { - pino.logger.info('Remove file requested') + logger.info('Remove file requested') callback(null) } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index cdc39d7..eabdd9e 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,9 +2,9 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { config } from '../config.js' import { ConfigNode, NodeWithPeerId, UnixFsMulterFile } from './types.js' -import { statfs } from 'node:fs/promises' -import { pino } from './logger.js' -import { getFolderSizeBin } from 'go-get-folder-size' +import { access, lstat, opendir, statfs } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { logger } from './logger.js' /** * Get peerId from multiaddr string @@ -72,16 +72,73 @@ export function flatFiles( } } +/** + * Report free space on the filesystem that holds `path`. + * + * On a first run the store directory can be measured before it is created, so + * the nearest existing ancestor is used instead. It is on the same filesystem + * unless a mount point is created underneath it later, which would only matter + * once that mount exists and the directory itself becomes measurable. + * + * @param path Directory whose filesystem should be measured + */ export async function availableStorageSize(path: string) { - const statistics = await statfs(path, { bigint: true }) + const statistics = await statfs(await nearestExistingDir(path), { bigint: true }) return statistics.bsize * statistics.bavail } +async function nearestExistingDir(path: string): Promise { + let dir = path + + for (;;) { + try { + await access(dir) + return dir + } catch { + const parent = dirname(dir) + if (parent === dir) { + return dir + } + dir = parent + } + } +} + +/** + * Sum the apparent size, in bytes, of every regular file under `dir`. + * + * Symlinks are measured but not followed, so a link pointing outside the tree + * cannot inflate the total or cause an infinite walk. + * + * @param dir Directory to walk; a missing or unreadable directory yields 0 + */ export async function dirSize(dir: string): Promise { + let total = 0 + try { - return await getFolderSizeBin(dir, false, { loose: true }) - } catch (e) { - pino.logger.error(e) + const entries = await opendir(dir, { recursive: true }) + + for await (const entry of entries) { + if (entry.isDirectory()) { + continue + } + + try { + const stats = await lstat(join(entry.parentPath, entry.name)) + total += stats.size + } catch { + // The store is written concurrently, so entries can disappear mid-walk + } + } + } catch (err) { + // A store directory that does not exist yet simply holds nothing. This is + // the normal state on a first run, when the scan can start before the + // stores are created, so it is not worth logging as an error. + if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { + logger.error(err) + } + return 0 } - return 0 + + return total } diff --git a/test/cidCompatibility.test.ts b/test/cidCompatibility.test.ts new file mode 100644 index 0000000..994f448 --- /dev/null +++ b/test/cidCompatibility.test.ts @@ -0,0 +1,98 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { Readable } from 'node:stream' +import { after, before, describe, it } from 'node:test' +import { unixfs, type UnixFS } from '@helia/unixfs' +import { FsBlockstore } from 'blockstore-fs' +import { CID } from 'multiformats/cid' +import { CID_FIXTURES, README_FIXTURE, deterministicBytes } from './fixtures.js' +import { UnixfsMulterStorage } from '../src/utils/unixfs-multer.storage.js' +import type { UnixFsMulterFile } from '../src/utils/types.js' + +let storeDir: string +let blockstore: FsBlockstore +let ifs: UnixFS +let storage: UnixfsMulterStorage + +/** + * Push a buffer through the production multer storage engine and return the + * record it produces, exactly as `POST /api/file/upload` does. + */ +async function uploadThroughStorageEngine( + name: string, + content: Buffer +): Promise> { + const file = { + fieldname: 'files', + originalname: name, + encoding: '7bit', + mimetype: 'application/octet-stream', + stream: Readable.from(content) + } as unknown as Express.Multer.File + + return new Promise((resolve, reject) => { + storage._handleFile({} as never, file, (err, result) => { + if (err != null) { + reject(err) + return + } + resolve(result as Partial) + }) + }) +} + +before(async () => { + storeDir = await mkdtemp(join(tmpdir(), 'ipfs-node-cid-')) + blockstore = new FsBlockstore(join(storeDir, 'blockstore')) + await blockstore.open() + ifs = unixfs({ blockstore }) + storage = new UnixfsMulterStorage({ unixfs: ifs }) +}) + +after(async () => { + await blockstore.close() + await rm(storeDir, { recursive: true, force: true }) +}) + +describe('file CID compatibility with the pre-migration stack', () => { + it('reproduces the CID documented in README.md', async () => { + const { cid } = await uploadThroughStorageEngine(README_FIXTURE.name, README_FIXTURE.content) + + assert.equal(cid?.toString(), README_FIXTURE.cid) + }) + + for (const fixture of CID_FIXTURES) { + it(`reproduces the pre-migration CID for ${fixture.name} (${fixture.length} bytes)`, async () => { + const content = deterministicBytes(fixture.length, fixture.seed) + const { cid } = await uploadThroughStorageEngine(fixture.name, content) + + assert.equal(cid?.toString(), fixture.cid) + }) + } + + it('still sanitizes the filename, which does not affect the CID', async () => { + const content = deterministicBytes(1024, 'small') + const record = await uploadThroughStorageEngine('../folder\\evil\n\u202efile.txt', content) + + assert.equal(record.originalname, '_folder_evilfile.txt') + assert.equal(record.cid?.toString(), CID_FIXTURES[1].cid) + }) + + it('stores content that reads back byte-for-byte', async () => { + const content = deterministicBytes(3145735, 'd') + const { cid } = await uploadThroughStorageEngine('multi-chunk.bin', content) + const parsed = CID.parse(String(cid)) + + const stats = await ifs.stat(parsed, { offline: true }) + assert.equal(stats.size, BigInt(content.length)) + + const chunks: Uint8Array[] = [] + for await (const chunk of ifs.cat(parsed, { offline: true })) { + chunks.push(chunk) + } + + assert.deepEqual(Buffer.concat(chunks), content) + }) +}) diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 0000000..355ab98 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,153 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { ConfigError, config, configFileName, validateConfig } from '../src/config.js' + +/** A minimal config that passes validation; individual tests break one field. */ +function validRaw(): Record { + return { + nodes: [{ name: 'ipfs1', multiAddr: '/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWTest' }], + storeFolder: '.adm-ipfs', + logLevel: 'debug', + peerDiscovery: { + bootstrap: ['/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWTest'], + listen: ['/ip4/0.0.0.0/tcp/4001'] + }, + serverPort: 4000, + diskUsageScanPeriod: '*/30 * * * * *', + uploadLimitSizeBytes: 268435456, + maxFileCount: 10, + findFileTimeout: 20000, + cors: { allowedOrigins: ['https://adm.im'] }, + trustProxy: false, + adminApiKey: '', + enableDebugApi: false + } +} + +describe('configFileName', () => { + it('defaults to config.json5', () => { + assert.equal(configFileName(), 'config.json5') + assert.equal(configFileName(''), 'config.json5') + assert.equal(configFileName(undefined), 'config.json5') + }) + + it('maps a name to the documented config..json5 form', () => { + assert.equal(configFileName('test1'), 'config.test1.json5') + assert.equal(configFileName('default'), 'config.default.json5') + }) +}) + +describe('validateConfig', () => { + it('accepts a complete config', () => { + const parsed = validateConfig(validRaw()) + + assert.equal(parsed.serverPort, 4000) + assert.equal(parsed.nodes.length, 1) + assert.equal(parsed.nodes[0].name, 'ipfs1') + assert.deepEqual(parsed.peerDiscovery.listen, ['/ip4/0.0.0.0/tcp/4001']) + assert.deepEqual(parsed.cors.allowedOrigins, ['https://adm.im']) + assert.equal(parsed.enableDebugApi, false) + }) + + it('ignores unknown keys so deployments can carry extras', () => { + assert.doesNotThrow(() => validateConfig({ ...validRaw(), someFutureOption: 42 })) + }) + + const invalidCases: Array<[string, (raw: Record) => void, RegExp]> = [ + ['nodes is missing', (raw) => delete raw.nodes, /nodes/], + [ + 'a node has no multiAddr', + (raw) => { + raw.nodes = [{ name: 'ipfs1' }] + }, + /nodes\[0\]\.multiAddr/ + ], + [ + 'storeFolder is empty', + (raw) => { + raw.storeFolder = '' + }, + /storeFolder/ + ], + [ + 'logLevel is not a pino level', + (raw) => { + raw.logLevel = 'verbose' + }, + /logLevel/ + ], + [ + 'listen is empty', + (raw) => { + raw.peerDiscovery = { bootstrap: [], listen: [] } + }, + /peerDiscovery\.listen/ + ], + [ + 'bootstrap contains a non-string', + (raw) => { + raw.peerDiscovery = { bootstrap: [42], listen: ['/ip4/0.0.0.0/tcp/4001'] } + }, + /peerDiscovery\.bootstrap\[0\]/ + ], + [ + 'serverPort is not an integer', + (raw) => { + raw.serverPort = 4000.5 + }, + /serverPort/ + ], + [ + 'diskUsageScanPeriod is missing', + (raw) => delete raw.diskUsageScanPeriod, + /diskUsageScanPeriod/ + ], + [ + 'findFileTimeout is negative', + (raw) => { + raw.findFileTimeout = -1 + }, + /findFileTimeout/ + ] + ] + + for (const [description, mutate, expected] of invalidCases) { + it(`rejects a config where ${description}`, () => { + const raw = validRaw() + mutate(raw) + + assert.throws( + () => validateConfig(raw), + (err: unknown) => { + assert.ok(err instanceof ConfigError) + assert.match(err.message, expected) + return true + } + ) + }) + } + + it('rejects a config that is not an object', () => { + assert.throws(() => validateConfig(null), ConfigError) + assert.throws(() => validateConfig([]), ConfigError) + }) + + it('delegates the security surface to validateSecurityConfig', () => { + // Not a ConfigError: the security validator owns these fields and its + // messages are asserted in security.test.ts + assert.throws(() => validateConfig({ ...validRaw(), trustProxy: true }), /trustProxy/) + assert.throws(() => validateConfig({ ...validRaw(), maxFileCount: 0 }), /maxFileCount/) + assert.throws( + () => validateConfig({ ...validRaw(), cors: { allowedOrigins: ['*'] } }), + /origin/i + ) + }) +}) + +describe('the config loaded at startup', () => { + it('is validated and exposes the test configuration', () => { + assert.equal(config.logLevel, 'silent') + assert.deepEqual(config.peerDiscovery.bootstrap, []) + assert.equal(config.enableDebugApi, false) + }) +}) diff --git a/test/diskUsage.test.ts b/test/diskUsage.test.ts new file mode 100644 index 0000000..43e6d32 --- /dev/null +++ b/test/diskUsage.test.ts @@ -0,0 +1,77 @@ +import assert from 'node:assert/strict' +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { describe, it } from 'node:test' +import { availableStorageSize, dirSize } from '../src/utils/utils.js' + +describe('dirSize', () => { + it('sums file sizes recursively', async () => { + const dir = await mkdtemp(join(tmpdir(), 'ipfs-node-dirsize-')) + + try { + await writeFile(join(dir, 'a.bin'), Buffer.alloc(100)) + await mkdir(join(dir, 'nested', 'deeper'), { recursive: true }) + await writeFile(join(dir, 'nested', 'b.bin'), Buffer.alloc(250)) + await writeFile(join(dir, 'nested', 'deeper', 'c.bin'), Buffer.alloc(1)) + + assert.equal(await dirSize(dir), 351) + } finally { + await rm(dir, { recursive: true, force: true }) + } + }) + + it('does not follow symlinks out of the tree', async () => { + const dir = await mkdtemp(join(tmpdir(), 'ipfs-node-dirsize-link-')) + const outside = await mkdtemp(join(tmpdir(), 'ipfs-node-dirsize-outside-')) + + try { + await writeFile(join(outside, 'big.bin'), Buffer.alloc(10_000)) + await writeFile(join(dir, 'a.bin'), Buffer.alloc(100)) + await symlink(join(outside, 'big.bin'), join(dir, 'link.bin')) + + // The link itself is counted, its 10 kB target is not + const size = await dirSize(dir) + assert.ok(size >= 100 && size < 1000, `unexpected size ${size}`) + } finally { + await rm(dir, { recursive: true, force: true }) + await rm(outside, { recursive: true, force: true }) + } + }) + + it('returns 0 for a missing directory', async () => { + assert.equal(await dirSize(join(tmpdir(), 'ipfs-node-does-not-exist-12345')), 0) + }) + + it('returns 0 for an empty directory', async () => { + const dir = await mkdtemp(join(tmpdir(), 'ipfs-node-dirsize-empty-')) + + try { + assert.equal(await dirSize(dir), 0) + } finally { + await rm(dir, { recursive: true, force: true }) + } + }) +}) + +describe('availableStorageSize', () => { + it('reports free space for an existing directory', async () => { + const free = await availableStorageSize(tmpdir()) + + assert.equal(typeof free, 'bigint') + assert.ok(free > 0n) + }) + + it('falls back to the nearest existing ancestor on a first run', async () => { + const parent = await mkdtemp(join(tmpdir(), 'ipfs-node-statfs-')) + + try { + // The store directory does not exist yet, as on a first start + const missing = join(parent, 'store', 'blockstore') + + assert.equal(await availableStorageSize(missing), await availableStorageSize(parent)) + } finally { + await rm(parent, { recursive: true, force: true }) + } + }) +}) diff --git a/test/fixtures.ts b/test/fixtures.ts new file mode 100644 index 0000000..eb68a25 --- /dev/null +++ b/test/fixtures.ts @@ -0,0 +1,96 @@ +import { createHash } from 'node:crypto' + +/** + * Deterministic pseudo-random bytes. + * + * The generator is a SHA-256 chain seeded with `seed`, so the same seed and + * length always produce the same bytes. This is what makes the recorded CIDs in + * {@link CID_FIXTURES} reproducible. + * + * @param length Number of bytes to produce + * @param seed Seed string mixed into the first hash + */ +export function deterministicBytes(length: number, seed: string): Buffer { + const out = Buffer.alloc(length) + let block = createHash('sha256').update(seed).digest() + + for (let offset = 0; offset < length; offset += 32) { + block.copy(out, offset, 0, Math.min(32, length - offset)) + block = createHash('sha256').update(block).digest() + } + + return out +} + +export interface CidFixture { + name: string + length: number + seed: string + /** CID produced by the pre-migration stack (helia 4.2.6 / @helia/unixfs 3.0.7). */ + cid: string +} + +/** + * File CIDs recorded from the pre-migration stack. + * + * Sizes bracket the 1 MiB default chunk size so that both the single-block + * (`raw`) and the multi-block (`dag-pb`) import paths are covered. `hello.txt` + * is the example documented in `README.md`. + */ +export const CID_FIXTURES: CidFixture[] = [ + { + name: 'empty.bin', + length: 0, + seed: 'empty', + cid: 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' + }, + { + name: 'small.bin', + length: 1024, + seed: 'small', + cid: 'bafkreihfzyt2g6pbd4rhk67deek7xh33xams74spn72eqq5qhx2ypphvii' + }, + { + name: 'unicode-файл.bin', + length: 5000, + seed: 'u', + cid: 'bafkreig2ml3a53tagqpjbqbh7o6gr3rb2j4bvdbjiq3azpjp6yi7rzhzha' + }, + { + name: 'chunk-minus-1.bin', + length: 1048575, + seed: 'a', + cid: 'bafkreibvqh3ms3gu3igry47phefzvsw3ebc2kgdw7ax5ulbb4vwtflcyme' + }, + { + name: 'chunk-exact.bin', + length: 1048576, + seed: 'b', + cid: 'bafkreig3i63vhvjjvolfvk3cqi6hpmumwfn2cqvln7uqgvdynrr2h7l5x4' + }, + { + name: 'chunk-plus-1.bin', + length: 1048577, + seed: 'c', + cid: 'bafybeiccwqpicdluy2i54utx2ao4eus3ngjkuput3rxqasdb7wqi5g5zvq' + }, + { + name: 'multi-chunk.bin', + length: 3145735, + seed: 'd', + cid: 'bafybeifo32iadmp7eawlzbipy7s2k67774lumc7fb6yrtnjofylt2tuhgq' + }, + { + name: 'deep-dag.bin', + length: 12582925, + seed: 'e', + cid: 'bafybeifc5mk2pihtuaksnact2orkabzuf3z4wtn7jwrm7e2d2scvkdkkhu' + } +] + +/** The `hello.txt` upload documented in `README.md`, with its published CID. */ +export const README_FIXTURE = { + name: 'hello.txt', + content: Buffer.from('Hello ipfs-node!\n', 'utf8'), + cid: 'bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm' +} diff --git a/test/integration/peerExchange.test.ts b/test/integration/peerExchange.test.ts new file mode 100644 index 0000000..565a0ea --- /dev/null +++ b/test/integration/peerExchange.test.ts @@ -0,0 +1,166 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { Readable } from 'node:stream' +import { after, before, describe, it } from 'node:test' +import { FsBlockstore } from 'blockstore-fs' +import { FsDatastore } from 'datastore-fs' +import { CID } from 'multiformats/cid' +import { createIpfsNode, createUnixFs, type IpfsNode } from '../../src/ipfs-node.js' +import { README_FIXTURE, deterministicBytes } from '../fixtures.js' + +/** Only loopback, and port 0 so the OS picks a free port. */ +const LISTEN = ['/ip4/127.0.0.1/tcp/0'] + +interface TestNode { + node: IpfsNode + blockstore: FsBlockstore + datastore: FsDatastore +} + +/** + * Start a node backed by a throwaway on-disk store. + * + * @param dir Store directory; pass an existing one to restart a node with its + * previous identity + * @param bootstrap Multiaddrs to dial on startup + */ +async function startNode(dir: string, bootstrap: string[] = []): Promise { + const blockstore = new FsBlockstore(join(dir, 'blockstore')) + const datastore = new FsDatastore(join(dir, 'datastore')) + await blockstore.open() + await datastore.open() + + const node = await createIpfsNode({ blockstore, datastore, listen: LISTEN, bootstrap }) + + return { node, blockstore, datastore } +} + +async function stopNode(testNode: TestNode): Promise { + await testNode.node.stop() + await testNode.blockstore.close() + await testNode.datastore.close() +} + +let dirA: string +let dirB: string +let nodeA: TestNode +let nodeB: TestNode + +before(async () => { + dirA = await mkdtemp(join(tmpdir(), 'ipfs-node-a-')) + dirB = await mkdtemp(join(tmpdir(), 'ipfs-node-b-')) + + nodeA = await startNode(dirA) + nodeB = await startNode(dirB) + + // Connect B to A directly; the ADAMANT topology relies on known peers only + await nodeB.node.libp2p.dial(nodeA.node.libp2p.getMultiaddrs()) +}) + +after(async () => { + await stopNode(nodeB) + await stopNode(nodeA) + await rm(dirA, { recursive: true, force: true }) + await rm(dirB, { recursive: true, force: true }) +}) + +describe('two isolated nodes', () => { + it('start with distinct peer ids', () => { + assert.notEqual(nodeA.node.libp2p.peerId.toString(), nodeB.node.libp2p.peerId.toString()) + assert.equal(nodeA.node.status, 'started') + assert.equal(nodeB.node.status, 'started') + }) + + it('register only the identify and ping services', () => { + assert.deepEqual(Object.keys(nodeA.node.libp2p.services).sort(), ['identify', 'ping']) + }) + + it('are connected to each other', () => { + const peers = nodeB.node.libp2p.getPeers().map((peer) => peer.toString()) + + assert.ok(peers.includes(nodeA.node.libp2p.peerId.toString())) + }) + + it('answer the ping service used by GET /api/libp2p/services/ping', async () => { + const rtt = await nodeB.node.libp2p.services.ping.ping(nodeA.node.libp2p.peerId) + + assert.equal(typeof rtt, 'number') + assert.ok(rtt >= 0) + }) + + it('expose JSON-serialisable connection data for GET /api/libp2p/connections', () => { + const [connection] = nodeB.node.libp2p.getConnections() + + assert.ok(connection) + assert.equal(connection.multiplexer, '/yamux/1.0.0') + assert.equal(connection.encryption, '/noise') + assert.equal(connection.remotePeer.toString(), nodeA.node.libp2p.peerId.toString()) + }) +}) + +describe('file exchange between known peers', () => { + it('a file uploaded to node A is retrievable from node B', async () => { + const ifsA = createUnixFs(nodeA.node) + const ifsB = createUnixFs(nodeB.node) + + const content = deterministicBytes(3145735, 'd') + const cid = await ifsA.addByteStream(Readable.from(content)) + for await (const _pinned of nodeA.node.pins.add(cid)) { + // draining the generator performs the pin + } + + // Node B has none of these blocks locally; they must come from node A + const localOnly = ifsB.cat(cid, { offline: true }) + await assert.rejects(async () => { + for await (const _chunk of localOnly) { + // draining is enough to trigger the local lookup + } + }) + + const chunks: Uint8Array[] = [] + for await (const chunk of ifsB.cat(cid, { signal: AbortSignal.timeout(30_000) })) { + chunks.push(chunk) + } + + assert.deepEqual(Buffer.concat(chunks), content) + }) + + it('a transferred file keeps its pre-migration CID', async () => { + const ifsA = createUnixFs(nodeA.node) + const ifsB = createUnixFs(nodeB.node) + + const cid = await ifsA.addByteStream(Readable.from(README_FIXTURE.content)) + assert.equal(cid.toString(), README_FIXTURE.cid) + + const chunks: Uint8Array[] = [] + for await (const chunk of ifsB.cat(CID.parse(README_FIXTURE.cid), { + signal: AbortSignal.timeout(30_000) + })) { + chunks.push(chunk) + } + + assert.deepEqual(Buffer.concat(chunks), README_FIXTURE.content) + }) +}) + +describe('peer identity', () => { + it('is stable across a restart with the same store', async () => { + const dir = await mkdtemp(join(tmpdir(), 'ipfs-node-restart-')) + + try { + const first = await startNode(dir) + const firstPeerId = first.node.libp2p.peerId.toString() + await stopNode(first) + + const second = await startNode(dir) + const secondPeerId = second.node.libp2p.peerId.toString() + await stopNode(second) + + assert.equal(secondPeerId, firstPeerId) + } finally { + await rm(dir, { recursive: true, force: true }) + } + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 2d609d8..8e1aec0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,11 +2,11 @@ "compilerOptions": { "allowJs": false, "checkJs": false, - "lib": ["ES2022", "DOM"], + "lib": ["ES2024", "DOM"], "module": "NodeNext", "moduleResolution": "NodeNext", "sourceMap": true, - "target": "ES2022", + "target": "ES2024", "strict": true, "noImplicitAny": true, "useUnknownInCatchVariables": false, diff --git a/tsconfig.test.json b/tsconfig.test.json index 4055431..60855bf 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -4,5 +4,5 @@ "outDir": "./dist-test", "rootDir": "." }, - "include": ["src/security/**/*.ts", "src/utils/sanitizeFilename.ts", "test/**/*.ts"] + "include": ["src/**/*.ts", "test/**/*.ts"] } From bd3b442c7168e08fbedbfa0d31a8ae9792f5f4d2 Mon Sep 17 00:00:00 2001 From: adamantmm Date: Sat, 22 Aug 2026 16:26:03 +0300 Subject: [PATCH 101/167] fix(tsconfig): cover test sources in the type-checked project `tsconfig.json` included only `src`, so `test/**` belonged to no project. Editors resolve a file to the nearest `tsconfig.json`; when that config does not list the file, they fall back to default compiler options instead of the project's `lib`, `module` and `moduleResolution` settings, and report module resolution and missing `@types/node` errors that neither `npm run build` nor `npm test` produces. Split the single configuration in three so the project-wide file can cover every type-checked source without changing what the builds emit: - `tsconfig.json` type-checks `src` and `test` and emits nothing - `tsconfig.build.json` builds `src` into `dist` - `tsconfig.test.json` builds `src` and `test` into `dist-test` `rootDir` is now explicit in both build configurations rather than inferred from the common ancestor of the input files, and `dist` and `dist-test` are excluded so compiler output can never become compiler input. Document the layout, and the fact that naming a file on the `tsc` command line bypasses `tsconfig.json` entirely, which leaves `outDir` unapplied and emits the `.js` next to its source. Co-Authored-By: Claude Opus 5 --- README.md | 18 ++++++++++++++++++ package.json | 2 +- tsconfig.build.json | 10 ++++++++++ tsconfig.json | 11 ++++++++--- tsconfig.test.json | 2 ++ 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tsconfig.build.json diff --git a/README.md b/README.md index 16e7d46..ccc0fe5 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,24 @@ npm test The unit suites cover the HTTP security boundary, configuration validation, filename sanitization, disk measurement, and the file CIDs issued before the Helia migration. The integration suite starts two isolated nodes with temporary stores, transfers a file between them over bitswap, and verifies that a peer identity survives a restart. +### TypeScript project layout + +Three configurations share one set of compiler options: + +| File | Purpose | +| --------------------- | --------------------------------------------------------------------- | +| `tsconfig.json` | Type-checks `src` and `test`. Emits nothing; this is what editors use | +| `tsconfig.build.json` | Builds `src` into `dist` for `npm run build` | +| `tsconfig.test.json` | Builds `src` and `test` into `dist-test` for `npm test` | + +Type-check everything without emitting: + +```bash +npx tsc --noEmit +``` + +Pass no file arguments to `tsc`. Naming a file on the command line makes TypeScript ignore `tsconfig.json` entirely, so `outDir` is not applied — the `.js` file is written next to its source — and the project's `lib`, `module`, and `moduleResolution` settings are replaced by defaults, which reports module-resolution and missing-`@types/node` errors that the project itself does not have. + ## API usage ### Upload files diff --git a/package.json b/package.json index 9f65715..75fe60f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "private": true, "type": "module", "scripts": { - "build": "rimraf dist && tsc", + "build": "rimraf dist && tsc -p tsconfig.build.json", "start": "node dist/index.js", "dev": "nodemon", "lint": "eslint .", diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..68e52da --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + // Application build: `src` only, emitted to `dist`. + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 8e1aec0..c41f38b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,9 @@ { + // Project-wide configuration. Editors resolve a file to the nearest + // tsconfig.json, so this one must cover every type-checked file; anything it + // misses is type-checked by the editor under default compiler options, which + // report spurious errors about module resolution and missing Node typings. + // It never emits: `tsconfig.build.json` and `tsconfig.test.json` do that. "compilerOptions": { "allowJs": false, "checkJs": false, @@ -11,8 +16,8 @@ "noImplicitAny": true, "useUnknownInCatchVariables": false, "skipLibCheck": true, - "outDir": "./dist" + "noEmit": true }, - "exclude": ["node_modules"], - "include": ["src/index.ts", "src/**/*.ts"] + "exclude": ["node_modules", "dist", "dist-test"], + "include": ["src/**/*.ts", "test/**/*.ts"] } diff --git a/tsconfig.test.json b/tsconfig.test.json index 60855bf..5d767e2 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,6 +1,8 @@ { + // Test build: `src` and `test`, emitted to `dist-test` preserving both trees. "extends": "./tsconfig.json", "compilerOptions": { + "noEmit": false, "outDir": "./dist-test", "rootDir": "." }, From f3d29631cde538017b15a78016ef4b74bae3460f Mon Sep 17 00:00:00 2001 From: adamantmm Date: Sat, 22 Aug 2026 17:29:13 +0300 Subject: [PATCH 102/167] feat(storage): implement file registry and upload session management - Add `FileRegistry` for lifecycle management of uploaded files, including confirmation and expiration handling. - Introduce `UploadSession` to track uploads, manage blockstore interactions, and ensure cleanup of uncommitted uploads. - Implement concurrency limits for uploads to prevent race conditions. - Create tests for file registry, garbage collection, replication, and storage limits to ensure functionality and reliability. - Enhance storage configuration resolution to enforce limits and defaults. --- README.md | 121 +++++++- config.default.json5 | 36 ++- config.test.json5 | 25 +- docs/storage-lifecycle.md | 236 ++++++++++++++++ src/api/file.ts | 194 ++++++++++--- src/api/helia.ts | 13 +- src/api/index.ts | 17 +- src/api/node.ts | 9 +- src/api/replication.ts | 29 ++ src/api/storage.ts | 109 ++++++++ src/config.ts | 53 +++- src/disk-usage.cron.ts | 31 +-- src/gc.cron.ts | 89 ++++++ src/index.ts | 34 ++- src/middleware/uploadGuards.ts | 123 +++++++++ src/multer.ts | 4 +- src/replication.cron.ts | 62 +++++ src/security/accessPolicy.ts | 20 +- src/security/errors.ts | 7 + src/security/replicationToken.ts | 43 +++ src/storage/config.ts | 262 ++++++++++++++++++ src/storage/gc.ts | 238 ++++++++++++++++ src/storage/limits.ts | 135 +++++++++ src/storage/metrics.ts | 98 +++++++ src/storage/pinning.ts | 84 ++++++ src/storage/registry.ts | 254 +++++++++++++++++ src/storage/replication.ts | 133 +++++++++ src/storage/service.ts | 171 ++++++++++++ src/storage/state.ts | 13 + src/storage/uploadSession.ts | 217 +++++++++++++++ src/utils/types.ts | 9 +- src/utils/unixfs-multer.storage.ts | 67 ++++- src/utils/utils.ts | 3 +- test/cidCompatibility.test.ts | 17 +- test/fileRegistry.test.ts | 177 ++++++++++++ test/garbageCollectionPlan.test.ts | 101 +++++++ test/integration/storageLifecycle.test.ts | 322 ++++++++++++++++++++++ test/replication.test.ts | 111 ++++++++ test/security.test.ts | 61 +++- test/storageConfig.test.ts | 95 +++++++ test/storageLimits.test.ts | 113 ++++++++ 41 files changed, 3840 insertions(+), 96 deletions(-) create mode 100644 docs/storage-lifecycle.md create mode 100644 src/api/replication.ts create mode 100644 src/api/storage.ts create mode 100644 src/gc.cron.ts create mode 100644 src/middleware/uploadGuards.ts create mode 100644 src/replication.cron.ts create mode 100644 src/security/replicationToken.ts create mode 100644 src/storage/config.ts create mode 100644 src/storage/gc.ts create mode 100644 src/storage/limits.ts create mode 100644 src/storage/metrics.ts create mode 100644 src/storage/pinning.ts create mode 100644 src/storage/registry.ts create mode 100644 src/storage/replication.ts create mode 100644 src/storage/service.ts create mode 100644 src/storage/state.ts create mode 100644 src/storage/uploadSession.ts create mode 100644 test/fileRegistry.test.ts create mode 100644 test/garbageCollectionPlan.test.ts create mode 100644 test/integration/storageLifecycle.test.ts create mode 100644 test/replication.test.ts create mode 100644 test/storageConfig.test.ts create mode 100644 test/storageLimits.test.ts diff --git a/README.md b/README.md index ccc0fe5..89a4327 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,35 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "read": { "windowMs": 60000, "limit": 100 } }, "adminApiKey": "", - "enableDebugApi": false + "enableDebugApi": false, + "storage": { + "maxRequestSizeBytes": 536870912, + "maxConcurrentUploads": 4, + "diskReserveBytes": 5368709120, + "confirmationRequired": false, + "temporaryTtlMs": 86400000, + "gc": { + "enabled": false, + "schedule": "0 */15 * * * *", + "highWatermarkBytes": 53687091200, + "lowWatermarkBytes": 42949672960 + } + }, + "replication": { + "enabled": false, + "factor": 2, + "ackQuorum": 1, + "requireQuorumOnUpload": false, + "requestTimeoutMs": 30000, + "repairEnabled": true, + "repairSchedule": "0 */30 * * * *", + "token": "" + } } ``` +`storage` and `replication` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Both sections are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. + Generate the administrative secret before enabling operator endpoints: ```bash @@ -77,18 +102,24 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - Leave `trustProxy` as `false` for direct connections; configure exact proxy addresses, CIDR ranges, or a verified hop count behind a proxy - Set `enableDebugApi: true` only when the authenticated debug route is operationally required - Tune the endpoint-specific `rateLimits` for the deployment perimeter +- Review `storage.diskReserveBytes` and `storage.maxRequestSizeBytes` for the deployment volume; the defaults suit a dedicated disk +- Leave `storage.gc.enabled` as `false` until a deletion policy is agreed; `POST /api/storage/gc` runs on demand meanwhile +- Set `replication.token` on every node that should accept copies, and `replication.enabled` on every node that should push them +- Add `apiUrl` to each entry of `nodes` that takes part in replication Invalid CORS, proxy, API-key, or rate-limit configuration stops the process instead of silently weakening the boundary. ## HTTP access policy -| Class | Routes | Policy | -| -------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------- | -| Public | `GET /`, `GET /api/node/health` | No authentication | -| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | -| Administrative | `GET /api/node/info`, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | -| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | -| Authenticated user | None | The service has no end-user identity or session layer | +| Class | Routes | Policy | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | +| Public | `GET /`, `GET /api/node/health` | No authentication | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | +| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | +| Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | +| Peer replication | `POST /api/replication/:cid` | A matching `x-replication-token` header is required; the admin key is not accepted | +| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | +| Authenticated user | None | The service has no end-user identity or session layer | Administrative coverage includes pin operations, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. @@ -102,7 +133,7 @@ This is an explicit compatibility decision, not an authorization guarantee. A de The multipart contract accepts `files` parts only. Text fields are rejected with a controlled `400 Bad Request` response. -Interrupted uploads can leave unpinned blocks in the blockstore. Deleting only the returned root CID would miss descendant blocks, while recursively deleting content-addressed blocks could remove data shared with pinned files. Until [#22](https://github.com/Adamant-im/ipfs-node/issues/22) adds reference-aware garbage collection, operators must monitor and bound blockstore growth at the deployment layer. +An interrupted upload no longer leaves blocks behind. Each request owns a session that records the blocks it created; a rejected, aborted, or partially failed request removes exactly those, skipping blocks that already existed, blocks a concurrent upload is still writing, and blocks a pin protects. Whatever survives cleanup is unpinned and reclaimable by garbage collection. Blockstore growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the collection watermarks — see [docs/storage-lifecycle.md](docs/storage-lifecycle.md). ### CORS @@ -214,10 +245,68 @@ Example response: ```json { "filesNames": ["file.txt"], - "cids": ["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"] + "cids": ["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"], + "files": [ + { + "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", + "name": "file.txt", + "state": "confirmed", + "expiresAt": null + } + ], + "replication": { + "mode": "best-effort", + "factor": 1, + "required": 1, + "acknowledged": 1, + "replicas": [], + "satisfied": true, + "attempts": [] + } +} +``` + +Upload responses use the following stable status contract: + +| Status | Meaning | +| -------------------------- | -------------------------------------------------------------------------- | +| `200 OK` | Every file was stored and pinned | +| `400 Bad Request` | No file was sent, too many files, or a single file exceeded its size limit | +| `413 Payload Too Large` | The combined size of the files exceeded `storage.maxRequestSizeBytes` | +| `429 Too Many Requests` | The upload rate limit or the concurrent upload limit was exceeded | +| `503 Service Unavailable` | The replication quorum was required and could not be reached | +| `507 Insufficient Storage` | Storing the request would consume `storage.diskReserveBytes` | + +### Check the state of a file + +```bash +curl --fail-with-body \ + https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm/status +``` + +Example response: + +```json +{ + "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", + "state": "confirmed", + "pinned": true, + "createdAt": 1720614998797, + "expiresAt": null, + "confirmedAt": 1720614998797, + "replication": { "acknowledged": 1, "required": 1, "factor": 1 } } ``` +### Read the storage report and policy + +```bash +curl --fail-with-body https://ipfs.example.org/api/storage/metrics +curl --fail-with-body https://ipfs.example.org/api/storage/policy +``` + +`metrics` reports pinned, reclaimable, available, and reserved bytes together with the lifecycle counters. `policy` reports the limits and the durability mode a client should expect before uploading. + ### Download a file ```bash @@ -261,6 +350,18 @@ curl --fail-with-body \ https://ipfs.example.org/api/node/info ``` +### Run garbage collection + +Add `?dryRun=true` to report the exact CIDs that would be released and retained without deleting anything. This is the supported way to review a deletion policy before enabling the scheduled collector. + +```bash +curl --fail-with-body --request POST \ + --header 'x-api-key: your-generated-key' \ + 'https://ipfs.example.org/api/storage/gc?dryRun=true' +``` + +`POST /api/storage/repair` pushes copies of any confirmed file that holds fewer than `replication.factor` of them. + ## Validation ```bash diff --git a/config.default.json5 b/config.default.json5 index ad8a0c7..c51d247 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -3,6 +3,7 @@ { name: 'ipfs1', multiAddr: '/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq' + // apiUrl: 'https://ipfs1.example', // Only needed when replication is enabled }, { name: 'ipfs2', @@ -50,5 +51,38 @@ // Admin routes return 503 while this value is empty. adminApiKey: '', // The debug API is absent unless explicitly enabled; it still requires the admin key. - enableDebugApi: false + enableDebugApi: false, + // Bounded storage lifecycle. Every option has a documented default, so an + // existing configuration file keeps working without this section. + storage: { + maxRequestSizeBytes: 536870912, // 512 MiB. Combined size of all files in one request + maxConcurrentUploads: 4, // Uploads writing into the blockstore at the same time + diskReserveBytes: 5368709120, // 5 GiB of free space uploads must never consume + // true keeps an upload temporary until POST /api/file/:cid/confirm. + // Enabling it is a protocol change: clients must perform that call. + confirmationRequired: false, + temporaryTtlMs: 86400000, // 24 h. Lifetime of an unconfirmed upload + gc: { + // Controls the scheduled collector only. POST /api/storage/gc always runs + // on demand. Enable this after agreeing on a deletion policy. + enabled: false, + schedule: '0 */15 * * * *', + highWatermarkBytes: 53687091200, // 50 GiB. Collection starts above this blockstore size + lowWatermarkBytes: 42949672960 // 40 GiB. Collection reclaims down to this size + } + }, + // Replication across the nodes listed in `nodes` that declare an `apiUrl`. + // Disabled by default: the node then stores content best effort, locally only. + replication: { + enabled: false, + factor: 2, // Copies that must exist, including this node + ackQuorum: 1, // Acknowledgements required before an upload is durable + requireQuorumOnUpload: false, // true rejects an upload that cannot reach the quorum + requestTimeoutMs: 30000, + repairEnabled: true, + repairSchedule: '0 */30 * * * *', + // Shared by all ADAMANT nodes, sent as x-replication-token. + // Generate with: openssl rand -hex 32 + token: '' + } } diff --git a/config.test.json5 b/config.test.json5 index aecf76f..7cc611c 100644 --- a/config.test.json5 +++ b/config.test.json5 @@ -24,5 +24,28 @@ read: { windowMs: 60000, limit: 100 } }, adminApiKey: '', - enableDebugApi: false + enableDebugApi: false, + storage: { + maxRequestSizeBytes: 536870912, + maxConcurrentUploads: 2, + diskReserveBytes: 1048576, + confirmationRequired: false, + temporaryTtlMs: 3600000, + gc: { + enabled: false, + schedule: '0 */15 * * * *', + highWatermarkBytes: 10485760, + lowWatermarkBytes: 5242880 + } + }, + replication: { + enabled: false, + factor: 2, + ackQuorum: 1, + requireQuorumOnUpload: false, + requestTimeoutMs: 5000, + repairEnabled: false, + repairSchedule: '0 */30 * * * *', + token: '' + } } diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md new file mode 100644 index 0000000..bf51c2f --- /dev/null +++ b/docs/storage-lifecycle.md @@ -0,0 +1,236 @@ +# Storage lifecycle, garbage collection, and replication + +This document describes how an ADAMANT IPFS node bounds its disk usage, what +happens to an uploaded file over time, and what durability the node promises. + +Configuration lives in the `storage` and `replication` sections of the node +configuration file. Both sections are optional: every option has a documented +default, so a configuration file written before this feature keeps working. See +`config.default.json5`. + +## Upload admission + +An upload is refused before a single block reaches the blockstore when any of +these limits is exceeded. + +| Limit | Option | Response | +| ---------------------- | ------------------------------ | -------- | +| Concurrent uploads | `storage.maxConcurrentUploads` | `429` | +| Aggregate request size | `storage.maxRequestSizeBytes` | `413` | +| Disk reserve | `storage.diskReserveBytes` | `507` | +| Files per request | `maxFileCount` | `400` | +| Single file size | `uploadLimitSizeBytes` | `400` | + +The first three are checked by the upload guard before the multipart parser +runs. The last two are enforced by the parser itself through +`createMultipartLimits`, which aborts the request before handing an over-limit +part to the storage engine. + +The aggregate size is checked twice: against `Content-Length` before parsing, +and against the bytes actually streamed, because a chunked request declares no +size. + +Free space is measured on the filesystem that holds the blockstore, not on the +root filesystem. A request is admitted only when +`freeSpace - declaredRequestSize >= storage.diskReserveBytes`. + +## Cleanup of failed uploads + +Every upload request owns a session that records the blocks it created. Blocks +that already existed are not recorded, so a failing upload cannot delete content +that was stored earlier. + +The session is committed only after every file of the request has been stored, +pinned, and registered. Any other outcome removes the recorded blocks: + +- the parser rejected the request +- a file failed to import +- the route failed after some files were already imported +- the client disconnected mid-upload + +A recorded block is deleted only when no other in-flight upload references it and +no pin protects it. Anything that survives cleanup is unpinned and therefore +reclaimable by the next collection. + +Cleanup deletes through the file-backed blockstore rather than through the Helia +blockstore facade. The facade cancels a reprovide before deleting, and this node +registers no content routers on purpose, so a delete through it always fails. + +## File states + +| State | Meaning | Pinned | Reclaimable | +| ----------- | --------------------------------------- | ------ | ----------------- | +| `temporary` | Accepted, not yet durable | yes | after `expiresAt` | +| `confirmed` | Durable | yes | never | +| `expired` | Released by TTL or by an explicit unpin | no | yes | + +The state of every file this node accepted is stored in the node datastore under +the `/adm/files` prefix, so it survives a restart. `GET /api/file/:cid/status` +reports it. + +`storage.confirmationRequired` decides which state an upload starts in: + +- `false` (default): uploads are confirmed immediately. This is the behaviour the + current ADAMANT client protocol expects, and no file expires on its own. +- `true`: uploads stay `temporary` until an authorized confirmation arrives, and + an unconfirmed upload becomes reclaimable after `storage.temporaryTtlMs`. + Enabling this is a protocol change: clients must call the confirm endpoint. + +### Authorized transitions + +These routes change durability and require the administrative key +(`adminApiKey`, sent as `x-api-key`): + +- `POST /api/file/:cid/confirm` — `temporary` to `confirmed` +- `POST /api/file/:cid/unpin` — `confirmed` to `expired` +- `POST /api/helia/pin/:cid` — pin and register arbitrary content +- `POST /api/storage/gc`, `POST /api/storage/repair` + +The guard fails closed. With no key configured the routes answer `503` rather +than being open, so a node cannot be filled with permanently pinned content by +an anonymous caller. + +`POST /api/replication/:cid` is different: it is authorized by +`replication.token`, a secret shared by the ADAMANT nodes. A peer only ever asks +this node to store a copy, so distributing the administrative key across the node +set would grant far more than replication needs. Setting the token opens the +intake route on its own; `replication.enabled` only governs whether this node +pushes copies out. + +## Garbage collection + +Collection applies two independent rules: + +1. **Expiry.** A `temporary` file that outlived `expiresAt` is released, + whatever the blockstore size is. +2. **Watermarks.** When the blockstore grows above + `storage.gc.highWatermarkBytes`, the oldest unconfirmed files are released + until the estimated size drops below `storage.gc.lowWatermarkBytes`. The gap + between the two thresholds stops the collector from running on every tick. + +Released files lose their pin, then Helia deletes every unpinned block. That +pass also reclaims blocks cached while serving other peers. + +Confirmed files are never selected by the planner, and Helia never deletes a +pinned block. Before deleting anything, the collector verifies that every +confirmed file is still protected and restores a missing pin first. + +`storage.gc.enabled` controls the **scheduled** collector and is `false` by +default: deletion in production must be agreed with the maintainers first. The +authorized `POST /api/storage/gc` endpoint always runs on demand, and +`?dryRun=true` reports the exact CIDs that would be released and retained without +touching a block. + +The `removedCids` field of a collection report identifies blocks, not files. The +blockstore addresses content by multihash and rebuilds a CID with its own default +codec while listing, so an entry can read differently from the CID a file was +uploaded under. + +Collection takes the Helia blockstore write lock, so uploads wait while it runs. +Schedule it accordingly. + +## Replication and durability + +`replication.enabled` is `false` by default. The node then stores content +**best effort**: one copy, on this node, pinned, with no promise that any other +node holds it. `GET /api/storage/policy` reports `"mode": "best-effort"` so +clients can see what they are getting. + +With replication enabled, a node that makes a file durable pushes it to the other +ADAMANT nodes listed in `nodes` that declare an `apiUrl`. The peer pulls the DAG +over libp2p and pins it before answering, so an acknowledgement means the copy +exists and is protected. + +- `replication.factor` — copies that must exist, including this node +- `replication.ackQuorum` — acknowledgements required for an upload to be durable +- `replication.requireQuorumOnUpload` — when `true`, an upload that cannot reach + the quorum is released and answered with `503`; when `false` (default) the + upload succeeds, reports the shortfall, and the repair job converges later + +The repair job (`replication.repairSchedule`, or `POST /api/storage/repair`) +lists confirmed files holding fewer copies than the factor requires and pushes +the missing ones again. + +Replication needs the peers to be connected over libp2p, because the copy itself +travels by bitswap. Keep every replication peer in `peerDiscovery.bootstrap` as +well as in `nodes`. + +### Why not Kubo with IPFS Cluster + +Helia has no native pin-orchestration protocol. Cross-node pinning has to come +from somewhere, so the two realistic options are: + +| | Helia with an explicit control plane (selected) | Kubo with IPFS Cluster | +| ------------------ | -------------------------------------------------------------- | ------------------------------------------ | +| Runtime | The existing Node.js process | A Go daemon plus a cluster daemon per node | +| Pin orchestration | REST calls between ADAMANT nodes, authorized by a shared token | Raft or CRDT consensus inside the cluster | +| Replication factor | Enforced by this node and by the repair job | Enforced by the cluster | +| Operational cost | None beyond the current deployment | New services, new state, new failure modes | +| Fit | Small, fixed, mutually trusted node set | Large or dynamic clusters | + +The ADAMANT node set is small, fixed, and mutually trusted, and the project +already runs a REST API on every node. An explicit control plane covers the +requirement without adding a second runtime, so **Helia-native orchestration is +the selected durability model**. Kubo with IPFS Cluster remains the fallback if +the node set grows to a size where consensus-based pin allocation is worth its +operational cost. + +## Storage report + +`GET /api/storage/metrics` reports: + +- `pinnedBytes` — content protected by a pin, estimated from the blocks this node + wrote for each registered file +- `reclaimableBytes` — blockstore bytes that no pin protects +- `availableBytes` — free space on the blockstore filesystem +- `reservedBytes` and `usableBytes` — the disk reserve and what is left for uploads +- `files` — how many files are in each lifecycle state + +Values are refreshed on the `diskUsageScanPeriod` schedule, because a directory +scan and a full registry sweep are too expensive for a request path. A subset is +also included in `GET /api/node/info`. + +## Recovery and rollback + +Read this before enabling `storage.gc.enabled` in production. + +### Before enabling collection + +1. Run `POST /api/storage/gc?dryRun=true` and review `releasedCids` and + `retainedCids`. Nothing is deleted by a dry run. +2. Confirm that every CID that must survive appears in `retainedCids`. If a CID + is missing, it is not confirmed: pin it with `POST /api/helia/pin/:cid` or + confirm it with `POST /api/file/:cid/confirm` first. +3. Confirm `repairedPins` is empty. A non-empty list means confirmed content had + lost its pin, which must be understood before deleting anything. +4. Back up the datastore directory. It holds the pinset and the lifecycle + registry, which are what protects content from deletion. + +### Rolling back + +- **Disable collection**: set `storage.gc.enabled` to `false` and restart. The + scheduled collector stops; nothing else changes. +- **Undo a release before collection runs**: a released file is unpinned but its + blocks are still on disk. `POST /api/helia/pin/:cid` restores the pin and + registers the file as confirmed again. +- **Recover content already deleted**: the blocks are gone from this node. Re-pin + the CID with `POST /api/helia/pin/:cid` while a peer that still holds it is + connected, and the DAG is pulled back over libp2p. Verify with + `GET /api/file/:cid` afterwards. If no node holds the content any more, it can + only be restored by re-uploading the original file. +- **Restore the datastore backup**: stop the node, replace the datastore + directory, and start it again. Pins and lifecycle records return to the state + of the backup; blocks deleted since then still have to be re-fetched as above. + +### Operational safeguards + +- Keep `storage.gc.enabled` false until a deletion policy is agreed +- Keep `replication.factor` at two or more, so a mistaken deletion on one node is + recoverable from another +- Run the dry run again after changing the watermarks or the TTL +- Watch `GET /api/storage/metrics` for `gc.lastRun.errors` after each run + +## Out of scope + +Content encryption remains the responsibility of the ADAMANT client protocol. +This node stores and serves whatever bytes it is given. diff --git a/src/api/file.ts b/src/api/file.ts index 6201949..a886341 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -1,56 +1,140 @@ import { Router } from 'express' -import { CID } from 'multiformats/cid' -import { multerStorage } from '../multer.js' import { config } from '../config.js' import { helia } from '../helia.js' +import { admitUpload, getUploadSession } from '../middleware/uploadGuards.js' +import { readLimiter, uploadLimiter } from '../middleware/rateLimiter.js' +import { multerStorage } from '../multer.js' +import { pinFile } from '../storage/pinning.js' +import { confirmFile, releaseFile, replicateFile } from '../storage/service.js' +import { fileRegistry } from '../storage/state.js' +import { parseCid } from '../utils/cid.js' +import { sendDownloadStream } from '../utils/downloadResponse.js' +import { downloadFile, getFileStats } from '../utils/file.js' import { logger } from '../utils/logger.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' -import { downloadFile, getFileStats } from '../utils/file.js' -import { uploadLimiter, readLimiter } from '../middleware/rateLimiter.js' -import { parseCid } from '../utils/cid.js' -import { sendDownloadStream } from '../utils/downloadResponse.js' const router = Router() -router.post('/upload', uploadLimiter, multerStorage.array('files'), async (req, res, next) => { - if (!Array.isArray(req.files) || req.files.length === 0) { - res.statusCode = 400 - return res.send({ error: 'No file uploaded' }) - } +/** Routes that make content durable or reclaim it; mounted behind the admin key. */ +export const fileAdminRouter = Router() - if (req.files.length > config.maxFileCount) { - res.status(400).send({ - error: `File limit exceeded. Max ${config.maxFileCount} allowed.` - }) - return +/** + * Accept files into the blockstore. + * + * `admitUpload` refuses the request before any block is written when the node + * is at its concurrency limit, when the aggregate request size is too large, or + * when storing the request would eat into the disk reserve. The multipart + * parser enforces the file count and the per-file size, so an over-limit part + * never reaches the blockstore either. + * + * Files become durable immediately unless `storage.confirmationRequired` is + * enabled, in which case they stay temporary until an authorized confirmation. + */ +router.post( + '/upload', + uploadLimiter, + admitUpload, + multerStorage.array('files'), + async (req, res, next) => { + if (!Array.isArray(req.files) || req.files.length === 0) { + res.statusCode = 400 + return res.send({ error: 'No file uploaded' }) + } + + const session = getUploadSession(req) + + try { + const files = flatFiles(req.files as UnixFsMulterFile[]) + logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) + + const records = [] + for (const file of files) { + const cid = file.cid.toString() + logger.info(`Successfully added file ${cid}`) + + await pinFile(helia, file.cid) + + records.push( + await fileRegistry.register( + { + cid, + name: file.originalname, + fileSize: file.size, + storedBytes: file.storedBytes + }, + { + confirmationRequired: config.storage.confirmationRequired, + temporaryTtlMs: config.storage.temporaryTtlMs + } + ) + ) + } + + // Every file is stored and pinned: the blocks written by this request must + // now survive, so ownership passes from the session to the registry. + session.commit() + + const durable = records.filter((record) => record.state === 'confirmed') + const replication = await Promise.all(durable.map((record) => replicateFile(record.cid))) + + if ( + config.replication.requireQuorumOnUpload && + replication.some((report) => !report.satisfied) + ) { + // The policy demands a quorum, so the upload is not durable. The files are + // released instead of being kept as a copy nobody promised to hold. + await Promise.all(durable.map((record) => releaseFile(record.cid))) + + return res.status(503).send({ error: 'Replication quorum not reached' }) + } + + res.send({ + filesNames: files.map((file) => file.originalname), + cids: records.map((record) => record.cid), + files: records.map((record) => ({ + cid: record.cid, + name: record.name, + state: record.state, + expiresAt: record.expiresAt + })), + replication: replication[0] ?? null + }) + } catch (err) { + // The session is still open, so the response listener reclaims every block + // this request created. + next(err) + } } +) +/** + * Lifecycle state of a file. + * + * Deliberately free of the original filename so that the endpoint stays safe to + * expose next to the public download route. + */ +router.get('/:cid/status', readLimiter, async (req, res, next) => { try { - const files = flatFiles(req.files as UnixFsMulterFile[]) - logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) - - const cids: CID[] = [] - for (const file of files) { - logger.info(`Adding ${file.originalname} to IPFS`) - - const { cid } = file - logger.info(`Successfully added file ${cid}`) - cids.push(cid) - - const isPinned = await helia.pins.isPinned(cid) - if (isPinned) { - logger.info(`File already pinned ${cid}`) - } else { - for await (const pinned of helia.pins.add(cid)) { - logger.info(`Filed pinned: ${pinned}`) - } - } + const cid = parseCid(req.params.cid) + const record = await fileRegistry.get(cid.toString()) + + if (!record) { + return res.status(404).send({ error: 'Unknown CID' }) } res.send({ - filesNames: files.map((file) => file.originalname), - cids: cids.map((cid) => cid.toString()) + cid: record.cid, + state: record.state, + pinned: record.pinned, + createdAt: record.createdAt, + expiresAt: record.expiresAt, + confirmedAt: record.confirmedAt, + replication: { + acknowledged: record.replicas.length + 1, + required: config.replication.enabled ? config.replication.ackQuorum : 1, + factor: config.replication.enabled ? config.replication.factor : 1 + } }) } catch (err) { next(err) @@ -75,4 +159,40 @@ router.get('/:cid', readLimiter, async (req, res, next) => { } }) +/** Promote a temporary upload to durable storage. */ +fileAdminRouter.post('/:cid/confirm', async (req, res, next) => { + try { + const cid = parseCid(req.params.cid) + const record = await confirmFile(cid.toString()) + + if (!record) { + return res.status(404).send({ error: 'Unknown CID' }) + } + + res.send({ cid: record.cid, state: record.state, replicas: record.replicas }) + } catch (err) { + next(err) + } +}) + +/** + * Release durable content. + * The blocks are reclaimed by the next garbage collection, not by this call. + */ +fileAdminRouter.post('/:cid/unpin', async (req, res, next) => { + try { + const cid = parseCid(req.params.cid) + const record = await releaseFile(cid.toString()) + + res.send({ + cid: cid.toString(), + state: record?.state ?? 'expired', + pinned: false, + reclaimable: true + }) + } catch (err) { + next(err) + } +}) + export default router diff --git a/src/api/helia.ts b/src/api/helia.ts index be7d424..dc40343 100644 --- a/src/api/helia.ts +++ b/src/api/helia.ts @@ -1,6 +1,7 @@ import { Router } from 'express' import { Pin } from 'helia' import { helia } from '../helia.js' +import { confirmFile } from '../storage/service.js' import { logger } from '../utils/logger.js' import { pinLimiter, readLimiter } from '../middleware/rateLimiter.js' import { parseCid } from '../utils/cid.js' @@ -22,13 +23,17 @@ router.get('/pins', readLimiter, async (req, res, next) => { } }) +/** + * Pin arbitrary content and record it as durable. + * + * The file is registered as well as pinned: an unregistered pin is invisible to + * the storage report and to the garbage collection accounting, which would make + * the reclaimable estimate wrong. + */ router.post('/pin/:cid', pinLimiter, async (req, res, next) => { try { const cid = parseCid(req.params.cid) - // `pins.add` yields the CID of every block it walks while pinning the DAG - for await (const pinnedCid of helia.pins.add(cid)) { - logger.info(`Pinned block: ${pinnedCid.toString()}`) - } + await confirmFile(cid.toString(), { registerUnknown: true }) res.send({ pinned: true, cid: cid.toString() }) } catch (err) { next(err) diff --git a/src/api/index.ts b/src/api/index.ts index 790d26a..7a1187b 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,7 +1,20 @@ -import file from './file.js' +import file, { fileAdminRouter } from './file.js' import node, { publicNodeRouter } from './node.js' import helia from './helia.js' import libp2p from './libp2p.js' import debug from './debug.js' +import replication from './replication.js' +import storage, { storageAdminRouter } from './storage.js' -export { file, node, publicNodeRouter, helia, libp2p, debug } +export { + file, + fileAdminRouter, + node, + publicNodeRouter, + helia, + libp2p, + debug, + replication, + storage, + storageAdminRouter +} diff --git a/src/api/node.ts b/src/api/node.ts index 9cb08b4..b11f5bf 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -2,6 +2,7 @@ import { Router } from 'express' import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' +import { getStorageMetrics } from '../storage/metrics.js' export const publicNodeRouter = Router() const router = Router() @@ -20,6 +21,7 @@ publicNodeRouter.get('/health', async (req, res, next) => { router.get('/info', async (req, res, next) => { try { const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() + const storage = getStorageMetrics() res.send({ version: packageJson.version, timestamp: Date.now(), @@ -28,7 +30,12 @@ router.get('/info', async (req, res, next) => { multiAddresses: helia.libp2p.getMultiaddrs(), blockstoreSizeMb, datastoreSizeMb, - availableSizeInMb + availableSizeInMb, + // Byte-accurate figures; GET /api/storage/metrics carries the full report. + pinnedBytes: storage.pinnedBytes, + reclaimableBytes: storage.reclaimableBytes, + availableBytes: storage.availableBytes, + reservedBytes: storage.reservedBytes }) } catch (err) { next(err) diff --git a/src/api/replication.ts b/src/api/replication.ts new file mode 100644 index 0000000..b5448c6 --- /dev/null +++ b/src/api/replication.ts @@ -0,0 +1,29 @@ +import { Router } from 'express' +import { pinLimiter } from '../middleware/rateLimiter.js' +import { acceptReplica } from '../storage/service.js' +import { parseCid } from '../utils/cid.js' +import { logger } from '../utils/logger.js' + +const router = Router() + +/** + * Store a copy on behalf of another ADAMANT node. + * + * The DAG is pulled over libp2p and pinned before the response is sent, so a + * `200` means the copy is durable here. Access is granted by the shared + * replication token, not by the administrative key; see `accessPolicy.ts`. + */ +router.post('/:cid', pinLimiter, async (req, res) => { + const cid = parseCid(req.params.cid) + + try { + const record = await acceptReplica(cid.toString()) + res.send({ cid: record.cid, state: record.state, storedBytes: record.storedBytes }) + } catch (err) { + // A peer that cannot serve the DAG is an expected outcome, not a node fault. + logger.warn(`Replication of ${cid.toString()} failed: ${(err as Error).message}`) + res.status(502).send({ error: 'Unable to store the replica' }) + } +}) + +export default router diff --git a/src/api/storage.ts b/src/api/storage.ts new file mode 100644 index 0000000..5332bad --- /dev/null +++ b/src/api/storage.ts @@ -0,0 +1,109 @@ +import { Router } from 'express' +import { config } from '../config.js' +import { + collectGarbage, + GarbageCollectionBusyError, + getGarbageCollectionState +} from '../gc.cron.js' +import { readLimiter } from '../middleware/rateLimiter.js' +import { + getReplicationState, + repairUnderReplicatedFiles, + ReplicationRepairBusyError +} from '../replication.cron.js' +import { getStorageMetrics } from '../storage/metrics.js' + +const router = Router() + +/** Routes that reclaim storage or move copies; mounted behind the admin key. */ +export const storageAdminRouter = Router() + +/** + * Storage report: pinned, reclaimable, available and reserved bytes, plus the + * lifecycle counters and the state of the background jobs. + * + * Values come from the disk usage schedule, so they are as fresh as + * `diskUsageScanPeriod`. + */ +router.get('/metrics', readLimiter, (req, res) => { + res.send({ + timestamp: Date.now(), + storage: getStorageMetrics(), + gc: getGarbageCollectionState(), + replication: getReplicationState() + }) +}) + +/** Limits and lifecycle rules a client needs before uploading. */ +router.get('/policy', readLimiter, (req, res) => { + res.send({ + maxFileCount: config.maxFileCount, + uploadLimitSizeBytes: config.uploadLimitSizeBytes, + maxRequestSizeBytes: config.storage.maxRequestSizeBytes, + maxConcurrentUploads: config.storage.maxConcurrentUploads, + confirmationRequired: config.storage.confirmationRequired, + temporaryTtlMs: config.storage.temporaryTtlMs, + durability: config.replication.enabled + ? { + mode: 'quorum', + factor: config.replication.factor, + ackQuorum: config.replication.ackQuorum + } + : { mode: 'best-effort', factor: 1, ackQuorum: 1 } + }) +}) + +/** + * Run garbage collection on demand. + * + * `?dryRun=true` reports the files that would be released and the files that + * would be retained without touching a single block, which is the supported way + * to review a deletion policy before enabling the scheduled collector. + * + * `?force=true` collects even when the blockstore is below the high watermark. + */ +storageAdminRouter.post('/gc', async (req, res, next) => { + try { + const report = await collectGarbage({ + dryRun: req.query.dryRun === 'true', + force: req.query.force === 'true' + }) + + // The response is built field by field rather than forwarded, so the + // payload stays a deliberate contract and carries nothing derived from the + // request beyond the two flags above. + res.send({ + startedAt: report.startedAt, + durationMs: report.durationMs, + dryRun: report.dryRun, + collected: report.collected, + blockstoreBytesBefore: report.blockstoreBytesBefore, + estimatedBytesAfter: report.estimatedBytesAfter, + releasedCids: report.releasedCids, + retainedCids: report.retainedCids, + removedBlocks: report.removedBlocks, + removedCids: report.removedCids, + repairedPins: report.repairedPins, + errors: report.errors + }) + } catch (err) { + if (err instanceof GarbageCollectionBusyError) { + return res.status(409).send({ error: 'Garbage collection is already running' }) + } + next(err) + } +}) + +/** Detect and repair under-replicated durable content on demand. */ +storageAdminRouter.post('/repair', async (req, res, next) => { + try { + res.send(await repairUnderReplicatedFiles()) + } catch (err) { + if (err instanceof ReplicationRepairBusyError) { + return res.status(409).send({ error: 'Replication repair is already running' }) + } + next(err) + } +}) + +export default router diff --git a/src/config.ts b/src/config.ts index 0c3f864..587030c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,6 +5,12 @@ import JSON5 from 'json5' import { validateSecurityConfig } from './security/config.js' import type { RateLimitPolicy } from './security/rateLimit.js' import type { TrustProxySetting } from './security/trustProxy.js' +import { + resolveReplicationConfig, + resolveStorageConfig, + type ReplicationConfig, + type StorageConfig +} from './storage/config.js' /** * Locate the repository root by walking up to the nearest `package.json`. @@ -40,6 +46,11 @@ export type LogLevel = (typeof LOG_LEVELS)[number] export interface ConfigNode { name: string multiAddr: string + /** + * Base URL of the node REST API, for example `https://ipfs1.adamant.im`. + * Only nodes that take part in replication need one. + */ + apiUrl?: string } export interface Config { @@ -73,6 +84,10 @@ export interface Config { /** Administrative API key. An empty value makes administrative routes fail closed. */ adminApiKey: string enableDebugApi: boolean + /** Bounded storage lifecycle; see `src/storage/config.ts`. */ + storage: StorageConfig + /** Cross-node durability policy; see `src/storage/config.ts`. */ + replication: ReplicationConfig } /** @@ -123,6 +138,28 @@ function requireStringArray(value: unknown, path: string): string[] { return value.map((item, index) => requireString(item, `${path}[${index}]`)) } +/** + * Validate an HTTP(S) base URL used to reach another node's REST API. + * + * Only the scheme and syntax are checked; reachability is a runtime concern. + */ +function requireHttpUrl(value: unknown, path: string): string { + const raw = requireString(value, path) + + let url: URL + try { + url = new URL(raw) + } catch { + fail(path, 'must be an absolute http(s) URL') + } + + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + fail(path, 'must use the http or https scheme') + } + + return raw +} + /** * Validate a positive integer. * @@ -158,7 +195,11 @@ export function validateConfig(raw: unknown): Config { const entry = requireObject(node, `nodes[${index}]`) return { name: requireString(entry.name, `nodes[${index}].name`), - multiAddr: requireString(entry.multiAddr, `nodes[${index}].multiAddr`) + multiAddr: requireString(entry.multiAddr, `nodes[${index}].multiAddr`), + apiUrl: + entry.apiUrl === undefined + ? undefined + : requireHttpUrl(entry.apiUrl, `nodes[${index}].apiUrl`) } }) @@ -185,6 +226,12 @@ export function validateConfig(raw: unknown): Config { const cors = requireObject(root.cors, 'cors') + // Owns the storage lifecycle and replication policy. Both sections are + // optional: every option falls back to a documented default so that config + // files written before this feature keep working. + const storage = resolveStorageConfig(root.storage, root.uploadLimitSizeBytes as number) + const replication = resolveReplicationConfig(root.replication) + return { nodes, storeFolder, @@ -199,7 +246,9 @@ export function validateConfig(raw: unknown): Config { trustProxy: (root.trustProxy ?? false) as TrustProxySetting, rateLimits: root.rateLimits as Config['rateLimits'], adminApiKey: (root.adminApiKey ?? '') as string, - enableDebugApi: root.enableDebugApi === true + enableDebugApi: root.enableDebugApi === true, + storage, + replication } } diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index f052232..ce026dd 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -1,15 +1,10 @@ -import { blockstorePath, datastorePath } from './store.js' -import { dirSize, availableStorageSize } from './utils/utils.js' import { CronJob } from 'cron' import { config } from './config.js' +import { getStorageMetrics, refreshStorageMetrics } from './storage/metrics.js' import { logger } from './utils/logger.js' const oneMb = 1048576 -let blockstoreSizeMb = 0 -let datastoreSizeMb = 0 -let availableSizeInMb = 0 - let started = false export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { if (!started) { @@ -24,26 +19,24 @@ async function scan() { logger.info('[Cron] Running "diskUsage" cronjob.') const start = Date.now() - const blockstoreSize = await dirSize(blockstorePath) - if (blockstoreSize > 0) { - blockstoreSizeMb = blockstoreSize / oneMb - } + // The same scan feeds the byte-accurate storage report, so both stay in step. + await refreshStorageMetrics() - const datastoreSize = await dirSize(datastorePath) - if (datastoreSize > 0) { - datastoreSizeMb = datastoreSize / oneMb - } - - availableSizeInMb = Number((await availableStorageSize(blockstorePath)) / BigInt(oneMb)) logger.info(`Check folder size took ${Date.now() - start} ms.`) } scan().catch((err) => logger.error(`${err.message}\n${err.stack}`)) +/** + * Disk usage in megabytes, kept for the existing `/api/node/info` payload. + * Byte-accurate values and lifecycle counters live in the storage metrics. + */ export function getDiskUsageStats() { + const metrics = getStorageMetrics() + return { - blockstoreSizeMb, - datastoreSizeMb, - availableSizeInMb + blockstoreSizeMb: metrics.blockstoreBytes / oneMb, + datastoreSizeMb: metrics.datastoreBytes / oneMb, + availableSizeInMb: metrics.availableBytes / oneMb } } diff --git a/src/gc.cron.ts b/src/gc.cron.ts new file mode 100644 index 0000000..6bedeca --- /dev/null +++ b/src/gc.cron.ts @@ -0,0 +1,89 @@ +import { CronJob } from 'cron' +import { config } from './config.js' +import { helia } from './helia.js' +import { runGarbageCollection, type GcReport } from './storage/gc.js' +import { refreshStorageMetrics } from './storage/metrics.js' +import { fileRegistry } from './storage/state.js' +import { logger } from './utils/logger.js' + +let running = false +let lastReport: GcReport | null = null + +export class GarbageCollectionBusyError extends Error { + constructor() { + super('Garbage collection is already running') + this.name = 'GarbageCollectionBusyError' + } +} + +/** + * Run one collection pass. + * + * Metrics are refreshed first so the watermark decision uses the current + * blockstore size rather than the value cached by the disk usage schedule. + * + * @param options.dryRun report the plan without unpinning or deleting anything + * @param options.force collect even when the blockstore is below the high watermark + */ +export async function collectGarbage( + options: { dryRun?: boolean; force?: boolean } = {} +): Promise { + if (running) { + throw new GarbageCollectionBusyError() + } + + running = true + try { + const metrics = await refreshStorageMetrics() + + const report = await runGarbageCollection({ + node: helia, + registry: fileRegistry, + watermarks: config.storage.gc, + blockstoreBytes: metrics.blockstoreBytes, + dryRun: options.dryRun, + force: options.force, + pinTimeoutMs: config.replication.requestTimeoutMs, + log: (message) => logger.info(message) + }) + + if (!report.dryRun) { + lastReport = report + await refreshStorageMetrics() + } + + return report + } finally { + running = false + } +} + +export const garbageCollectionCron = new CronJob(config.storage.gc.schedule, () => { + if (running) { + return + } + + logger.info('[Cron] Running "garbageCollection" cronjob.') + collectGarbage().catch((err) => logger.error(`${err.message}\n${err.stack}`)) +}) + +export function getGarbageCollectionState() { + return { + enabled: config.storage.gc.enabled, + schedule: config.storage.gc.schedule, + highWatermarkBytes: config.storage.gc.highWatermarkBytes, + lowWatermarkBytes: config.storage.gc.lowWatermarkBytes, + running, + lastRun: lastReport + ? { + startedAt: lastReport.startedAt, + durationMs: lastReport.durationMs, + collected: lastReport.collected, + releasedFiles: lastReport.releasedCids.length, + removedBlocks: lastReport.removedBlocks, + repairedPins: lastReport.repairedPins.length, + errors: lastReport.errors.length + } + : null + } +} diff --git a/src/index.ts b/src/index.ts index 75a5280..65228aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,14 @@ import express from 'express' import { httpLogger, logger } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' +import { garbageCollectionCron } from './gc.cron.js' +import { replicationRepairCron } from './replication.cron.js' import cors from 'cors' import * as routers from './api/index.js' import { errorHandler, notFoundHandler } from './middleware/errorHandler.js' import { mountApiRoutes } from './security/accessPolicy.js' import { createApiKeyAuth } from './security/apiKey.js' +import { createReplicationAuth } from './security/replicationToken.js' import { createCorsOriginDelegate } from './security/cors.js' import { parseTrustProxy } from './security/trustProxy.js' @@ -14,6 +17,27 @@ logger.info(`Using config file: ${CONFIG_FILE_NAME}`) diskUsageCron.start() +// Deletion is opt-in: the collector only runs once an operator has agreed to a +// deletion policy and enabled it. Until then the authorized +// POST /api/storage/gc endpoint stays the only way to reclaim space. +if (config.storage.gc.enabled) { + logger.info( + `Garbage collection enabled: high watermark ${config.storage.gc.highWatermarkBytes} bytes, ` + + `low watermark ${config.storage.gc.lowWatermarkBytes} bytes` + ) + garbageCollectionCron.start() +} else { + logger.info('Garbage collection is disabled. Storage grows until an operator collects it.') +} + +if (config.replication.enabled) { + if (config.replication.repairEnabled) { + replicationRepairCron.start() + } +} else { + logger.info('Replication is disabled. Uploaded content is stored best effort on this node.') +} + const PORT = config.serverPort const app = express() app.disable('x-powered-by') @@ -43,7 +67,15 @@ app.get('/', (req, res) => { res.send('IPFS node') }) -mountApiRoutes(app, routers, createApiKeyAuth(config.adminApiKey), config.enableDebugApi === true) +mountApiRoutes( + app, + routers, + createApiKeyAuth(config.adminApiKey), + config.enableDebugApi === true, + // The token alone opens replication intake. `replication.enabled` governs + // whether this node pushes copies out, which a receive-only node never does. + createReplicationAuth(config.replication.token) +) app.use(notFoundHandler) app.use(errorHandler) diff --git a/src/middleware/uploadGuards.ts b/src/middleware/uploadGuards.ts new file mode 100644 index 0000000..a8f0f10 --- /dev/null +++ b/src/middleware/uploadGuards.ts @@ -0,0 +1,123 @@ +import type { NextFunction, Request, RequestHandler, Response } from 'express' +import { config } from '../config.js' +import { helia } from '../helia.js' +import { blockstore, blockstorePath } from '../store.js' +import { checkDiskReserve, checkRequestSize, parseContentLength } from '../storage/limits.js' +import { uploadLimiter } from '../storage/state.js' +import { UploadSession } from '../storage/uploadSession.js' +import { parseCid } from '../utils/cid.js' +import { logger } from '../utils/logger.js' +import { availableStorageSize } from '../utils/utils.js' + +const SESSION_KEY = Symbol('uploadSession') + +type RequestWithSession = Request & { [SESSION_KEY]?: UploadSession } + +/** + * Session of the current upload request. + * + * Throws when called outside an admitted upload, which would otherwise let + * blocks be written with nobody owning their cleanup. + */ +export function getUploadSession(req: Request): UploadSession { + const session = (req as RequestWithSession)[SESSION_KEY] + + if (!session) { + throw new Error('Upload session is missing. The upload guards did not run for this request.') + } + + return session +} + +/** + * Admission control for uploads. + * + * Runs before the multipart parser so that a request which cannot be stored is + * refused before any block reaches the blockstore. It enforces the concurrent + * upload limit, the aggregate request size and the disk reserve, then attaches + * the session that owns cleanup for whatever the request writes. + * + * The per-file size and the file count are enforced by the parser itself; see + * `createMultipartLimits`. + */ +export const admitUpload: RequestHandler = ( + req: Request, + res: Response, + next: NextFunction +): void => { + const { storage } = config + + if (!uploadLimiter.tryAcquire()) { + res.set('Retry-After', '5') + res.status(429).send({ error: 'Too many concurrent uploads. Please try again later.' }) + return + } + + let released = false + const release = (): void => { + if (released) { + return + } + released = true + uploadLimiter.release() + } + + const declaredBytes = parseContentLength(req.headers['content-length']) + + if (!checkRequestSize(declaredBytes, storage.maxRequestSizeBytes).allowed) { + release() + res.status(413).send({ error: 'Upload size limit exceeded' }) + return + } + + availableStorageSize(blockstorePath) + .then((available) => { + const reserveCheck = checkDiskReserve({ + availableBytes: Number(available), + reserveBytes: storage.diskReserveBytes, + requestedBytes: declaredBytes + }) + + if (!reserveCheck.allowed) { + logger.warn(`Upload rejected: ${reserveCheck.reason}`) + release() + res.status(507).send({ error: 'Insufficient storage' }) + return + } + + const session = new UploadSession({ + blockstore: helia.blockstore, + isPinned: (cid) => helia.pins.isPinned(cid), + deleteBlock: (cid) => blockstore.delete(cid), + maxRequestSizeBytes: storage.maxRequestSizeBytes, + parseCid, + onCleanupError: (err) => logger.error(`Upload cleanup error: ${err.message}`) + }) + ;(req as RequestWithSession)[SESSION_KEY] = session + + // Covers every way a request can end without a durable result: a parser + // abort, a route failure, or a client that disconnected mid-upload. + res.on('close', () => { + release() + + if (session.isSettled) { + return + } + + session + .cleanup() + .then((removed) => { + if (removed > 0) { + logger.info(`Removed ${removed} blocks left by an unfinished upload`) + } + }) + .catch((err) => logger.error(`Upload cleanup failed: ${(err as Error).message}`)) + }) + + next() + }) + .catch((err) => { + release() + next(err) + }) +} diff --git a/src/multer.ts b/src/multer.ts index 37a59cd..fdcb006 100644 --- a/src/multer.ts +++ b/src/multer.ts @@ -1,10 +1,10 @@ import multer from 'multer' -import { ifs } from './helia.js' import { config } from './config.js' +import { getUploadSession } from './middleware/uploadGuards.js' import { UnixfsMulterStorage } from './utils/unixfs-multer.storage.js' import { createMultipartLimits } from './security/uploadLimits.js' export const multerStorage = multer({ - storage: new UnixfsMulterStorage({ unixfs: ifs }), + storage: new UnixfsMulterStorage({ getSession: getUploadSession }), limits: createMultipartLimits(config.uploadLimitSizeBytes, config.maxFileCount) }) diff --git a/src/replication.cron.ts b/src/replication.cron.ts new file mode 100644 index 0000000..d37d296 --- /dev/null +++ b/src/replication.cron.ts @@ -0,0 +1,62 @@ +import { CronJob } from 'cron' +import { config } from './config.js' +import { repairReplication, type RepairReport } from './storage/service.js' +import { logger } from './utils/logger.js' + +let running = false +let lastReport: RepairReport | null = null + +export class ReplicationRepairBusyError extends Error { + constructor() { + super('Replication repair is already running') + this.name = 'ReplicationRepairBusyError' + } +} + +/** + * Detect under-replicated durable content and push the missing copies. + * Runs one pass at a time; a pass that is still working skips the next tick. + */ +export async function repairUnderReplicatedFiles(): Promise { + if (running) { + throw new ReplicationRepairBusyError() + } + + running = true + try { + lastReport = await repairReplication() + + if (lastReport.underReplicated > 0) { + logger.info( + `Replication repair: ${lastReport.repaired.length} of ${lastReport.underReplicated} ` + + 'under-replicated files restored' + ) + } + + return lastReport + } finally { + running = false + } +} + +export const replicationRepairCron = new CronJob(config.replication.repairSchedule, () => { + if (running) { + return + } + + logger.info('[Cron] Running "replicationRepair" cronjob.') + repairUnderReplicatedFiles().catch((err) => logger.error(`${err.message}\n${err.stack}`)) +}) + +export function getReplicationState() { + return { + enabled: config.replication.enabled, + factor: config.replication.factor, + ackQuorum: config.replication.ackQuorum, + requireQuorumOnUpload: config.replication.requireQuorumOnUpload, + repairEnabled: config.replication.repairEnabled, + repairSchedule: config.replication.repairSchedule, + running, + lastRun: lastReport + } +} diff --git a/src/security/accessPolicy.ts b/src/security/accessPolicy.ts index e7fcc2c..a167314 100644 --- a/src/security/accessPolicy.ts +++ b/src/security/accessPolicy.ts @@ -2,11 +2,19 @@ import type { Application, RequestHandler, Router } from 'express' export type ApiRouters = { file: Router + /** File routes that make content durable or reclaim it. */ + fileAdminRouter: Router publicNodeRouter: Router node: Router helia: Router libp2p: Router debug: Router + /** Storage report and policy, safe to expose next to the upload routes. */ + storage: Router + /** Storage routes that reclaim space or move copies between nodes. */ + storageAdminRouter: Router + /** Peer-to-peer replication intake. */ + replication: Router } /** @@ -17,18 +25,28 @@ export type ApiRouters = { * @param routers API routers without cross-cutting access middleware * @param adminAuth fail-closed administrative authentication middleware * @param enableDebugApi whether the authenticated debug API is mounted + * @param replicationAuth fail-closed peer authentication for replication intake */ export function mountApiRoutes( app: Application, routers: ApiRouters, adminAuth: RequestHandler, - enableDebugApi: boolean + enableDebugApi: boolean, + replicationAuth: RequestHandler ): void { app.use('/api/file', routers.file) + app.use('/api/file', adminAuth, routers.fileAdminRouter) app.use('/api/node', routers.publicNodeRouter) app.use('/api/node', adminAuth, routers.node) + app.use('/api/storage', routers.storage) + app.use('/api/storage', adminAuth, routers.storageAdminRouter) + + // Peers authenticate with the shared replication token rather than the + // administrative key: storing a copy is all a peer may ask this node to do. + app.use('/api/replication', replicationAuth, routers.replication) + app.use('/api/helia', adminAuth, routers.helia) app.use('/api/libp2p', adminAuth, routers.libp2p) diff --git a/src/security/errors.ts b/src/security/errors.ts index ab011de..a7a1121 100644 --- a/src/security/errors.ts +++ b/src/security/errors.ts @@ -1,4 +1,5 @@ import multer from 'multer' +import { RequestSizeLimitError } from '../storage/limits.js' import { FileNotFoundError } from '../utils/fileErrors.js' export type PublicError = { @@ -34,6 +35,12 @@ export function getPublicError(error: unknown): PublicError { return { status: 408, body: { error: 'File request timed out' } } } + // Raised while streaming when the parts of one request exceed the aggregate + // limit. The configured limit is not echoed back to the client. + if (error instanceof RequestSizeLimitError) { + return { status: 413, body: { error: 'Upload size limit exceeded' } } + } + if (error instanceof multer.MulterError) { return { status: 400, body: { error: multerErrorMessage(error.code) } } } diff --git a/src/security/replicationToken.ts b/src/security/replicationToken.ts new file mode 100644 index 0000000..7b1d3a7 --- /dev/null +++ b/src/security/replicationToken.ts @@ -0,0 +1,43 @@ +import { createHmac, timingSafeEqual } from 'node:crypto' +import type { RequestHandler } from 'express' + +/** + * Create fail-closed middleware for peer replication requests. + * + * The token is shared by the ADAMANT nodes and authorizes exactly one + * operation: storing a copy of a CID. It is deliberately separate from + * `adminApiKey`, which would grant a peer far more than replication needs. + * + * @param configuredToken operator secret, or an empty value to refuse replication + * @returns Express middleware that accepts only a matching `x-replication-token` header + */ +export function createReplicationAuth(configuredToken: unknown): RequestHandler { + if (typeof configuredToken !== 'string' || configuredToken.length === 0) { + return (req, res): void => { + res.status(503).send({ error: 'Service not configured' }) + } + } + + const keyBytes = new TextEncoder().encode(configuredToken) + const configuredDigest = digest(configuredToken, keyBytes) + + return (req, res, next): void => { + const provided = req.headers['x-replication-token'] + if (typeof provided !== 'string') { + res.status(401).send({ error: 'Unauthorized' }) + return + } + + if (!timingSafeEqual(digest(provided, keyBytes), configuredDigest)) { + res.status(401).send({ error: 'Unauthorized' }) + return + } + + res.set('Cache-Control', 'no-store') + next() + } +} + +function digest(value: string, key: Uint8Array): Uint8Array { + return new Uint8Array(createHmac('sha256', key).update(value, 'utf8').digest()) +} diff --git a/src/storage/config.ts b/src/storage/config.ts new file mode 100644 index 0000000..ad4c2c7 --- /dev/null +++ b/src/storage/config.ts @@ -0,0 +1,262 @@ +const MiB = 1024 * 1024 +const GiB = 1024 * MiB + +/** + * Garbage collection thresholds, expressed in blockstore bytes. + * + * Collection starts once the blockstore grows above `highWatermarkBytes` and + * keeps reclaiming until it drops below `lowWatermarkBytes`. The gap between + * the two stops the collector from running on every tick near one threshold. + */ +export interface GarbageCollectionConfig { + /** Whether the scheduled collector runs. The admin endpoint works regardless. */ + enabled: boolean + /** Collection schedule in cron format. */ + schedule: string + highWatermarkBytes: number + lowWatermarkBytes: number +} + +export interface StorageConfig { + /** Maximum combined size of all files accepted in a single upload request. */ + maxRequestSizeBytes: number + /** Maximum number of upload requests writing into the blockstore at once. */ + maxConcurrentUploads: number + /** Free space on the blockstore filesystem that uploads must never consume. */ + diskReserveBytes: number + /** When true, an upload stays temporary until an authorized confirmation. */ + confirmationRequired: boolean + /** Lifetime of an unconfirmed upload before it becomes reclaimable. */ + temporaryTtlMs: number + gc: GarbageCollectionConfig +} + +export interface ReplicationConfig { + /** When false the node stores content best effort and never contacts peers. */ + enabled: boolean + /** Number of ADAMANT nodes that must hold a copy, including this node. */ + factor: number + /** Acknowledgements required before an upload is reported as durable. */ + ackQuorum: number + /** When true an upload fails unless the acknowledgement quorum is reached. */ + requireQuorumOnUpload: boolean + /** Timeout of a single replication request to a peer node. */ + requestTimeoutMs: number + repairEnabled: boolean + repairSchedule: string + /** + * Secret shared by the ADAMANT nodes, sent as `x-replication-token`. + * + * It is deliberately separate from `adminApiKey`: a peer only needs to ask + * this node to store a copy, so distributing the administrative key across + * the node set would grant far more than replication requires. + * + * Setting it opens the replication intake route, independently of `enabled`, + * so a node can accept copies without pushing any of its own. + */ + token: string +} + +export const DEFAULT_STORAGE_CONFIG: StorageConfig = { + maxRequestSizeBytes: 512 * MiB, + maxConcurrentUploads: 4, + diskReserveBytes: 5 * GiB, + confirmationRequired: false, + temporaryTtlMs: 24 * 60 * 60 * 1000, + gc: { + enabled: false, + schedule: '0 */15 * * * *', + highWatermarkBytes: 50 * GiB, + lowWatermarkBytes: 40 * GiB + } +} + +export const DEFAULT_REPLICATION_CONFIG: ReplicationConfig = { + enabled: false, + factor: 2, + ackQuorum: 1, + requireQuorumOnUpload: false, + requestTimeoutMs: 30000, + repairEnabled: true, + repairSchedule: '0 */30 * * * *', + token: '' +} + +function fail(path: string, expectation: string): never { + throw new Error(`Invalid config: "${path}" ${expectation}`) +} + +function section(value: unknown, path: string): Record { + if (value === undefined) { + return {} + } + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + fail(path, 'must be an object') + } + return value as Record +} + +function optionalBoolean(value: unknown, path: string, fallback: boolean): boolean { + if (value === undefined) { + return fallback + } + if (typeof value !== 'boolean') { + fail(path, 'must be a boolean') + } + return value +} + +function optionalString(value: unknown, path: string, fallback: string): string { + if (value === undefined) { + return fallback + } + if (typeof value !== 'string') { + fail(path, 'must be a string') + } + return value +} + +/** + * Validate an optional integer option. + * + * @param min Smallest accepted value, used to reject limits that would disable a guarantee + */ +function optionalInteger(value: unknown, path: string, fallback: number, min: number): number { + if (value === undefined) { + return fallback + } + if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < min) { + fail(path, `must be an integer >= ${min}`) + } + return value +} + +/** + * Resolve the `storage` section, filling in defaults for every option the + * operator did not set so that an existing config file keeps working. + * + * @param raw Value of the `storage` key from a parsed config file + * @param uploadLimitSizeBytes Per-file limit, which the aggregate limit must cover + */ +export function resolveStorageConfig(raw: unknown, uploadLimitSizeBytes: number): StorageConfig { + const input = section(raw, 'storage') + const gcInput = section(input.gc, 'storage.gc') + const defaults = DEFAULT_STORAGE_CONFIG + + const gc: GarbageCollectionConfig = { + enabled: optionalBoolean(gcInput.enabled, 'storage.gc.enabled', defaults.gc.enabled), + schedule: optionalString(gcInput.schedule, 'storage.gc.schedule', defaults.gc.schedule), + highWatermarkBytes: optionalInteger( + gcInput.highWatermarkBytes, + 'storage.gc.highWatermarkBytes', + defaults.gc.highWatermarkBytes, + 1 + ), + lowWatermarkBytes: optionalInteger( + gcInput.lowWatermarkBytes, + 'storage.gc.lowWatermarkBytes', + defaults.gc.lowWatermarkBytes, + 1 + ) + } + + if (gc.lowWatermarkBytes >= gc.highWatermarkBytes) { + fail('storage.gc.lowWatermarkBytes', 'must be lower than storage.gc.highWatermarkBytes') + } + + const storage: StorageConfig = { + maxRequestSizeBytes: optionalInteger( + input.maxRequestSizeBytes, + 'storage.maxRequestSizeBytes', + defaults.maxRequestSizeBytes, + 1 + ), + maxConcurrentUploads: optionalInteger( + input.maxConcurrentUploads, + 'storage.maxConcurrentUploads', + defaults.maxConcurrentUploads, + 1 + ), + diskReserveBytes: optionalInteger( + input.diskReserveBytes, + 'storage.diskReserveBytes', + defaults.diskReserveBytes, + 0 + ), + confirmationRequired: optionalBoolean( + input.confirmationRequired, + 'storage.confirmationRequired', + defaults.confirmationRequired + ), + temporaryTtlMs: optionalInteger( + input.temporaryTtlMs, + 'storage.temporaryTtlMs', + defaults.temporaryTtlMs, + 1 + ), + gc + } + + if (storage.maxRequestSizeBytes < uploadLimitSizeBytes) { + fail( + 'storage.maxRequestSizeBytes', + 'must be greater than or equal to uploadLimitSizeBytes, otherwise no single file can be uploaded' + ) + } + + return storage +} + +/** + * Resolve the `replication` section. + * + * @param raw Value of the `replication` key from a parsed config file + */ +export function resolveReplicationConfig(raw: unknown): ReplicationConfig { + const input = section(raw, 'replication') + const defaults = DEFAULT_REPLICATION_CONFIG + + const replication: ReplicationConfig = { + enabled: optionalBoolean(input.enabled, 'replication.enabled', defaults.enabled), + factor: optionalInteger(input.factor, 'replication.factor', defaults.factor, 1), + ackQuorum: optionalInteger(input.ackQuorum, 'replication.ackQuorum', defaults.ackQuorum, 1), + requireQuorumOnUpload: optionalBoolean( + input.requireQuorumOnUpload, + 'replication.requireQuorumOnUpload', + defaults.requireQuorumOnUpload + ), + requestTimeoutMs: optionalInteger( + input.requestTimeoutMs, + 'replication.requestTimeoutMs', + defaults.requestTimeoutMs, + 1 + ), + repairEnabled: optionalBoolean( + input.repairEnabled, + 'replication.repairEnabled', + defaults.repairEnabled + ), + repairSchedule: optionalString( + input.repairSchedule, + 'replication.repairSchedule', + defaults.repairSchedule + ), + token: optionalString(input.token, 'replication.token', defaults.token) + } + + if (replication.ackQuorum > replication.factor) { + fail('replication.ackQuorum', 'must not exceed replication.factor') + } + + if (replication.enabled && replication.token === '') { + fail('replication.token', 'must be set when replication is enabled') + } + + // A node that only accepts copies leaves `enabled` false but still needs the + // token, so the strength requirement applies whenever one is configured. + if (replication.token !== '' && replication.token.length < 32) { + fail('replication.token', 'must be a secret of at least 32 characters') + } + + return replication +} diff --git a/src/storage/gc.ts b/src/storage/gc.ts new file mode 100644 index 0000000..d18a571 --- /dev/null +++ b/src/storage/gc.ts @@ -0,0 +1,238 @@ +import { CID } from 'multiformats/cid' +import type { IpfsNode } from '../ipfs-node.js' +import type { GarbageCollectionConfig } from './config.js' +import { isProtected, pinFile, unpinFile } from './pinning.js' +import { FileRegistry, isExpired, type FileRecord } from './registry.js' + +export type Watermarks = Pick + +export interface GcPlanInput { + /** Current blockstore size in bytes. */ + blockstoreBytes: number + watermarks: Watermarks + /** Every registered file; confirmed files are filtered out by the planner. */ + records: FileRecord[] + now?: number +} + +export interface GcPlan { + /** True once the blockstore grew past the high watermark. */ + shouldCollect: boolean + /** Files released by the TTL policy, independent of the watermarks. */ + expired: FileRecord[] + /** Additional files evicted to bring the blockstore below the low watermark. */ + evicted: FileRecord[] + /** Files that must survive the collection. */ + retained: FileRecord[] + estimatedBytesAfter: number +} + +/** + * Decide what a collection run should release. + * + * Two independent rules are applied: + * + * 1. TTL: an abandoned upload is released once it outlives its expiry, whatever + * the blockstore size is. + * 2. Watermarks: when the blockstore grows above the high watermark, the oldest + * unconfirmed files are evicted until the estimate drops below the low + * watermark. Hysteresis between the two thresholds stops the collector from + * running on every tick. + * + * Confirmed files are never selected, so durable content cannot be reclaimed. + */ +export function planGarbageCollection(input: GcPlanInput): GcPlan { + const now = input.now ?? Date.now() + const { highWatermarkBytes, lowWatermarkBytes } = input.watermarks + + const confirmed = input.records.filter((record) => record.state === 'confirmed') + const releasable = input.records.filter((record) => record.state !== 'confirmed') + + const expired = releasable.filter((record) => isExpired(record, now)) + const alive = releasable.filter((record) => !isExpired(record, now)) + + let estimatedBytesAfter = input.blockstoreBytes + for (const record of expired) { + estimatedBytesAfter -= record.storedBytes + } + + const shouldCollect = input.blockstoreBytes > highWatermarkBytes + const evicted: FileRecord[] = [] + + if (shouldCollect) { + // Oldest first: the least recently accepted upload is evicted before newer + // content that a client may still be waiting to confirm. + const byAge = [...alive].sort((a, b) => a.createdAt - b.createdAt) + + for (const record of byAge) { + if (estimatedBytesAfter <= lowWatermarkBytes) { + break + } + + evicted.push(record) + estimatedBytesAfter -= record.storedBytes + } + } + + const releasedCids = new Set([...expired, ...evicted].map((record) => record.cid)) + + return { + shouldCollect, + expired, + evicted, + retained: [...confirmed, ...alive.filter((record) => !releasedCids.has(record.cid))], + estimatedBytesAfter: Math.max(0, estimatedBytesAfter) + } +} + +export interface GcReport { + startedAt: number + durationMs: number + dryRun: boolean + collected: boolean + blockstoreBytesBefore: number + estimatedBytesAfter: number + releasedCids: string[] + retainedCids: string[] + removedBlocks: number + /** + * Blocks deleted by this run, capped for readability. + * + * These identify blocks, not files: the blockstore addresses content by + * multihash and rebuilds a CID with its own default codec, so an entry can + * read differently from the CID a file was uploaded under. + */ + removedCids: string[] + /** Confirmed files whose pin was missing and had to be restored. */ + repairedPins: string[] + errors: string[] +} + +/** Number of removed CIDs kept in a report, to bound the response size. */ +const MAX_REPORTED_CIDS = 1000 + +export interface GcRunOptions { + node: IpfsNode + registry: FileRegistry + watermarks: Watermarks + blockstoreBytes: number + /** Report what would happen without unpinning or deleting anything. */ + dryRun?: boolean + /** Collect even when the blockstore is below the high watermark. */ + force?: boolean + /** Bounds the DAG walk when a missing pin has to be restored. */ + pinTimeoutMs?: number + now?: number + log?: (message: string) => void +} + +/** + * Release reclaimable files and delete every unpinned block. + * + * Helia collects the whole blockstore in one pass, so the amount reclaimed is + * controlled by choosing which files lose their pin beforehand. Confirmed files + * keep their pin, and a missing pin on confirmed content is restored before any + * block is deleted. + */ +export async function runGarbageCollection(options: GcRunOptions): Promise { + const startedAt = options.now ?? Date.now() + const log = options.log ?? ((): void => {}) + const errors: string[] = [] + + const records = await options.registry.all() + + const plan = planGarbageCollection({ + blockstoreBytes: options.blockstoreBytes, + watermarks: options.watermarks, + records, + now: startedAt + }) + + const released = [...plan.expired, ...plan.evicted] + const collect = options.force === true || plan.shouldCollect || released.length > 0 + + const report: GcReport = { + startedAt, + durationMs: 0, + dryRun: options.dryRun === true, + collected: false, + blockstoreBytesBefore: options.blockstoreBytes, + estimatedBytesAfter: plan.estimatedBytesAfter, + releasedCids: released.map((record) => record.cid), + retainedCids: plan.retained.map((record) => record.cid), + removedBlocks: 0, + removedCids: [], + repairedPins: [], + errors + } + + // Guard rail: durable content must be pinned before anything is deleted. + for (const record of plan.retained.filter((item) => item.state === 'confirmed')) { + try { + const cid = CID.parse(record.cid) + if (await isProtected(options.node, cid)) { + continue + } + + report.repairedPins.push(record.cid) + log(`Restoring the missing pin of the confirmed file ${record.cid}`) + + if (options.dryRun !== true) { + const signal = + options.pinTimeoutMs === undefined ? undefined : AbortSignal.timeout(options.pinTimeoutMs) + + await pinFile(options.node, cid, signal) + await options.registry.setPinned(record.cid, true) + } + } catch (err) { + errors.push(`Pin check failed for ${record.cid}: ${(err as Error).message}`) + } + } + + if (options.dryRun === true || !collect) { + report.durationMs = Date.now() - startedAt + return report + } + + for (const record of released) { + try { + await unpinFile(options.node, CID.parse(record.cid)) + await options.registry.release(record.cid) + } catch (err) { + errors.push(`Release failed for ${record.cid}: ${(err as Error).message}`) + } + } + + await options.node.gc({ + onProgress: (event) => { + if (event.type === 'helia:gc:deleted') { + report.removedBlocks += 1 + if (report.removedCids.length < MAX_REPORTED_CIDS) { + report.removedCids.push(event.detail.toString()) + } + } + + if (event.type === 'helia:gc:error') { + errors.push(String(event.detail)) + } + } + }) + + // The registry entry is only dropped once its blocks are gone, so a failed + // collection leaves the file visible and reclaimable on the next run. + for (const record of released) { + try { + await options.registry.remove(record.cid) + } catch (err) { + errors.push(`Registry cleanup failed for ${record.cid}: ${(err as Error).message}`) + } + } + + report.collected = true + report.durationMs = Date.now() - startedAt + log( + `Garbage collection released ${released.length} files and removed ${report.removedBlocks} blocks` + ) + + return report +} diff --git a/src/storage/limits.ts b/src/storage/limits.ts new file mode 100644 index 0000000..f1d074a --- /dev/null +++ b/src/storage/limits.ts @@ -0,0 +1,135 @@ +/** + * Pure upload admission helpers. + * + * They are deliberately free of Express, Helia and configuration imports so + * that every boundary they guard can be exercised in isolation. + */ + +/** Counts uploads that are currently writing into the blockstore. */ +export class ConcurrencyLimiter { + private inFlight = 0 + + constructor(private readonly maxConcurrent: number) {} + + get active(): number { + return this.inFlight + } + + get limit(): number { + return this.maxConcurrent + } + + /** Take a slot, or return false when the node is already at capacity. */ + tryAcquire(): boolean { + if (this.inFlight >= this.maxConcurrent) { + return false + } + + this.inFlight += 1 + return true + } + + release(): void { + if (this.inFlight > 0) { + this.inFlight -= 1 + } + } +} + +/** + * Read the declared body size of a request. + * Returns `undefined` for chunked uploads, which carry no `Content-Length`. + */ +export function parseContentLength(header: string | undefined): number | undefined { + if (header === undefined) { + return undefined + } + + const value = Number(header) + if (!Number.isFinite(value) || value < 0) { + return undefined + } + + return value +} + +export type DiskReserveInput = { + /** Free bytes on the filesystem that holds the blockstore */ + availableBytes: number + /** Free bytes uploads must never consume */ + reserveBytes: number + /** Bytes the request may add, when the client declared them */ + requestedBytes?: number +} + +export type AdmissionResult = { allowed: true } | { allowed: false; reason: string } + +/** + * Decide whether an upload may start without eating into the disk reserve. + * + * The declared request size is subtracted up front so that content is rejected + * before a single block is written, instead of after the blockstore has already + * grown past the reserve. + */ +export function checkDiskReserve(input: DiskReserveInput): AdmissionResult { + const requested = input.requestedBytes ?? 0 + const remaining = input.availableBytes - requested + + if (remaining < input.reserveBytes) { + return { + allowed: false, + reason: + `Insufficient storage. Free space ${input.availableBytes} bytes, ` + + `requested ${requested} bytes, reserve ${input.reserveBytes} bytes.` + } + } + + return { allowed: true } +} + +/** Reject a request whose declared body already exceeds the aggregate limit. */ +export function checkRequestSize( + declaredBytes: number | undefined, + maxRequestSizeBytes: number +): AdmissionResult { + if (declaredBytes !== undefined && declaredBytes > maxRequestSizeBytes) { + return { + allowed: false, + reason: `Request size limit exceeded. Max ${maxRequestSizeBytes} bytes allowed.` + } + } + + return { allowed: true } +} + +export class RequestSizeLimitError extends Error { + constructor(public readonly maxRequestSizeBytes: number) { + super(`Request size limit exceeded. Max ${maxRequestSizeBytes} bytes allowed.`) + this.name = 'RequestSizeLimitError' + } +} + +/** + * Aggregate byte budget shared by every file of a single multipart request. + * + * `Content-Length` alone is not enough: chunked requests declare no size, so the + * budget is also enforced while the parts are being streamed. + */ +export class RequestSizeBudget { + private consumed = 0 + + constructor(private readonly maxRequestSizeBytes: number) {} + + get used(): number { + return this.consumed + } + + /** Account for streamed bytes. Throws once the aggregate limit is passed. */ + consume(bytes: number): void { + this.consumed += bytes + + if (this.consumed > this.maxRequestSizeBytes) { + throw new RequestSizeLimitError(this.maxRequestSizeBytes) + } + } +} diff --git a/src/storage/metrics.ts b/src/storage/metrics.ts new file mode 100644 index 0000000..9e5e5db --- /dev/null +++ b/src/storage/metrics.ts @@ -0,0 +1,98 @@ +import { config } from '../config.js' +import { blockstorePath, datastorePath } from '../store.js' +import { logger } from '../utils/logger.js' +import { availableStorageSize, dirSize } from '../utils/utils.js' +import { countByState, type FileState } from './registry.js' +import { fileRegistry } from './state.js' + +export interface StorageMetrics { + /** Bytes currently occupied by the blockstore directory. */ + blockstoreBytes: number + /** Bytes currently occupied by the datastore directory. */ + datastoreBytes: number + /** + * Bytes of content protected by a pin. + * + * Estimated from the blocks this node wrote for each registered file. Blocks + * shared between files are counted once per file, so the value is an upper + * bound for deduplicated content. + */ + pinnedBytes: number + /** + * Bytes garbage collection could reclaim right now: everything in the + * blockstore that no pin protects, which is released files plus blocks cached + * while serving downloads for other peers. + */ + reclaimableBytes: number + /** Free bytes on the filesystem that holds the blockstore. */ + availableBytes: number + /** Free bytes uploads must never consume. */ + reservedBytes: number + /** Free bytes uploads may still use. */ + usableBytes: number + files: Record & { total: number } + /** Unix epoch milliseconds of the last successful refresh. */ + updatedAt: number | null +} + +let cached: StorageMetrics = { + blockstoreBytes: 0, + datastoreBytes: 0, + pinnedBytes: 0, + reclaimableBytes: 0, + availableBytes: 0, + reservedBytes: config.storage.diskReserveBytes, + usableBytes: 0, + files: { temporary: 0, confirmed: 0, expired: 0, total: 0 }, + updatedAt: null +} + +/** + * Recompute the storage report. + * + * Directory scans and a full registry sweep are too expensive for a request + * path, so this runs on the disk usage schedule and the API serves the cache. + */ +export async function refreshStorageMetrics(): Promise { + const [blockstoreBytes, datastoreBytes, availableBytes, records] = await Promise.all([ + dirSize(blockstorePath), + dirSize(datastorePath), + availableStorageSize(blockstorePath).then(Number), + fileRegistry.all() + ]) + + const pinnedBytes = records + .filter((record) => record.pinned) + .reduce((total, record) => total + record.storedBytes, 0) + + const reservedBytes = config.storage.diskReserveBytes + + cached = { + blockstoreBytes, + datastoreBytes, + pinnedBytes, + reclaimableBytes: Math.max(0, blockstoreBytes - pinnedBytes), + availableBytes, + reservedBytes, + usableBytes: Math.max(0, availableBytes - reservedBytes), + files: { ...countByState(records), total: records.length }, + updatedAt: Date.now() + } + + if (blockstoreBytes === 0 && pinnedBytes > 0) { + // `dirSize` reports 0 for an unreadable directory as well as an empty one. + // Watermark collection cannot work while that happens, so say so once per + // scan rather than letting the collector silently never trigger. + logger.warn( + 'Blockstore size reads as 0 while pinned content is registered. ' + + 'Watermark-based garbage collection cannot run until the scan succeeds.' + ) + } + + return cached +} + +/** Last computed storage report. Zeroed until the first refresh completes. */ +export function getStorageMetrics(): StorageMetrics { + return cached +} diff --git a/src/storage/pinning.ts b/src/storage/pinning.ts new file mode 100644 index 0000000..82c7e14 --- /dev/null +++ b/src/storage/pinning.ts @@ -0,0 +1,84 @@ +import { CID } from 'multiformats/cid' +import type { IpfsNode } from '../ipfs-node.js' + +/** Consume an async iterable for its side effects. */ +async function drain(source: AsyncIterable): Promise { + for await (const item of source) { + void item + } +} + +/** + * Pin a DAG so that garbage collection cannot reclaim it. + * + * Idempotent: pinning content that already carries a direct pin is reported as + * `false` instead of failing, because Helia rejects a duplicate pin. + * + * @param signal Bounds the DAG walk, which fetches missing blocks from peers + * @returns True when this call created the pin + */ +export async function pinFile(node: IpfsNode, cid: CID, signal?: AbortSignal): Promise { + try { + // Draining the generator is what performs the DAG walk. + await drain(node.pins.add(cid, { signal })) + return true + } catch (err) { + if ((err as Error).name === 'AlreadyPinnedError') { + return false + } + throw err + } +} + +/** + * Remove the direct pin of a DAG, making it reclaimable. + * + * Idempotent: content without a direct pin is reported as `false`. + */ +export async function unpinFile(node: IpfsNode, cid: CID): Promise { + try { + // Draining the generator is what performs the DAG walk. + await drain(node.pins.rm(cid)) + return true + } catch (err) { + if ((err as Error).name === 'NotFoundError') { + return false + } + throw err + } +} + +/** + * True when garbage collection will keep the content. + * + * This is the predicate Helia applies while collecting, so it answers the only + * question that matters for a protection check: direct and indirect pins both + * count. + */ +export async function isProtected(node: IpfsNode, cid: CID): Promise { + return node.pins.isPinned(cid) +} + +/** + * True when the CID carries a direct pin, as opposed to an indirect one. + * + * `pins.get` is used rather than `pins.ls({ cid })`: listing filters by a + * datastore key prefix, and `datastore-fs` reads a prefix as a directory glob, + * so the lookup returns nothing for a pinned CID on a file-backed node. + */ +export async function isDirectlyPinned(node: IpfsNode, cid: CID): Promise { + try { + await node.pins.get(cid) + return true + } catch (err) { + if ((err as Error).name === 'NotFoundError') { + return false + } + throw err + } +} + +/** Normalise a CID string for registry keys and pin lookups. */ +export function parseRecordCid(cid: string): CID { + return CID.parse(cid) +} diff --git a/src/storage/registry.ts b/src/storage/registry.ts new file mode 100644 index 0000000..462a773 --- /dev/null +++ b/src/storage/registry.ts @@ -0,0 +1,254 @@ +import { Key } from 'interface-datastore' +import type { Datastore, Pair } from 'interface-datastore' + +/** + * Datastore namespace of the lifecycle registry. + * It is kept separate from the Helia records stored in the same datastore. + */ +export const REGISTRY_PREFIX = '/adm/files' + +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +/** Lifecycle state of a file known to this node. */ +export type FileState = 'temporary' | 'confirmed' | 'expired' + +export interface FileRecord { + cid: string + name: string + state: FileState + /** Unix epoch milliseconds when the file entered the blockstore. */ + createdAt: number + /** Unix epoch milliseconds after which a temporary file is reclaimable. */ + expiresAt: number | null + confirmedAt: number | null + /** Size reported by UnixFS for the file itself. */ + fileSize: number + /** Bytes of blocks this node actually wrote for the file. */ + storedBytes: number + /** True while the root CID is pinned and therefore protected from collection. */ + pinned: boolean + /** Names of peer nodes that acknowledged holding a copy. */ + replicas: string[] +} + +export interface NewFileRecord { + cid: string + name: string + fileSize: number + storedBytes: number +} + +function isNotFound(err: unknown): boolean { + return (err as { name?: string })?.name === 'NotFoundError' +} + +/** + * True when the backing store has no directory on disk yet. + * A node that never stored a file has an empty registry, not a broken one. + */ +function isMissingStore(err: unknown): boolean { + return (err as { code?: string })?.code === 'ENOENT' || isNotFound(err) +} + +/** + * Durable index of every file this node accepted, with its lifecycle state. + * + * The registry is the authority for "may this content be reclaimed?"; the Helia + * pinset is the mechanism that enforces the answer. + * + * - `temporary`: accepted but not yet durable, reclaimable after `expiresAt` + * - `confirmed`: durable, pinned, never reclaimed by garbage collection + * - `expired`: released by TTL or by an explicit unpin, reclaimable now + */ +export class FileRegistry { + constructor( + private readonly datastore: Datastore, + private readonly prefix: string = REGISTRY_PREFIX + ) {} + + private key(cid: string): Key { + return new Key(`${this.prefix}/${cid}`) + } + + async get(cid: string): Promise { + try { + return JSON.parse(decoder.decode(await this.datastore.get(this.key(cid)))) as FileRecord + } catch (err) { + if (isNotFound(err)) { + return undefined + } + throw err + } + } + + async save(record: FileRecord): Promise { + await this.datastore.put(this.key(record.cid), encoder.encode(JSON.stringify(record))) + return record + } + + async remove(cid: string): Promise { + try { + await this.datastore.delete(this.key(cid)) + } catch (err) { + if (!isNotFound(err)) { + throw err + } + } + } + + async *list(): AsyncGenerator { + // `query` may return a sync iterable (memory) or an async one (file system). + const source = this.datastore.query({ prefix: this.prefix }) + const entries: Iterator | AsyncIterator = + Symbol.asyncIterator in source ? source[Symbol.asyncIterator]() : source[Symbol.iterator]() + + for (;;) { + let entry + + try { + entry = await entries.next() + } catch (err) { + if (isMissingStore(err)) { + // The datastore directory is created with the first write, so a node + // that has not stored anything yet simply has no records. + return + } + throw err + } + + if (entry.done === true) { + return + } + + try { + yield JSON.parse(decoder.decode(entry.value.value)) as FileRecord + } catch { + // A corrupted entry must not stop the whole sweep; garbage collection + // ignores it and the blocks stay protected until it is repaired. + continue + } + } + } + + /** Every record, materialised for callers that need more than one pass. */ + async all(): Promise { + const records: FileRecord[] = [] + for await (const record of this.list()) { + records.push(record) + } + return records + } + + /** + * Register a freshly uploaded file. + * + * @param file Identity and size of the imported content + * @param options `confirmationRequired` decides whether the upload becomes + * durable right away, which keeps the current client protocol working, or + * stays temporary until an authorized confirmation arrives + */ + async register( + file: NewFileRecord, + options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } + ): Promise { + const now = options.now ?? Date.now() + const existing = await this.get(file.cid) + + if (existing?.state === 'confirmed') { + // Re-uploading already durable content must not weaken its state. + return this.save({ + ...existing, + storedBytes: Math.max(existing.storedBytes, file.storedBytes), + fileSize: file.fileSize > 0 ? file.fileSize : existing.fileSize + }) + } + + const confirmed = !options.confirmationRequired + + return this.save({ + cid: file.cid, + name: file.name, + state: confirmed ? 'confirmed' : 'temporary', + createdAt: existing?.createdAt ?? now, + expiresAt: confirmed ? null : now + options.temporaryTtlMs, + confirmedAt: confirmed ? now : null, + fileSize: file.fileSize, + storedBytes: file.storedBytes, + pinned: true, + replicas: existing?.replicas ?? [] + }) + } + + /** Move a temporary file to the durable state. Confirming twice is a no-op. */ + async confirm(cid: string, now: number = Date.now()): Promise { + const record = await this.get(cid) + if (!record) { + return undefined + } + + if (record.state === 'confirmed') { + return record + } + + return this.save({ + ...record, + state: 'confirmed', + expiresAt: null, + confirmedAt: now, + pinned: true + }) + } + + /** + * Release a file so that garbage collection may reclaim it. + * The blocks stay on disk until the collector runs. + */ + async release(cid: string): Promise { + const record = await this.get(cid) + if (!record) { + return undefined + } + + return this.save({ ...record, state: 'expired', pinned: false }) + } + + async setPinned(cid: string, pinned: boolean): Promise { + const record = await this.get(cid) + return record ? this.save({ ...record, pinned }) : undefined + } + + async setReplicas(cid: string, replicas: string[]): Promise { + const record = await this.get(cid) + return record ? this.save({ ...record, replicas }) : undefined + } +} + +/** + * True when a temporary upload was abandoned, meaning nobody confirmed it + * before its TTL elapsed. Confirmed files never expire. + */ +export function isExpired(record: FileRecord, now: number = Date.now()): boolean { + if (record.state === 'expired') { + return true + } + + if (record.state !== 'temporary' || record.expiresAt === null) { + return false + } + + return record.expiresAt <= now +} + +/** Files that garbage collection is allowed to unpin and reclaim. */ +export function isReclaimable(record: FileRecord, now: number = Date.now()): boolean { + return record.state !== 'confirmed' && isExpired(record, now) +} + +export function countByState(records: FileRecord[]): Record { + const counts: Record = { temporary: 0, confirmed: 0, expired: 0 } + for (const record of records) { + counts[record.state] += 1 + } + return counts +} diff --git a/src/storage/replication.ts b/src/storage/replication.ts new file mode 100644 index 0000000..7332aa0 --- /dev/null +++ b/src/storage/replication.ts @@ -0,0 +1,133 @@ +import type { ReplicationConfig } from './config.js' + +export type ReplicationTarget = { + name: string + apiUrl: string +} + +export type ReplicationAck = { + node: string + ok: boolean + error?: string +} + +export type ReplicationReport = { + /** + * `quorum` when the node actively pushes copies to its peers, + * `best-effort` when replication is disabled and durability is local only. + */ + mode: 'quorum' | 'best-effort' + /** Copies the policy requires, including the copy on this node */ + factor: number + /** Acknowledgements the policy requires before an upload is durable */ + required: number + /** Copies confirmed right now, including the copy on this node */ + acknowledged: number + /** Peer nodes that acknowledged holding a copy */ + replicas: string[] + satisfied: boolean + attempts: ReplicationAck[] +} + +export type ReplicateOptions = { + cid: string + targets: ReplicationTarget[] + config: ReplicationConfig + token: string + /** Injected for tests; defaults to the global fetch */ + request?: (target: ReplicationTarget, cid: string) => Promise +} + +/** + * Ask a peer node to store a copy of the content. + * + * The peer pulls the DAG over libp2p and pins it, so a successful response + * means the copy exists and is protected, not merely that the request arrived. + */ +async function pushToPeer( + target: ReplicationTarget, + cid: string, + config: ReplicationConfig, + token: string +): Promise { + const url = `${target.apiUrl.replace(/\/+$/, '')}/api/replication/${cid}` + + const response = await fetch(url, { + method: 'POST', + headers: { + 'x-replication-token': token, + 'content-type': 'application/json' + }, + signal: AbortSignal.timeout(config.requestTimeoutMs) + }) + + if (!response.ok) { + throw new Error(`${target.name} responded with HTTP ${response.status}`) + } +} + +/** + * Replicate content across the configured ADAMANT nodes and report whether the + * acknowledgement quorum was reached. + * + * When replication is disabled the node reports `best-effort`: the content is + * stored and pinned locally, and no durability claim is made beyond that. + */ +export async function replicate(options: ReplicateOptions): Promise { + const { config, cid } = options + + if (!config.enabled) { + return { + mode: 'best-effort', + factor: 1, + required: 1, + acknowledged: 1, + replicas: [], + satisfied: true, + attempts: [] + } + } + + const request = options.request ?? ((target) => pushToPeer(target, cid, config, options.token)) + + const attempts = await Promise.all( + options.targets.map(async (target): Promise => { + try { + await request(target, cid) + return { node: target.name, ok: true } + } catch (err) { + return { node: target.name, ok: false, error: (err as Error).message } + } + }) + ) + + const replicas = attempts.filter((attempt) => attempt.ok).map((attempt) => attempt.node) + // The local copy counts towards the quorum: it is pinned before peers are asked. + const acknowledged = replicas.length + 1 + + return { + mode: 'quorum', + factor: config.factor, + required: config.ackQuorum, + acknowledged, + replicas, + satisfied: acknowledged >= config.ackQuorum, + attempts + } +} + +/** + * Copies still missing for a file, given the peers that already acknowledged. + * The local copy is always counted, so the result is what repair must create. + */ +export function missingReplicas(config: ReplicationConfig, replicas: string[]): number { + return Math.max(0, config.factor - (replicas.length + 1)) +} + +/** Files that hold fewer copies than the replication factor requires. */ +export function selectUnderReplicated( + records: T[], + config: ReplicationConfig +): T[] { + return records.filter((record) => missingReplicas(config, record.replicas) > 0) +} diff --git a/src/storage/service.ts b/src/storage/service.ts new file mode 100644 index 0000000..21a5f62 --- /dev/null +++ b/src/storage/service.ts @@ -0,0 +1,171 @@ +import { CID } from 'multiformats/cid' +import { config } from '../config.js' +import { helia, ifs } from '../helia.js' +import { logger } from '../utils/logger.js' +import { getNodesList } from '../utils/utils.js' +import { pinFile, unpinFile } from './pinning.js' +import { + replicate, + selectUnderReplicated, + type ReplicationReport, + type ReplicationTarget +} from './replication.js' +import type { FileRecord } from './registry.js' +import { fileRegistry } from './state.js' + +/** + * Peer nodes that can accept a copy. + * + * A node without `apiUrl` cannot be reached by the replication control plane + * and is skipped, which keeps mixed configurations usable during a rollout. + */ +export function getReplicationTargets(): ReplicationTarget[] { + return getNodesList([helia.libp2p.peerId.toString()]) + .filter((node): node is typeof node & { apiUrl: string } => typeof node.apiUrl === 'string') + .map((node) => ({ name: node.name, apiUrl: node.apiUrl })) +} + +/** + * Push a copy of `cid` to the peer nodes and store the acknowledgements. + * + * Failures are reported, never thrown: an upload that is already stored and + * pinned locally stays valid when a peer is unavailable, and the repair job + * retries later. + */ +export async function replicateFile(cid: string): Promise { + const report = await replicate({ + cid, + targets: getReplicationTargets(), + config: config.replication, + token: config.replication.token + }) + + if (report.mode === 'quorum') { + await fileRegistry.setReplicas(cid, report.replicas) + + if (!report.satisfied) { + logger.warn( + `Replication quorum not reached for ${cid}: ` + + `${report.acknowledged}/${report.required} copies` + ) + } + } + + return report +} + +/** + * Pin content that is not in the registry yet and record it as durable. + * + * The DAG is pulled over libp2p if it is missing locally, bounded by the + * replication request timeout so an unreachable CID cannot hang the caller. + * + * @param name Display name recorded for the file; defaults to its CID + */ +async function registerPinned(cid: CID, name: string): Promise { + const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) + + await pinFile(helia, cid, signal) + + // Everything is local after pinning, so the deduplicated DAG size is what + // this node actually holds on disk for the file. + const stats = await ifs.stat(cid, { extended: true, offline: true, signal }) + + return fileRegistry.register( + { + cid: cid.toString(), + name, + fileSize: Number(stats.size), + storedBytes: Number(stats.deduplicatedDagSize) + }, + { confirmationRequired: false, temporaryTtlMs: config.storage.temporaryTtlMs } + ) +} + +/** + * Make a file durable and replicate it. + * + * @param options `registerUnknown` decides what happens for a CID the node + * never accepted through an upload: the confirmation endpoint reports it as + * unknown, while an explicit pin request stores and registers it + */ +export async function confirmFile( + cid: string, + options: { registerUnknown?: boolean } = {} +): Promise { + const parsed = CID.parse(cid) + const known = await fileRegistry.get(cid) + + if (!known && options.registerUnknown !== true) { + return undefined + } + + if (known) { + await pinFile(helia, parsed) + await fileRegistry.confirm(cid) + } else { + await registerPinned(parsed, cid) + } + + await replicateFile(cid) + return fileRegistry.get(cid) +} + +/** + * Release a file so garbage collection may reclaim it. + * Blocks stay on disk until the collector runs, which keeps the action + * reversible until then. + */ +export async function releaseFile(cid: string): Promise { + await unpinFile(helia, CID.parse(cid)) + return fileRegistry.release(cid) +} + +/** + * Store a copy requested by another ADAMANT node. + * + * The DAG is pulled over libp2p and pinned before the response is sent, so an + * acknowledgement means the copy is durable on this node. + */ +export async function acceptReplica(cid: string): Promise { + return registerPinned(CID.parse(cid), cid) +} + +export interface RepairReport { + checked: number + underReplicated: number + repaired: string[] + stillMissing: string[] +} + +/** + * Detect and repair under-replicated durable content. + * + * Only confirmed files are considered: temporary uploads may still disappear by + * policy, so spending peer bandwidth on them is not worthwhile. + */ +export async function repairReplication(): Promise { + const report: RepairReport = { checked: 0, underReplicated: 0, repaired: [], stillMissing: [] } + + if (!config.replication.enabled) { + return report + } + + const confirmed = (await fileRegistry.all()).filter((record) => record.state === 'confirmed') + + report.checked = confirmed.length + const candidates = selectUnderReplicated(confirmed, config.replication) + report.underReplicated = candidates.length + + for (const record of candidates) { + const result = await replicateFile(record.cid) + + if (result.satisfied && result.acknowledged >= config.replication.factor) { + report.repaired.push(record.cid) + } else { + report.stillMissing.push(record.cid) + } + } + + return report +} diff --git a/src/storage/state.ts b/src/storage/state.ts new file mode 100644 index 0000000..ba1923a --- /dev/null +++ b/src/storage/state.ts @@ -0,0 +1,13 @@ +import { config } from '../config.js' +import { datastore } from '../store.js' +import { ConcurrencyLimiter } from './limits.js' +import { FileRegistry } from './registry.js' + +/** + * Lifecycle registry of every file this node accepted. + * It shares the node datastore and lives under its own key prefix. + */ +export const fileRegistry = new FileRegistry(datastore) + +/** Guards how many uploads may write into the blockstore at the same time. */ +export const uploadLimiter = new ConcurrencyLimiter(config.storage.maxConcurrentUploads) diff --git a/src/storage/uploadSession.ts b/src/storage/uploadSession.ts new file mode 100644 index 0000000..6eedfdb --- /dev/null +++ b/src/storage/uploadSession.ts @@ -0,0 +1,217 @@ +import type { Blockstore } from 'interface-blockstore' +import type { CID } from 'multiformats/cid' +import { RequestSizeBudget } from './limits.js' + +type Blocks = Blockstore + +/** + * Blocks that in-flight uploads are currently writing, counted per CID. + * + * Two requests may import identical content at the same time. Without this + * counter a failing request would delete blocks a concurrent request still + * depends on, so cleanup only touches CIDs no other session references. + */ +const inFlightBlocks = new Map() + +function retainBlock(cid: string): void { + inFlightBlocks.set(cid, (inFlightBlocks.get(cid) ?? 0) + 1) +} + +function releaseBlock(cid: string): number { + const next = (inFlightBlocks.get(cid) ?? 1) - 1 + + if (next <= 0) { + inFlightBlocks.delete(cid) + return 0 + } + + inFlightBlocks.set(cid, next) + return next +} + +/** Number of CIDs in-flight uploads currently hold. Exposed for diagnostics. */ +export function inFlightBlockCount(): number { + return inFlightBlocks.size +} + +type PutOptions = Parameters[2] +type GetOptions = Parameters[1] +type HasOptions = Parameters[1] +type BlockValue = Parameters[1] + +/** + * Blockstore facade that remembers which blocks an upload actually created. + * + * Blocks that already existed are not recorded: they belong to previously + * stored content and must survive the failure of the current upload. + */ +class RecordingBlockstore implements Pick { + /** CIDs this session wrote for the first time. */ + readonly created = new Set() + /** CIDs this session wrote, including duplicates of existing blocks. */ + private readonly touched = new Set() + bytesWritten = 0 + + constructor(private readonly base: Blocks) {} + + async put(cid: CID, block: BlockValue, options?: PutOptions): Promise { + const key = cid.toString() + const existed = await this.base.has(cid, options) + + if (!this.touched.has(key)) { + this.touched.add(key) + retainBlock(key) + } + + const result = await this.base.put(cid, block, options) + + if (!existed && !this.created.has(key)) { + this.created.add(key) + // The UnixFS importer always persists a whole block as one `Uint8Array`. + // A streamed value carries no length to account for without consuming it, + // so it is recorded for cleanup but contributes nothing to the estimate. + this.bytesWritten += block instanceof Uint8Array ? block.byteLength : 0 + } + + return result + } + + get(cid: CID, options?: GetOptions): ReturnType { + return this.base.get(cid, options) + } + + async has(cid: CID, options?: HasOptions): Promise { + return this.base.has(cid, options) + } + + /** Drop the in-flight references taken by `put`. */ + releaseAll(): string[] { + const zeroed: string[] = [] + + for (const key of this.touched) { + if (releaseBlock(key) === 0) { + zeroed.push(key) + } + } + + this.touched.clear() + return zeroed + } +} + +export interface UploadSessionOptions { + /** + * Blockstore the upload writes through. + * + * The Helia facade belongs here: it announces new blocks to connected peers + * and takes the collection lock while writing. + */ + blockstore: Blockstore + /** Decides whether a block is protected from removal. */ + isPinned: (cid: CID) => Promise + /** + * Removes a block this session created. + * + * This is deliberately not the Helia blockstore facade: its `delete` cancels + * a reprovide first, which fails outright on a node that registers no content + * routers, so cleanup addresses the store that actually holds the block. + */ + deleteBlock: (cid: CID) => Promise + maxRequestSizeBytes: number + parseCid: (cid: string) => CID + onCleanupError?: (err: Error) => void +} + +/** + * Tracks everything a single upload request writes so that a rejected, + * aborted or partially failed request leaves no blocks behind. + * + * The session is committed only after every file has been registered and + * pinned. Any other outcome, including a client disconnect, ends in cleanup. + */ +export class UploadSession { + readonly budget: RequestSizeBudget + readonly blockstore: RecordingBlockstore + private settled = false + + constructor(private readonly options: UploadSessionOptions) { + this.budget = new RequestSizeBudget(options.maxRequestSizeBytes) + this.blockstore = new RecordingBlockstore(options.blockstore) + } + + get bytesWritten(): number { + return this.blockstore.bytesWritten + } + + get isSettled(): boolean { + return this.settled + } + + /** + * Start accounting for a single part. + * + * Parts of a multipart request are imported one after another, so the delta + * of the session counter is the number of new bytes this part contributed. + */ + beginFile(): { readonly storedBytes: number } { + const before = this.blockstore.bytesWritten + const blockstore = this.blockstore + + return { + get storedBytes() { + return blockstore.bytesWritten - before + } + } + } + + /** Keep everything this session wrote. Called once the upload is durable. */ + commit(): void { + if (this.settled) { + return + } + + this.settled = true + this.blockstore.releaseAll() + } + + /** + * Remove the blocks this session created. + * + * A block is deleted only when no other in-flight upload references it and it + * is not pinned, so content that became durable in the meantime is never + * touched. Whatever survives this pass is unpinned and therefore reclaimable + * by the next garbage collection. + * + * @returns Number of blocks removed + */ + async cleanup(): Promise { + if (this.settled) { + return 0 + } + + this.settled = true + const releasable = new Set(this.blockstore.releaseAll()) + let removed = 0 + + for (const key of this.blockstore.created) { + if (!releasable.has(key)) { + continue + } + + try { + const cid = this.options.parseCid(key) + + if (await this.options.isPinned(cid)) { + continue + } + + await this.options.deleteBlock(cid) + removed += 1 + } catch (err) { + this.options.onCleanupError?.(err as Error) + } + } + + return removed + } +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 9efebcb..15b3f3e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -6,11 +6,18 @@ export type NodeWithPeerId = { name: string multiAddr: Multiaddr peerId: PeerId + /** Base URL of the node REST API; only replication peers need one. */ + apiUrl?: string } export type ConfigNode = { name: string multiAddr: string + apiUrl?: string } -export type UnixFsMulterFile = Express.Multer.File & { cid: CID } +export type UnixFsMulterFile = Express.Multer.File & { + cid: CID + /** Bytes of blocks this node wrote while importing the part. */ + storedBytes: number +} diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index 6285f1a..f1bd8d6 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -1,14 +1,47 @@ import { StorageEngine } from 'multer' import * as e from 'express' -import { UnixFS } from '@helia/unixfs' +import { unixfs } from '@helia/unixfs' +import { Readable, Transform } from 'node:stream' import { logger } from './logger.js' import { UnixFsMulterFile } from './types.js' import { sanitizeFilename } from './sanitizeFilename.js' +import type { RequestSizeBudget } from '../storage/limits.js' +import type { UploadSession } from '../storage/uploadSession.js' export interface UnixfsStorageOptions { - unixfs: UnixFS + /** Resolve the session that owns everything written by this request. */ + getSession: (req: e.Request) => UploadSession } +/** + * Wrap a part stream so that the aggregate request budget is enforced while the + * bytes flow. + * + * `Content-Length` is checked before parsing starts, but a chunked request + * declares no size, so the limit is applied here as well. + */ +function budgeted(stream: Readable, budget: RequestSizeBudget): Readable { + const meter = new Transform({ + transform(chunk: Buffer, encoding, callback) { + try { + budget.consume(chunk.length) + callback(null, chunk) + } catch (err) { + callback(err as Error) + } + } + }) + + return stream.pipe(meter) +} + +/** + * Multer storage engine that imports parts straight into the Helia blockstore. + * + * Every write goes through the request `UploadSession`, which records the + * blocks the request created so they can be removed again if the request is + * rejected, aborted, or fails after some files were already imported. + */ export class UnixfsMulterStorage implements StorageEngine { constructor(private readonly options: UnixfsStorageOptions) {} @@ -31,22 +64,42 @@ export class UnixfsMulterStorage implements StorageEngine { ): void { file.originalname = sanitizeFilename(file.originalname) - this.options.unixfs - .addByteStream(file.stream) + const session = this.options.getSession(req) + const write = session.beginFile() + + unixfs({ blockstore: session.blockstore }) + .addByteStream(budgeted(file.stream, session.budget)) .then((cid) => { - callback(undefined, { ...file, cid }) + callback(undefined, { ...file, cid, storedBytes: write.storedBytes }) }) .catch((err) => { callback(err, undefined) }) } + /** + * Called by multer for every already-imported part when a request is aborted. + * + * Cleanup is performed for the whole session at once, because a partially + * written part has no CID to remove individually. + */ _removeFile( req: e.Request, file: Express.Multer.File, callback: (error: Error | null) => void ): void { - logger.info('Remove file requested') - callback(null) + this.options + .getSession(req) + .cleanup() + .then((removed) => { + if (removed > 0) { + logger.info(`Removed ${removed} blocks left by a rejected upload request`) + } + callback(null) + }) + .catch((err) => { + logger.error(`Upload cleanup failed: ${(err as Error).message}`) + callback(null) + }) } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index eabdd9e..230ea1a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -30,7 +30,8 @@ export function parseNodes(): NodeWithPeerId[] { return config.nodes.map((node: ConfigNode) => ({ name: node.name, multiAddr: multiaddr(node.multiAddr), - peerId: peerIdFromMultiaddr(node.multiAddr) + peerId: peerIdFromMultiaddr(node.multiAddr), + apiUrl: node.apiUrl })) } diff --git a/test/cidCompatibility.test.ts b/test/cidCompatibility.test.ts index 994f448..6754bf9 100644 --- a/test/cidCompatibility.test.ts +++ b/test/cidCompatibility.test.ts @@ -9,12 +9,25 @@ import { FsBlockstore } from 'blockstore-fs' import { CID } from 'multiformats/cid' import { CID_FIXTURES, README_FIXTURE, deterministicBytes } from './fixtures.js' import { UnixfsMulterStorage } from '../src/utils/unixfs-multer.storage.js' +import { UploadSession } from '../src/storage/uploadSession.js' import type { UnixFsMulterFile } from '../src/utils/types.js' let storeDir: string let blockstore: FsBlockstore let ifs: UnixFS let storage: UnixfsMulterStorage +let session: UploadSession + +/** A session over the bare blockstore; nothing is pinned in this test. */ +function createSession(): UploadSession { + return new UploadSession({ + blockstore, + isPinned: async () => false, + deleteBlock: (cid) => blockstore.delete(cid), + maxRequestSizeBytes: 64 * 1024 * 1024, + parseCid: (value) => CID.parse(value) + }) +} /** * Push a buffer through the production multer storage engine and return the @@ -32,6 +45,8 @@ async function uploadThroughStorageEngine( stream: Readable.from(content) } as unknown as Express.Multer.File + session = createSession() + return new Promise((resolve, reject) => { storage._handleFile({} as never, file, (err, result) => { if (err != null) { @@ -48,7 +63,7 @@ before(async () => { blockstore = new FsBlockstore(join(storeDir, 'blockstore')) await blockstore.open() ifs = unixfs({ blockstore }) - storage = new UnixfsMulterStorage({ unixfs: ifs }) + storage = new UnixfsMulterStorage({ getSession: () => session }) }) after(async () => { diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts new file mode 100644 index 0000000..5065605 --- /dev/null +++ b/test/fileRegistry.test.ts @@ -0,0 +1,177 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { FsDatastore } from 'datastore-fs' +import { FileRegistry, isExpired, isReclaimable, type FileRecord } from '../src/storage/registry.js' + +const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' +const CID_B = 'bafkreihfzyt2g6pbd4rhk67deek7xh33xams74spn72eqq5qhx2ypphvii' +const TTL = 60_000 + +let storeDir: string +let datastore: FsDatastore +let counter = 0 + +/** A registry over its own key prefix so tests never see each other's records. */ +function createRegistry(): FileRegistry { + counter += 1 + return new FileRegistry(datastore, `/adm/test-${counter}`) +} + +const newFile = (cid = CID_A) => ({ cid, name: 'photo.jpg', fileSize: 100, storedBytes: 120 }) + +before(async () => { + storeDir = await mkdtemp(join(tmpdir(), 'ipfs-node-registry-')) + datastore = new FsDatastore(join(storeDir, 'datastore')) + await datastore.open() +}) + +after(async () => { + await datastore.close() + await rm(storeDir, { recursive: true, force: true }) +}) + +describe('FileRegistry', () => { + it('registers an upload as confirmed when confirmation is not required', async () => { + const registry = createRegistry() + + const record = await registry.register(newFile(), { + confirmationRequired: false, + temporaryTtlMs: TTL, + now: 1000 + }) + + assert.equal(record.state, 'confirmed') + assert.equal(record.expiresAt, null) + assert.equal(record.confirmedAt, 1000) + assert.equal(record.pinned, true) + }) + + it('registers an upload as temporary with a TTL when confirmation is required', async () => { + const registry = createRegistry() + + const record = await registry.register(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL, + now: 1000 + }) + + assert.equal(record.state, 'temporary') + assert.equal(record.expiresAt, 1000 + TTL) + assert.equal(record.confirmedAt, null) + }) + + it('never downgrades content that is already confirmed', async () => { + const registry = createRegistry() + await registry.register(newFile(), { + confirmationRequired: false, + temporaryTtlMs: TTL, + now: 1000 + }) + + const reuploaded = await registry.register(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL, + now: 2000 + }) + + assert.equal(reuploaded.state, 'confirmed') + assert.equal(reuploaded.expiresAt, null) + }) + + it('confirms a temporary file and clears its expiry', async () => { + const registry = createRegistry() + await registry.register(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL, + now: 1000 + }) + + const confirmed = await registry.confirm(CID_A, 5000) + + assert.equal(confirmed?.state, 'confirmed') + assert.equal(confirmed?.expiresAt, null) + assert.equal(confirmed?.confirmedAt, 5000) + }) + + it('reports an unknown CID instead of creating one on confirmation', async () => { + assert.equal(await createRegistry().confirm(CID_B), undefined) + }) + + it('releases a confirmed file so it becomes reclaimable', async () => { + const registry = createRegistry() + await registry.register(newFile(), { + confirmationRequired: false, + temporaryTtlMs: TTL, + now: 1000 + }) + + const released = await registry.release(CID_A) + + assert.equal(released?.state, 'expired') + assert.equal(released?.pinned, false) + assert.equal(isReclaimable(released as FileRecord, 1000), true) + }) + + it('lists every registered file and survives a reopen', async () => { + const registry = createRegistry() + await registry.register(newFile(CID_A), { confirmationRequired: false, temporaryTtlMs: TTL }) + await registry.register(newFile(CID_B), { confirmationRequired: true, temporaryTtlMs: TTL }) + + const cids = (await registry.all()).map((record) => record.cid) + + assert.deepEqual(cids.sort(), [CID_A, CID_B].sort()) + }) + + it('forgets a removed file and tolerates removing it twice', async () => { + const registry = createRegistry() + await registry.register(newFile(), { confirmationRequired: false, temporaryTtlMs: TTL }) + + await registry.remove(CID_A) + await assert.doesNotReject(() => registry.remove(CID_A)) + + assert.equal(await registry.get(CID_A), undefined) + }) + + it('reports an empty registry for a store that does not exist yet', async () => { + const missing = join(tmpdir(), `ipfs-node-registry-missing-${process.pid}`) + const registry = new FileRegistry(new FsDatastore(missing)) + + assert.deepEqual(await registry.all(), []) + }) +}) + +describe('expiry rules', () => { + const record: Omit = { + cid: CID_A, + name: 'photo.jpg', + fileSize: 100, + storedBytes: 120, + createdAt: 1000, + confirmedAt: null, + pinned: true, + replicas: [] + } + + it('expires an abandoned temporary upload after its TTL', () => { + const temporary: FileRecord = { ...record, state: 'temporary', expiresAt: 2000 } + + assert.equal(isExpired(temporary, 1999), false) + assert.equal(isExpired(temporary, 2000), true) + }) + + it('never expires confirmed content', () => { + const confirmed: FileRecord = { ...record, state: 'confirmed', expiresAt: null } + + assert.equal(isExpired(confirmed, Number.MAX_SAFE_INTEGER), false) + assert.equal(isReclaimable(confirmed, Number.MAX_SAFE_INTEGER), false) + }) + + it('treats an explicitly released file as reclaimable right away', () => { + const released: FileRecord = { ...record, state: 'expired', expiresAt: null, pinned: false } + + assert.equal(isReclaimable(released, 0), true) + }) +}) diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts new file mode 100644 index 0000000..4accad1 --- /dev/null +++ b/test/garbageCollectionPlan.test.ts @@ -0,0 +1,101 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { planGarbageCollection } from '../src/storage/gc.js' +import type { FileRecord } from '../src/storage/registry.js' + +const watermarks = { highWatermarkBytes: 1000, lowWatermarkBytes: 500 } + +function record(overrides: Partial & Pick): FileRecord { + return { + name: 'file', + createdAt: 0, + expiresAt: null, + confirmedAt: null, + fileSize: 100, + storedBytes: 100, + pinned: true, + replicas: [], + ...overrides + } +} + +describe('planGarbageCollection', () => { + it('does not collect while the blockstore is below the high watermark', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 900, + watermarks, + records: [record({ cid: 'a', state: 'temporary', expiresAt: 5000 })], + now: 1000 + }) + + assert.equal(plan.shouldCollect, false) + assert.deepEqual(plan.evicted, []) + }) + + it('releases an abandoned upload once its TTL elapsed, whatever the size is', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 10, + watermarks, + records: [record({ cid: 'a', state: 'temporary', expiresAt: 500 })], + now: 1000 + }) + + assert.deepEqual( + plan.expired.map((item) => item.cid), + ['a'] + ) + }) + + it('never selects confirmed content', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 5000, + watermarks, + records: [ + record({ cid: 'durable', state: 'confirmed' }), + record({ cid: 'temp', state: 'temporary', expiresAt: 9000 }) + ], + now: 1000 + }) + + assert.equal(plan.shouldCollect, true) + const released = [...plan.expired, ...plan.evicted].map((item) => item.cid) + assert.deepEqual(released, ['temp']) + assert.ok(plan.retained.some((item) => item.cid === 'durable')) + }) + + it('evicts the oldest unconfirmed files until the low watermark is reached', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 800, + watermarks: { highWatermarkBytes: 700, lowWatermarkBytes: 600 }, + records: [ + record({ cid: 'newest', state: 'temporary', createdAt: 300, expiresAt: 9000 }), + record({ cid: 'oldest', state: 'temporary', createdAt: 100, expiresAt: 9000 }), + record({ cid: 'middle', state: 'temporary', createdAt: 200, expiresAt: 9000 }) + ], + now: 1000 + }) + + assert.deepEqual( + plan.evicted.map((item) => item.cid), + ['oldest', 'middle'] + ) + assert.equal(plan.estimatedBytesAfter, 600) + }) + + it('stops evicting as soon as the estimate is below the low watermark', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 750, + watermarks: { highWatermarkBytes: 700, lowWatermarkBytes: 700 }, + records: [ + record({ cid: 'a', state: 'temporary', createdAt: 1, expiresAt: 9000 }), + record({ cid: 'b', state: 'temporary', createdAt: 2, expiresAt: 9000 }) + ], + now: 1000 + }) + + assert.deepEqual( + plan.evicted.map((item) => item.cid), + ['a'] + ) + }) +}) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts new file mode 100644 index 0000000..c397e70 --- /dev/null +++ b/test/integration/storageLifecycle.test.ts @@ -0,0 +1,322 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { unixfs, type UnixFS } from '@helia/unixfs' +import { FsBlockstore } from 'blockstore-fs' +import { FsDatastore } from 'datastore-fs' +import { CID } from 'multiformats/cid' +import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' +import { runGarbageCollection } from '../../src/storage/gc.js' +import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' +import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' +import { UploadSession } from '../../src/storage/uploadSession.js' +import { deterministicBytes } from '../fixtures.js' + +/** Only loopback, and port 0 so the OS picks a free port. */ +const LISTEN = ['/ip4/127.0.0.1/tcp/0'] +const WATERMARKS = { highWatermarkBytes: 1_000_000_000, lowWatermarkBytes: 500_000_000 } + +let storeDir: string +let blockstore: FsBlockstore +let datastore: FsDatastore +let node: IpfsNode +let ifs: UnixFS +let prefixCounter = 0 + +function createRegistry(): FileRegistry { + prefixCounter += 1 + return new FileRegistry(datastore, `/adm/lifecycle-${prefixCounter}`) +} + +function createSession(maxRequestSizeBytes = 64 * 1024 * 1024): UploadSession { + return new UploadSession({ + blockstore: node.blockstore, + isPinned: (cid) => node.pins.isPinned(cid), + deleteBlock: (cid) => blockstore.delete(cid), + maxRequestSizeBytes, + parseCid: (value) => CID.parse(value), + onCleanupError: (err) => assert.fail(`unexpected cleanup error: ${err.message}`) + }) +} + +function record(overrides: Pick & Partial): FileRecord { + return { + name: 'file', + createdAt: 0, + expiresAt: null, + confirmedAt: null, + fileSize: 0, + storedBytes: 100, + pinned: true, + replicas: [], + ...overrides + } +} + +/** + * Multihash of a CID, as a hex string. + * + * The blockstore addresses blocks by multihash and rebuilds a CID with its own + * default codec while listing, so a stored block can carry a different CID + * representation than the one a file was uploaded under. Comparing digests is + * what makes "is this block still here?" answerable. + */ +function digestOf(cid: CID | string): string { + const parsed = typeof cid === 'string' ? CID.parse(cid) : cid + return Buffer.from(parsed.multihash.bytes).toString('hex') +} + +/** Digests of every block currently held by the blockstore. */ +async function storedDigests(): Promise> { + const digests = new Set() + for await (const { cid } of blockstore.getAll()) { + digests.add(digestOf(cid)) + } + return digests +} + +before(async () => { + storeDir = await mkdtemp(join(tmpdir(), 'ipfs-node-lifecycle-')) + blockstore = new FsBlockstore(join(storeDir, 'blockstore')) + datastore = new FsDatastore(join(storeDir, 'datastore')) + await blockstore.open() + await datastore.open() + + node = await createIpfsNode({ blockstore, datastore, listen: LISTEN }) + ifs = unixfs(node) +}) + +after(async () => { + await node.stop() + await blockstore.close() + await datastore.close() + await rm(storeDir, { recursive: true, force: true }) +}) + +describe('pin helpers on a file-backed node', () => { + it('pins once and reports a repeated pin as a no-op', async () => { + const cid = await ifs.addBytes(deterministicBytes(64, 'pin-once')) + + assert.equal(await pinFile(node, cid), true) + assert.equal(await pinFile(node, cid), false) + assert.equal(await isProtected(node, cid), true) + assert.equal(await isDirectlyPinned(node, cid), true) + }) + + it('unpins once and reports a repeated unpin as a no-op', async () => { + const cid = await ifs.addBytes(deterministicBytes(64, 'unpin-once')) + await pinFile(node, cid) + + assert.equal(await unpinFile(node, cid), true) + assert.equal(await unpinFile(node, cid), false) + assert.equal(await isProtected(node, cid), false) + }) + + it('reports a leaf of a pinned DAG as protected but not directly pinned', async () => { + // Larger than the 1 MiB chunk size, so the root is a dag-pb node over leaves + const root = await ifs.addBytes(deterministicBytes(3_145_735, 'multi')) + + // `pins.add` yields every CID it walks, so the leaves come out with the root + const walked: CID[] = [] + for await (const cid of node.pins.add(root)) { + walked.push(cid) + } + + const leaves = walked.filter((cid) => !cid.equals(root)) + assert.ok(leaves.length > 0, 'fixture must span more than one block') + + assert.equal(await isDirectlyPinned(node, root), true) + assert.equal(await isDirectlyPinned(node, leaves[0]), false) + // The leaf carries no pin of its own, yet collection must keep it + assert.equal(await isProtected(node, leaves[0]), true) + }) +}) + +describe('upload session cleanup', () => { + it('removes the blocks a rejected upload created', async () => { + const session = createSession() + const content = deterministicBytes(2048, 'rejected') + const cid = await unixfs({ blockstore: session.blockstore }).addBytes(content) + + assert.equal(session.bytesWritten > 0, true) + assert.equal(await session.cleanup(), 1) + assert.equal(await blockstore.has(cid), false) + }) + + it('keeps the blocks of a committed upload', async () => { + const session = createSession() + const cid = await unixfs({ blockstore: session.blockstore }).addBytes( + deterministicBytes(2048, 'committed') + ) + + session.commit() + + assert.equal(await session.cleanup(), 0) + assert.equal(await blockstore.has(cid), true) + }) + + it('leaves blocks that already existed before the upload', async () => { + const content = deterministicBytes(2048, 'pre-existing') + const cid = await ifs.addBytes(content) + + const session = createSession() + await unixfs({ blockstore: session.blockstore }).addBytes(content) + + assert.equal(session.bytesWritten, 0) + assert.equal(await session.cleanup(), 0) + assert.equal(await blockstore.has(cid), true) + }) + + it('leaves blocks that became pinned in the meantime', async () => { + const session = createSession() + const cid = await unixfs({ blockstore: session.blockstore }).addBytes( + deterministicBytes(2048, 'pinned-late') + ) + await pinFile(node, cid) + + assert.equal(await session.cleanup(), 0) + assert.equal(await blockstore.has(cid), true) + }) + + it('does not delete blocks a concurrent upload still writes', async () => { + const content = deterministicBytes(2048, 'shared') + const failing = createSession() + const succeeding = createSession() + + const cid = await unixfs({ blockstore: failing.blockstore }).addBytes(content) + await unixfs({ blockstore: succeeding.blockstore }).addBytes(content) + + assert.equal(await failing.cleanup(), 0) + assert.equal(await blockstore.has(cid), true) + + succeeding.commit() + assert.equal(await blockstore.has(cid), true) + }) + + it('stops a request once the aggregate budget is exhausted', () => { + // The budget meters the multipart stream, so it is charged by the storage + // engine rather than by an in-memory import. + const session = createSession(1024) + + session.budget.consume(1000) + assert.throws(() => session.budget.consume(100), /Request size limit exceeded/) + }) +}) + +describe('garbage collection against fixture data', () => { + /** + * One confirmed file, one abandoned upload, one live upload, and a block that + * no pin protects, standing in for content cached while serving a peer. + */ + async function createFixture(): Promise<{ + registry: FileRegistry + cids: Record + }> { + const registry = createRegistry() + const cids: Record = {} + + const add = async ( + label: string, + state: FileRecord['state'], + expiresAt: number | null + ): Promise => { + const cid = await ifs.addBytes(deterministicBytes(1024, `${label}-${prefixCounter}`)) + await pinFile(node, cid) + await registry.save(record({ cid: cid.toString(), state, expiresAt, storedBytes: 1024 })) + cids[label] = cid.toString() + } + + await add('confirmed', 'confirmed', null) + await add('abandoned', 'temporary', 500) + await add('live', 'temporary', 9_000_000_000_000) + + const cached = await ifs.addBytes(deterministicBytes(1024, `cached-${prefixCounter}`)) + cids.cached = cached.toString() + + return { registry, cids } + } + + it('reports the plan without touching anything in a dry run', async () => { + const { registry, cids } = await createFixture() + const before = await storedDigests() + + const report = await runGarbageCollection({ + node, + registry, + watermarks: WATERMARKS, + blockstoreBytes: 4096, + dryRun: true, + now: 1000 + }) + + assert.equal(report.collected, false) + assert.deepEqual(report.releasedCids, [cids.abandoned]) + assert.deepEqual(report.repairedPins, []) + assert.deepEqual([...(await storedDigests())].sort(), [...before].sort()) + assert.equal((await registry.get(cids.abandoned))?.state, 'temporary') + }) + + it('removes exactly the abandoned upload and the unpinned cache', async () => { + const { registry, cids } = await createFixture() + + const report = await runGarbageCollection({ + node, + registry, + watermarks: WATERMARKS, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.equal(report.collected, true) + assert.deepEqual(report.releasedCids, [cids.abandoned]) + assert.deepEqual(report.retainedCids.sort(), [cids.confirmed, cids.live].sort()) + assert.deepEqual(report.errors, []) + + const remaining = await storedDigests() + assert.equal(remaining.has(digestOf(cids.confirmed)), true) + assert.equal(remaining.has(digestOf(cids.live)), true) + assert.equal(remaining.has(digestOf(cids.abandoned)), false) + assert.equal(remaining.has(digestOf(cids.cached)), false) + + assert.equal(await registry.get(cids.abandoned), undefined) + assert.equal((await registry.get(cids.confirmed))?.state, 'confirmed') + }) + + it('evicts a live upload too when the blockstore is above the high watermark', async () => { + const { registry, cids } = await createFixture() + + const report = await runGarbageCollection({ + node, + registry, + watermarks: { highWatermarkBytes: 2048, lowWatermarkBytes: 1024 }, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.deepEqual(report.releasedCids.sort(), [cids.abandoned, cids.live].sort()) + assert.deepEqual(report.retainedCids, [cids.confirmed]) + + const remaining = await storedDigests() + assert.equal(remaining.has(digestOf(cids.confirmed)), true) + assert.equal(remaining.has(digestOf(cids.live)), false) + }) + + it('restores a missing pin on confirmed content before deleting anything', async () => { + const { registry, cids } = await createFixture() + await unpinFile(node, CID.parse(cids.confirmed)) + + const report = await runGarbageCollection({ + node, + registry, + watermarks: WATERMARKS, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.deepEqual(report.repairedPins, [cids.confirmed]) + assert.equal(await isProtected(node, CID.parse(cids.confirmed)), true) + assert.equal((await storedDigests()).has(digestOf(cids.confirmed)), true) + }) +}) diff --git a/test/replication.test.ts b/test/replication.test.ts new file mode 100644 index 0000000..07e67a5 --- /dev/null +++ b/test/replication.test.ts @@ -0,0 +1,111 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import type { ReplicationConfig } from '../src/storage/config.js' +import { missingReplicas, replicate, selectUnderReplicated } from '../src/storage/replication.js' + +const baseConfig: ReplicationConfig = { + enabled: true, + factor: 3, + ackQuorum: 2, + requireQuorumOnUpload: false, + requestTimeoutMs: 1000, + repairEnabled: true, + repairSchedule: '0 * * * * *', + token: 'r'.repeat(64) +} + +const targets = [ + { name: 'ipfs2', apiUrl: 'https://ipfs2.example' }, + { name: 'ipfs3', apiUrl: 'https://ipfs3.example' } +] + +describe('replicate', () => { + it('reports best effort storage when replication is disabled', async () => { + const report = await replicate({ + cid: 'bafkrei1', + targets, + config: { ...baseConfig, enabled: false }, + token: baseConfig.token, + request: async () => assert.fail('peers must not be contacted') + }) + + assert.equal(report.mode, 'best-effort') + assert.equal(report.acknowledged, 1) + assert.equal(report.satisfied, true) + }) + + it('counts the local copy towards the quorum', async () => { + const report = await replicate({ + cid: 'bafkrei1', + targets, + config: baseConfig, + token: baseConfig.token, + request: async (target) => { + if (target.name === 'ipfs3') { + throw new Error('unreachable') + } + } + }) + + assert.equal(report.acknowledged, 2) + assert.deepEqual(report.replicas, ['ipfs2']) + assert.equal(report.satisfied, true) + }) + + it('reports an unsatisfied quorum when too few peers acknowledge', async () => { + const report = await replicate({ + cid: 'bafkrei1', + targets, + config: baseConfig, + token: baseConfig.token, + request: async () => { + throw new Error('unreachable') + } + }) + + assert.equal(report.acknowledged, 1) + assert.equal(report.satisfied, false) + assert.deepEqual( + report.attempts.map((attempt) => attempt.ok), + [false, false] + ) + }) + + it('survives a node that is offline', async () => { + const report = await replicate({ + cid: 'bafkrei1', + targets, + config: { ...baseConfig, factor: 2, ackQuorum: 1 }, + token: baseConfig.token, + request: async (target) => { + if (target.name === 'ipfs2') { + throw new Error('connection refused') + } + } + }) + + assert.equal(report.satisfied, true) + assert.deepEqual(report.replicas, ['ipfs3']) + }) +}) + +describe('under-replication detection', () => { + it('counts the copies still missing, including the local one', () => { + assert.equal(missingReplicas(baseConfig, []), 2) + assert.equal(missingReplicas(baseConfig, ['ipfs2']), 1) + assert.equal(missingReplicas(baseConfig, ['ipfs2', 'ipfs3']), 0) + }) + + it('selects only the files below the replication factor', () => { + const records = [ + { cid: 'a', replicas: ['ipfs2', 'ipfs3'] }, + { cid: 'b', replicas: ['ipfs2'] }, + { cid: 'c', replicas: [] } + ] + + assert.deepEqual( + selectUnderReplicated(records, baseConfig).map((item) => item.cid), + ['b', 'c'] + ) + }) +}) diff --git a/test/security.test.ts b/test/security.test.ts index d9dc680..e3de6f8 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -10,6 +10,7 @@ import { validateSecurityConfig } from '../src/security/config.js' import { createCorsOriginDelegate, createOriginMatcher } from '../src/security/cors.js' import { getPublicError, InvalidRequestError } from '../src/security/errors.js' import { createRateLimiter } from '../src/security/rateLimit.js' +import { createReplicationAuth } from '../src/security/replicationToken.js' import { parseTrustProxy } from '../src/security/trustProxy.js' import { createMultipartLimits } from '../src/security/uploadLimits.js' import { FileNotFoundError } from '../src/utils/fileErrors.js' @@ -200,12 +201,14 @@ describe('administrative API key', () => { describe('route access policy', () => { const key = 'b'.repeat(64) + const replicationToken = 'c'.repeat(64) const app = express() let serverUrl = '' let closeServer: (() => Promise) | undefined before(async () => { const file = Router().get('/test', (req, res) => res.send({ public: true })) + const fileAdminRouter = Router().post('/test/confirm', (req, res) => res.send({ admin: true })) const publicNodeRouter = Router().get('/health', (req, res) => res.send({ public: true })) const node = Router() .get('/info', (req, res) => res.send({ admin: true })) @@ -213,12 +216,27 @@ describe('route access policy', () => { const helia = Router().get('/test', (req, res) => res.send({ admin: true })) const libp2p = Router().get('/test', (req, res) => res.send({ admin: true })) const debug = Router().get('/test', (req, res) => res.send({ admin: true })) + const storage = Router().get('/metrics', (req, res) => res.send({ public: true })) + const storageAdminRouter = Router().post('/gc', (req, res) => res.send({ admin: true })) + const replication = Router().post('/test', (req, res) => res.send({ peer: true })) mountApiRoutes( app, - { file, publicNodeRouter, node, helia, libp2p, debug }, + { + file, + fileAdminRouter, + publicNodeRouter, + node, + helia, + libp2p, + debug, + storage, + storageAdminRouter, + replication + }, createApiKeyAuth(key), - false + false, + createReplicationAuth(replicationToken) ) const server = await startServer(app) @@ -228,9 +246,37 @@ describe('route access policy', () => { after(async () => closeServer?.()) - it('keeps health and file transfer public', async () => { + it('keeps health, file transfer, and the storage report public', async () => { assert.equal((await fetch(`${serverUrl}/api/node/health`)).status, 200) assert.equal((await fetch(`${serverUrl}/api/file/test`)).status, 200) + assert.equal((await fetch(`${serverUrl}/api/storage/metrics`)).status, 200) + }) + + it('protects the routes that make content durable or reclaim it', async () => { + for (const path of ['/api/file/test/confirm', '/api/storage/gc']) { + assert.equal((await fetch(`${serverUrl}${path}`, { method: 'POST' })).status, 401) + assert.equal( + (await fetch(`${serverUrl}${path}`, { method: 'POST', headers: { 'x-api-key': key } })) + .status, + 200 + ) + } + }) + + it('accepts replication only with the peer token, not with the admin key', async () => { + const path = `${serverUrl}/api/replication/test` + + assert.equal((await fetch(path, { method: 'POST' })).status, 401) + assert.equal((await fetch(path, { method: 'POST', headers: { 'x-api-key': key } })).status, 401) + assert.equal( + ( + await fetch(path, { + method: 'POST', + headers: { 'x-replication-token': replicationToken } + }) + ).status, + 200 + ) }) it('protects node information, Helia, and libp2p routes', async () => { @@ -260,14 +306,19 @@ describe('route access policy', () => { debugApp, { file: emptyRouter, + fileAdminRouter: emptyRouter, publicNodeRouter: emptyRouter, node: emptyRouter, helia: emptyRouter, libp2p: emptyRouter, - debug + debug, + storage: emptyRouter, + storageAdminRouter: emptyRouter, + replication: emptyRouter }, createApiKeyAuth(key), - true + true, + createReplicationAuth('') ) const server = await startServer(debugApp) diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts new file mode 100644 index 0000000..824f60b --- /dev/null +++ b/test/storageConfig.test.ts @@ -0,0 +1,95 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { + DEFAULT_REPLICATION_CONFIG, + DEFAULT_STORAGE_CONFIG, + resolveReplicationConfig, + resolveStorageConfig +} from '../src/storage/config.js' + +const UPLOAD_LIMIT = 1024 + +describe('resolveStorageConfig', () => { + it('applies every default when the section is absent', () => { + assert.deepEqual(resolveStorageConfig(undefined, UPLOAD_LIMIT), DEFAULT_STORAGE_CONFIG) + }) + + it('keeps operator values and fills in the rest', () => { + const storage = resolveStorageConfig( + { diskReserveBytes: 1, gc: { enabled: true } }, + UPLOAD_LIMIT + ) + + assert.equal(storage.diskReserveBytes, 1) + assert.equal(storage.gc.enabled, true) + assert.equal(storage.gc.schedule, DEFAULT_STORAGE_CONFIG.gc.schedule) + assert.equal(storage.temporaryTtlMs, DEFAULT_STORAGE_CONFIG.temporaryTtlMs) + }) + + it('rejects a low watermark the collector could never reach', () => { + assert.throws( + () => + resolveStorageConfig( + { gc: { highWatermarkBytes: 100, lowWatermarkBytes: 100 } }, + UPLOAD_LIMIT + ), + /lowWatermarkBytes/ + ) + }) + + it('rejects an aggregate limit smaller than the per-file limit', () => { + assert.throws( + () => resolveStorageConfig({ maxRequestSizeBytes: 512 }, UPLOAD_LIMIT), + /maxRequestSizeBytes/ + ) + }) + + it('rejects limits that would disable a guarantee', () => { + assert.throws(() => resolveStorageConfig({ maxConcurrentUploads: 0 }, UPLOAD_LIMIT)) + assert.throws(() => resolveStorageConfig({ temporaryTtlMs: 0 }, UPLOAD_LIMIT)) + assert.throws(() => resolveStorageConfig({ diskReserveBytes: -1 }, UPLOAD_LIMIT)) + assert.throws(() => resolveStorageConfig({ confirmationRequired: 'yes' }, UPLOAD_LIMIT)) + }) + + it('accepts a zero disk reserve, which only disables the reserve check', () => { + assert.equal(resolveStorageConfig({ diskReserveBytes: 0 }, UPLOAD_LIMIT).diskReserveBytes, 0) + }) +}) + +describe('resolveReplicationConfig', () => { + const token = 'r'.repeat(64) + + it('defaults to disabled best-effort storage', () => { + assert.deepEqual(resolveReplicationConfig(undefined), DEFAULT_REPLICATION_CONFIG) + assert.equal(DEFAULT_REPLICATION_CONFIG.enabled, false) + }) + + it('rejects a quorum larger than the replication factor', () => { + assert.throws( + () => resolveReplicationConfig({ enabled: true, factor: 2, ackQuorum: 3, token }), + /ackQuorum/ + ) + }) + + it('rejects enabling replication without a peer token', () => { + assert.throws(() => resolveReplicationConfig({ enabled: true }), /replication.token/) + assert.throws( + () => resolveReplicationConfig({ enabled: true, token: 'short' }), + /replication.token/ + ) + }) + + it('accepts a complete policy', () => { + const replication = resolveReplicationConfig({ + enabled: true, + factor: 3, + ackQuorum: 2, + requireQuorumOnUpload: true, + token + }) + + assert.equal(replication.factor, 3) + assert.equal(replication.ackQuorum, 2) + assert.equal(replication.requireQuorumOnUpload, true) + }) +}) diff --git a/test/storageLimits.test.ts b/test/storageLimits.test.ts new file mode 100644 index 0000000..fc6773c --- /dev/null +++ b/test/storageLimits.test.ts @@ -0,0 +1,113 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { + checkDiskReserve, + checkRequestSize, + ConcurrencyLimiter, + parseContentLength, + RequestSizeBudget, + RequestSizeLimitError +} from '../src/storage/limits.js' + +describe('ConcurrencyLimiter', () => { + it('admits uploads up to the configured limit', () => { + const limiter = new ConcurrencyLimiter(2) + + assert.equal(limiter.tryAcquire(), true) + assert.equal(limiter.tryAcquire(), true) + assert.equal(limiter.tryAcquire(), false) + assert.equal(limiter.active, 2) + }) + + it('admits a new upload once a slot is released', () => { + const limiter = new ConcurrencyLimiter(1) + + assert.equal(limiter.tryAcquire(), true) + assert.equal(limiter.tryAcquire(), false) + + limiter.release() + assert.equal(limiter.tryAcquire(), true) + }) + + it('ignores a release without a matching acquire', () => { + const limiter = new ConcurrencyLimiter(1) + + limiter.release() + assert.equal(limiter.active, 0) + assert.equal(limiter.tryAcquire(), true) + }) +}) + +describe('parseContentLength', () => { + it('reads a declared body size', () => { + assert.equal(parseContentLength('1024'), 1024) + }) + + it('returns undefined for a chunked or malformed request', () => { + assert.equal(parseContentLength(undefined), undefined) + assert.equal(parseContentLength('not-a-number'), undefined) + assert.equal(parseContentLength('-1'), undefined) + }) +}) + +describe('checkRequestSize', () => { + it('rejects a request larger than the aggregate limit', () => { + const result = checkRequestSize(2048, 1024) + + assert.equal(result.allowed, false) + }) + + it('accepts a request at the limit', () => { + assert.equal(checkRequestSize(1024, 1024).allowed, true) + }) + + it('accepts a request without a declared size', () => { + assert.equal(checkRequestSize(undefined, 1024).allowed, true) + }) +}) + +describe('checkDiskReserve', () => { + it('accepts an upload that leaves the reserve intact', () => { + const result = checkDiskReserve({ + availableBytes: 1000, + reserveBytes: 100, + requestedBytes: 900 + }) + + assert.equal(result.allowed, true) + }) + + it('rejects an upload that would consume the reserve', () => { + const result = checkDiskReserve({ + availableBytes: 1000, + reserveBytes: 100, + requestedBytes: 901 + }) + + assert.equal(result.allowed, false) + }) + + it('rejects any upload once free space is already below the reserve', () => { + const result = checkDiskReserve({ availableBytes: 50, reserveBytes: 100 }) + + assert.equal(result.allowed, false) + }) +}) + +describe('RequestSizeBudget', () => { + it('accumulates streamed bytes across the parts of one request', () => { + const budget = new RequestSizeBudget(10) + + budget.consume(4) + budget.consume(6) + + assert.equal(budget.used, 10) + }) + + it('throws once the aggregate limit is passed', () => { + const budget = new RequestSizeBudget(10) + + budget.consume(9) + assert.throws(() => budget.consume(2), RequestSizeLimitError) + }) +}) From 7a557c7703f6f64126171c3e0134bae0515761d9 Mon Sep 17 00:00:00 2001 From: adamantmm Date: Sat, 22 Aug 2026 21:26:37 +0300 Subject: [PATCH 103/167] refactor(storage): place copies over libp2p by file age The replication control plane ran over the REST API, which forced every peer to publish an `apiUrl` and to share a `replication.token`. A shared secret cannot be handed to an arbitrary participant without ceasing to be a secret, so that design could never open the network to third-party nodes. Replication now runs on the libp2p protocol `/adamant/replication/1.0.0`. The handshake already proves the calling peer id, so no secret is distributed, no HTTP address is published, and no second port is exposed. Which peers may spend this node's disk becomes a policy rather than a credential. Copies no longer go to every peer. Holders are derived from the CID with rendezvous hashing, so every node computes the same set without coordinating, and a node joining or leaving moves only the CIDs it wins or loses. How many holders a file gets depends on its age through `replication.placement`: four while fresh, three past 180 days, two past a year. Age is used rather than time of last access, which would record when users read their files. Collection gains a handover step: a node outside a file's designated holders releases its copy once every designated holder confirms over the protocol that it has the file. The designated set is identical on every node and its members never consider releasing, so two nodes cannot drop the last two copies at the same moment without any locking between them. Nothing is released while the network is no larger than the desired copy count. Co-Authored-By: Claude Opus 5 --- README.md | 39 ++-- config.default.json5 | 19 +- config.test.json5 | 8 +- docs/storage-lifecycle.md | 132 +++++++++--- src/api/file.ts | 2 +- src/api/index.ts | 2 - src/api/replication.ts | 29 --- src/api/storage.ts | 6 +- src/config.ts | 33 +-- src/gc.cron.ts | 24 ++- src/index.ts | 18 +- src/replication.cron.ts | 2 +- src/security/accessPolicy.ts | 12 +- src/security/replicationToken.ts | 43 ---- src/storage/config.ts | 91 ++++++--- src/storage/gc.ts | 10 +- src/storage/placement.ts | 145 ++++++++++++++ src/storage/registry.ts | 32 ++- src/storage/replication.ts | 135 +++++++------ src/storage/replicationProtocol.ts | 199 +++++++++++++++++++ src/storage/service.ts | 195 +++++++++++++++--- src/utils/types.ts | 3 - src/utils/utils.ts | 3 +- test/fileRegistry.test.ts | 1 + test/garbageCollectionPlan.test.ts | 1 + test/integration/replicationProtocol.test.ts | 155 +++++++++++++++ test/integration/storageLifecycle.test.ts | 1 + test/placement.test.ts | 138 +++++++++++++ test/replication.test.ts | 188 ++++++++++++------ test/security.test.ts | 46 +---- test/storageConfig.test.ts | 56 ++++-- 31 files changed, 1330 insertions(+), 438 deletions(-) delete mode 100644 src/api/replication.ts delete mode 100644 src/security/replicationToken.ts create mode 100644 src/storage/placement.ts create mode 100644 src/storage/replicationProtocol.ts create mode 100644 test/integration/replicationProtocol.test.ts create mode 100644 test/placement.test.ts diff --git a/README.md b/README.md index 89a4327..d2db2e3 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,16 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, }, "replication": { "enabled": false, - "factor": 2, + "placement": [ + { "minAgeMs": 0, "copies": 4 }, + { "minAgeMs": 15552000000, "copies": 3 }, + { "minAgeMs": 31536000000, "copies": 2 } + ], "ackQuorum": 1, "requireQuorumOnUpload": false, "requestTimeoutMs": 30000, "repairEnabled": true, - "repairSchedule": "0 */30 * * * *", - "token": "" + "repairSchedule": "0 */30 * * * *" } } ``` @@ -104,22 +107,22 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - Tune the endpoint-specific `rateLimits` for the deployment perimeter - Review `storage.diskReserveBytes` and `storage.maxRequestSizeBytes` for the deployment volume; the defaults suit a dedicated disk - Leave `storage.gc.enabled` as `false` until a deletion policy is agreed; `POST /api/storage/gc` runs on demand meanwhile -- Set `replication.token` on every node that should accept copies, and `replication.enabled` on every node that should push them -- Add `apiUrl` to each entry of `nodes` that takes part in replication +- Set `replication.enabled` on every node that should place copies; replication needs no key and no extra address, because it runs on a libp2p protocol between the peers already listed in `nodes` +- Tune `replication.placement` if the deployment wants a different number of copies per file age Invalid CORS, proxy, API-key, or rate-limit configuration stops the process instead of silently weakening the boundary. ## HTTP access policy -| Class | Routes | Policy | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | -| Public | `GET /`, `GET /api/node/health` | No authentication | -| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | -| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | -| Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | -| Peer replication | `POST /api/replication/:cid` | A matching `x-replication-token` header is required; the admin key is not accepted | -| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | -| Authenticated user | None | The service has no end-user identity or session layer | +| Class | Routes | Policy | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | +| Public | `GET /`, `GET /api/node/health` | No authentication | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | +| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | +| Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | +| Peer replication | libp2p `/adamant/replication/1.0.0`, not an HTTP route | Authenticated by the libp2p handshake; accepted only from the peers listed in `nodes` | +| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | +| Authenticated user | None | The service has no end-user identity or session layer | Administrative coverage includes pin operations, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. @@ -256,11 +259,13 @@ Example response: ], "replication": { "mode": "best-effort", - "factor": 1, + "desiredCopies": 1, + "copies": 1, "required": 1, "acknowledged": 1, "replicas": [], "satisfied": true, + "networkTooSmall": true, "attempts": [] } } @@ -294,7 +299,7 @@ Example response: "createdAt": 1720614998797, "expiresAt": null, "confirmedAt": 1720614998797, - "replication": { "acknowledged": 1, "required": 1, "factor": 1 } + "replication": { "acknowledged": 1, "required": 1, "heldLocally": true } } ``` @@ -360,7 +365,7 @@ curl --fail-with-body --request POST \ 'https://ipfs.example.org/api/storage/gc?dryRun=true' ``` -`POST /api/storage/repair` pushes copies of any confirmed file that holds fewer than `replication.factor` of them. +`POST /api/storage/repair` places copies of any confirmed file whose designated holders have not all acknowledged. A collection pass also hands over local copies of files that belong on other nodes, once those nodes confirm they hold them; the report lists them under `demoted`. ## Validation diff --git a/config.default.json5 b/config.default.json5 index c51d247..242f9f4 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -3,7 +3,6 @@ { name: 'ipfs1', multiAddr: '/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq' - // apiUrl: 'https://ipfs1.example', // Only needed when replication is enabled }, { name: 'ipfs2', @@ -71,18 +70,24 @@ lowWatermarkBytes: 42949672960 // 40 GiB. Collection reclaims down to this size } }, - // Replication across the nodes listed in `nodes` that declare an `apiUrl`. + // Replication across the nodes listed in `nodes`. Copies travel over libp2p, + // so a peer needs no HTTP address and no shared secret: the handshake already + // proves which node is calling. // Disabled by default: the node then stores content best effort, locally only. replication: { enabled: false, - factor: 2, // Copies that must exist, including this node + // How many nodes should hold a file, including this one, by file age. + // Copies shrink as content ages instead of being tracked by access time, + // which would record when users fetch their files. + placement: [ + { minAgeMs: 0, copies: 4 }, // fresh + { minAgeMs: 15552000000, copies: 3 }, // older than 180 days + { minAgeMs: 31536000000, copies: 2 } // older than 365 days + ], ackQuorum: 1, // Acknowledgements required before an upload is durable requireQuorumOnUpload: false, // true rejects an upload that cannot reach the quorum requestTimeoutMs: 30000, repairEnabled: true, - repairSchedule: '0 */30 * * * *', - // Shared by all ADAMANT nodes, sent as x-replication-token. - // Generate with: openssl rand -hex 32 - token: '' + repairSchedule: '0 */30 * * * *' } } diff --git a/config.test.json5 b/config.test.json5 index 7cc611c..cebd636 100644 --- a/config.test.json5 +++ b/config.test.json5 @@ -40,12 +40,14 @@ }, replication: { enabled: false, - factor: 2, + placement: [ + { minAgeMs: 0, copies: 3 }, + { minAgeMs: 31536000000, copies: 2 } + ], ackQuorum: 1, requireQuorumOnUpload: false, requestTimeoutMs: 5000, repairEnabled: false, - repairSchedule: '0 */30 * * * *', - token: '' + repairSchedule: '0 */30 * * * *' } } diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index bf51c2f..050593a 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -90,16 +90,19 @@ The guard fails closed. With no key configured the routes answer `503` rather than being open, so a node cannot be filled with permanently pinned content by an anonymous caller. -`POST /api/replication/:cid` is different: it is authorized by -`replication.token`, a secret shared by the ADAMANT nodes. A peer only ever asks -this node to store a copy, so distributing the administrative key across the node -set would grant far more than replication needs. Setting the token opens the -intake route on its own; `replication.enabled` only governs whether this node -pushes copies out. +Replication between nodes is not an HTTP route and uses no key at all. It runs +on the libp2p protocol `/adamant/replication/1.0.0`, where the handshake already +proves the calling peer id cryptographically. Nothing has to be distributed, and +a peer needs no HTTP address to be reachable. + +Which peers may spend this node's disk is a policy, not a credential: today the +node accepts requests from the peers listed in `nodes`. That is still a closed +set, and it is the piece an uploader signature replaces so that anyone can run a +node without being handed a secret. ## Garbage collection -Collection applies two independent rules: +Collection applies three independent rules: 1. **Expiry.** A `temporary` file that outlived `expiresAt` is released, whatever the blockstore size is. @@ -107,6 +110,10 @@ Collection applies two independent rules: `storage.gc.highWatermarkBytes`, the oldest unconfirmed files are released until the estimated size drops below `storage.gc.lowWatermarkBytes`. The gap between the two thresholds stops the collector from running on every tick. +3. **Handover.** A confirmed file whose copies belong on other nodes is released + here once those nodes confirm they hold it. This is how an ageing file drops + from four holders to two without ever losing durability; see + [Replication and durability](#replication-and-durability). Released files lose their pin, then Helia deletes every unpinned block. That pass also reclaims blocks cached while serving other peers. @@ -136,20 +143,79 @@ Schedule it accordingly. node holds it. `GET /api/storage/policy` reports `"mode": "best-effort"` so clients can see what they are getting. -With replication enabled, a node that makes a file durable pushes it to the other -ADAMANT nodes listed in `nodes` that declare an `apiUrl`. The peer pulls the DAG -over libp2p and pins it before answering, so an acknowledgement means the copy -exists and is protected. +With replication enabled, a file is placed on a subset of the ADAMANT nodes. The +peer pulls the DAG over libp2p and pins it before answering, so an +acknowledgement means the copy exists and is protected there, not that a request +was queued. + +### How many copies, and where + +Copies are **not** sent to every node. On a large node set that would cost +bandwidth and disk everywhere for no added durability. Copies are also not sent +to a random subset, because then no node could tell where a file is supposed to +live, and both repair and handover would need a network-wide search. + +Instead the holders are derived from the CID with rendezvous hashing: each +candidate node is scored by `sha256(peerId ‖ cid)` and the highest scores win. +Two properties follow, and the rest of the design depends on both: + +- Every node computes the same holders for a CID without asking anyone, so the + set is agreed by construction rather than negotiated. +- Adding or removing a node moves only the fraction of CIDs it wins or loses, + instead of reshuffling all of them the way a modulo assignment would. + +How many holders a file gets depends on its **age**, through +`replication.placement`: + +| File age | Holders, including this node | +| ------------------- | ---------------------------- | +| Fresh | 4 | +| Older than 180 days | 3 | +| Older than 365 days | 2 | + +Age is used rather than time of last access on purpose. Recording when a file +was last read would build a log of user activity, and sharing that log between +nodes so they could agree on it would spread the leak further. Creation time is +already implied by the upload, so it reveals nothing new. + +When the network is no larger than the desired copy count, every node is +expected to hold the file: no copy requests are sent, because there is nowhere +for a copy to go, and nothing is ever handed over. -- `replication.factor` — copies that must exist, including this node -- `replication.ackQuorum` — acknowledgements required for an upload to be durable -- `replication.requireQuorumOnUpload` — when `true`, an upload that cannot reach - the quorum is released and answered with `503`; when `false` (default) the - upload succeeds, reports the shortfall, and the repair job converges later +### Handing a copy over + +As a file ages its desired holder count shrinks, so nodes that are no longer +designated may release their copy. A node releases one only when **both** hold: + +- it is outside the designated set for that CID, and +- every designated holder confirms over the protocol that it has the file. + +The designated set is identical on every node, and a designated holder never +considers releasing. That is what prevents two nodes from dropping the last two +copies at the same moment, with no locking between them. If a designated holder +does not answer, or answers that it does not have the file, the copy stays where +it is and repair puts it where it belongs on the next pass. + +Releasing a copy leaves the registry record in place with `heldLocally: false`: +the file is still durable in the network, this node simply stopped being one of +its holders. + +### Reading a file + +Routing stays the node's problem, not the client's. A client asks whichever node +it likes for a CID; if that node does not hold the file, it fetches the blocks +over bitswap from the peers that do and streams them on. Placement does not +change that contract — it only makes it likely that a connected peer has the +file, and it tells the node which peers to expect it from. + +A node that cannot retrieve content answers `408` after `findFileTimeout`. It +never answers `404`, because it cannot know that content does not exist. + +### Repair The repair job (`replication.repairSchedule`, or `POST /api/storage/repair`) -lists confirmed files holding fewer copies than the factor requires and pushes -the missing ones again. +lists confirmed files this node holds whose designated peers have not all +acknowledged, and places the missing copies again. Replication needs the peers to be connected over libp2p, because the copy itself travels by bitswap. Keep every replication peer in `peerDiscovery.bootstrap` as @@ -160,18 +226,17 @@ well as in `nodes`. Helia has no native pin-orchestration protocol. Cross-node pinning has to come from somewhere, so the two realistic options are: -| | Helia with an explicit control plane (selected) | Kubo with IPFS Cluster | -| ------------------ | -------------------------------------------------------------- | ------------------------------------------ | -| Runtime | The existing Node.js process | A Go daemon plus a cluster daemon per node | -| Pin orchestration | REST calls between ADAMANT nodes, authorized by a shared token | Raft or CRDT consensus inside the cluster | -| Replication factor | Enforced by this node and by the repair job | Enforced by the cluster | -| Operational cost | None beyond the current deployment | New services, new state, new failure modes | -| Fit | Small, fixed, mutually trusted node set | Large or dynamic clusters | - -The ADAMANT node set is small, fixed, and mutually trusted, and the project -already runs a REST API on every node. An explicit control plane covers the -requirement without adding a second runtime, so **Helia-native orchestration is -the selected durability model**. Kubo with IPFS Cluster remains the fallback if +| | Helia with an explicit control plane (selected) | Kubo with IPFS Cluster | +| ----------------- | ----------------------------------------------------------------------- | ------------------------------------------ | +| Runtime | The existing Node.js process | A Go daemon plus a cluster daemon per node | +| Pin orchestration | A libp2p protocol between ADAMANT nodes, authenticated by the handshake | Raft or CRDT consensus inside the cluster | +| Placement | Rendezvous hashing over the CID, computed identically everywhere | Allocation decided by the cluster | +| Operational cost | None beyond the current deployment | New services, new state, new failure modes | +| Fit | Small, fixed, mutually trusted node set | Large or dynamic clusters | + +The ADAMANT nodes already speak libp2p to each other, so the control plane costs +one protocol handler and no new runtime, credential, or port. **Helia-native +orchestration is therefore the selected durability model.** Kubo with IPFS Cluster remains the fallback if the node set grows to a size where consensus-based pin allocation is worth its operational cost. @@ -186,6 +251,9 @@ operational cost. - `reservedBytes` and `usableBytes` — the disk reserve and what is left for uploads - `files` — how many files are in each lifecycle state +A collection report also lists `demoted`: files whose local copy was handed over +to their designated holders during that pass. + Values are refreshed on the `diskUsageScanPeriod` schedule, because a directory scan and a full registry sweep are too expensive for a request path. A subset is also included in `GET /api/node/info`. @@ -225,8 +293,8 @@ Read this before enabling `storage.gc.enabled` in production. ### Operational safeguards - Keep `storage.gc.enabled` false until a deletion policy is agreed -- Keep `replication.factor` at two or more, so a mistaken deletion on one node is - recoverable from another +- Keep the smallest tier of `replication.placement` at two copies or more, so a + mistaken deletion on one node is recoverable from another - Run the dry run again after changing the watermarks or the TTL - Watch `GET /api/storage/metrics` for `gc.lastRun.errors` after each run diff --git a/src/api/file.ts b/src/api/file.ts index a886341..c52c36b 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -133,7 +133,7 @@ router.get('/:cid/status', readLimiter, async (req, res, next) => { replication: { acknowledged: record.replicas.length + 1, required: config.replication.enabled ? config.replication.ackQuorum : 1, - factor: config.replication.enabled ? config.replication.factor : 1 + heldLocally: record.heldLocally } }) } catch (err) { diff --git a/src/api/index.ts b/src/api/index.ts index 7a1187b..5a10a20 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,7 +3,6 @@ import node, { publicNodeRouter } from './node.js' import helia from './helia.js' import libp2p from './libp2p.js' import debug from './debug.js' -import replication from './replication.js' import storage, { storageAdminRouter } from './storage.js' export { @@ -14,7 +13,6 @@ export { helia, libp2p, debug, - replication, storage, storageAdminRouter } diff --git a/src/api/replication.ts b/src/api/replication.ts deleted file mode 100644 index b5448c6..0000000 --- a/src/api/replication.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Router } from 'express' -import { pinLimiter } from '../middleware/rateLimiter.js' -import { acceptReplica } from '../storage/service.js' -import { parseCid } from '../utils/cid.js' -import { logger } from '../utils/logger.js' - -const router = Router() - -/** - * Store a copy on behalf of another ADAMANT node. - * - * The DAG is pulled over libp2p and pinned before the response is sent, so a - * `200` means the copy is durable here. Access is granted by the shared - * replication token, not by the administrative key; see `accessPolicy.ts`. - */ -router.post('/:cid', pinLimiter, async (req, res) => { - const cid = parseCid(req.params.cid) - - try { - const record = await acceptReplica(cid.toString()) - res.send({ cid: record.cid, state: record.state, storedBytes: record.storedBytes }) - } catch (err) { - // A peer that cannot serve the DAG is an expected outcome, not a node fault. - logger.warn(`Replication of ${cid.toString()} failed: ${(err as Error).message}`) - res.status(502).send({ error: 'Unable to store the replica' }) - } -}) - -export default router diff --git a/src/api/storage.ts b/src/api/storage.ts index 5332bad..7ce657c 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -46,10 +46,11 @@ router.get('/policy', readLimiter, (req, res) => { durability: config.replication.enabled ? { mode: 'quorum', - factor: config.replication.factor, + // Copies are reduced as a file ages; the tiers are the whole policy. + placement: config.replication.placement, ackQuorum: config.replication.ackQuorum } - : { mode: 'best-effort', factor: 1, ackQuorum: 1 } + : { mode: 'best-effort', placement: [{ minAgeMs: 0, copies: 1 }], ackQuorum: 1 } }) }) @@ -81,6 +82,7 @@ storageAdminRouter.post('/gc', async (req, res, next) => { estimatedBytesAfter: report.estimatedBytesAfter, releasedCids: report.releasedCids, retainedCids: report.retainedCids, + demoted: report.demoted, removedBlocks: report.removedBlocks, removedCids: report.removedCids, repairedPins: report.repairedPins, diff --git a/src/config.ts b/src/config.ts index 587030c..bac457e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -46,11 +46,6 @@ export type LogLevel = (typeof LOG_LEVELS)[number] export interface ConfigNode { name: string multiAddr: string - /** - * Base URL of the node REST API, for example `https://ipfs1.adamant.im`. - * Only nodes that take part in replication need one. - */ - apiUrl?: string } export interface Config { @@ -138,28 +133,6 @@ function requireStringArray(value: unknown, path: string): string[] { return value.map((item, index) => requireString(item, `${path}[${index}]`)) } -/** - * Validate an HTTP(S) base URL used to reach another node's REST API. - * - * Only the scheme and syntax are checked; reachability is a runtime concern. - */ -function requireHttpUrl(value: unknown, path: string): string { - const raw = requireString(value, path) - - let url: URL - try { - url = new URL(raw) - } catch { - fail(path, 'must be an absolute http(s) URL') - } - - if (url.protocol !== 'http:' && url.protocol !== 'https:') { - fail(path, 'must use the http or https scheme') - } - - return raw -} - /** * Validate a positive integer. * @@ -195,11 +168,7 @@ export function validateConfig(raw: unknown): Config { const entry = requireObject(node, `nodes[${index}]`) return { name: requireString(entry.name, `nodes[${index}].name`), - multiAddr: requireString(entry.multiAddr, `nodes[${index}].multiAddr`), - apiUrl: - entry.apiUrl === undefined - ? undefined - : requireHttpUrl(entry.apiUrl, `nodes[${index}].apiUrl`) + multiAddr: requireString(entry.multiAddr, `nodes[${index}].multiAddr`) } }) diff --git a/src/gc.cron.ts b/src/gc.cron.ts index 6bedeca..e692f64 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -3,11 +3,18 @@ import { config } from './config.js' import { helia } from './helia.js' import { runGarbageCollection, type GcReport } from './storage/gc.js' import { refreshStorageMetrics } from './storage/metrics.js' +import { demoteReleasableCopies } from './storage/service.js' import { fileRegistry } from './storage/state.js' import { logger } from './utils/logger.js' +/** A collection pass, plus the copies it handed over to other nodes first. */ +export interface CollectionReport extends GcReport { + /** Files whose local copy was released because peers hold enough copies. */ + demoted: string[] +} + let running = false -let lastReport: GcReport | null = null +let lastReport: CollectionReport | null = null export class GarbageCollectionBusyError extends Error { constructor() { @@ -27,13 +34,17 @@ export class GarbageCollectionBusyError extends Error { */ export async function collectGarbage( options: { dryRun?: boolean; force?: boolean } = {} -): Promise { +): Promise { if (running) { throw new GarbageCollectionBusyError() } running = true try { + // Hand copies over before collecting, so the blocks they free are reclaimed + // by the same pass instead of waiting for the next one. + const demoted = options.dryRun === true ? [] : (await demoteReleasableCopies()).demoted + const metrics = await refreshStorageMetrics() const report = await runGarbageCollection({ @@ -47,12 +58,14 @@ export async function collectGarbage( log: (message) => logger.info(message) }) - if (!report.dryRun) { - lastReport = report + const collection: CollectionReport = { ...report, demoted } + + if (!collection.dryRun) { + lastReport = collection await refreshStorageMetrics() } - return report + return collection } finally { running = false } @@ -80,6 +93,7 @@ export function getGarbageCollectionState() { durationMs: lastReport.durationMs, collected: lastReport.collected, releasedFiles: lastReport.releasedCids.length, + demotedFiles: lastReport.demoted.length, removedBlocks: lastReport.removedBlocks, repairedPins: lastReport.repairedPins.length, errors: lastReport.errors.length diff --git a/src/index.ts b/src/index.ts index 65228aa..8ac6709 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,9 @@ import express from 'express' import { httpLogger, logger } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' +import { helia } from './helia.js' +import { registerReplicationProtocol } from './storage/replicationProtocol.js' +import { createReplicationHandlers } from './storage/service.js' import { garbageCollectionCron } from './gc.cron.js' import { replicationRepairCron } from './replication.cron.js' import cors from 'cors' @@ -9,7 +12,6 @@ import * as routers from './api/index.js' import { errorHandler, notFoundHandler } from './middleware/errorHandler.js' import { mountApiRoutes } from './security/accessPolicy.js' import { createApiKeyAuth } from './security/apiKey.js' -import { createReplicationAuth } from './security/replicationToken.js' import { createCorsOriginDelegate } from './security/cors.js' import { parseTrustProxy } from './security/trustProxy.js' @@ -31,6 +33,10 @@ if (config.storage.gc.enabled) { } if (config.replication.enabled) { + // Answering the protocol is what lets peers place copies here, so it is + // registered before the HTTP server starts accepting uploads. + await registerReplicationProtocol(helia, createReplicationHandlers()) + if (config.replication.repairEnabled) { replicationRepairCron.start() } @@ -67,15 +73,7 @@ app.get('/', (req, res) => { res.send('IPFS node') }) -mountApiRoutes( - app, - routers, - createApiKeyAuth(config.adminApiKey), - config.enableDebugApi === true, - // The token alone opens replication intake. `replication.enabled` governs - // whether this node pushes copies out, which a receive-only node never does. - createReplicationAuth(config.replication.token) -) +mountApiRoutes(app, routers, createApiKeyAuth(config.adminApiKey), config.enableDebugApi === true) app.use(notFoundHandler) app.use(errorHandler) diff --git a/src/replication.cron.ts b/src/replication.cron.ts index d37d296..844315d 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -51,7 +51,7 @@ export const replicationRepairCron = new CronJob(config.replication.repairSchedu export function getReplicationState() { return { enabled: config.replication.enabled, - factor: config.replication.factor, + placement: config.replication.placement, ackQuorum: config.replication.ackQuorum, requireQuorumOnUpload: config.replication.requireQuorumOnUpload, repairEnabled: config.replication.repairEnabled, diff --git a/src/security/accessPolicy.ts b/src/security/accessPolicy.ts index a167314..5546215 100644 --- a/src/security/accessPolicy.ts +++ b/src/security/accessPolicy.ts @@ -13,8 +13,6 @@ export type ApiRouters = { storage: Router /** Storage routes that reclaim space or move copies between nodes. */ storageAdminRouter: Router - /** Peer-to-peer replication intake. */ - replication: Router } /** @@ -25,14 +23,12 @@ export type ApiRouters = { * @param routers API routers without cross-cutting access middleware * @param adminAuth fail-closed administrative authentication middleware * @param enableDebugApi whether the authenticated debug API is mounted - * @param replicationAuth fail-closed peer authentication for replication intake */ export function mountApiRoutes( app: Application, routers: ApiRouters, adminAuth: RequestHandler, - enableDebugApi: boolean, - replicationAuth: RequestHandler + enableDebugApi: boolean ): void { app.use('/api/file', routers.file) app.use('/api/file', adminAuth, routers.fileAdminRouter) @@ -43,9 +39,9 @@ export function mountApiRoutes( app.use('/api/storage', routers.storage) app.use('/api/storage', adminAuth, routers.storageAdminRouter) - // Peers authenticate with the shared replication token rather than the - // administrative key: storing a copy is all a peer may ask this node to do. - app.use('/api/replication', replicationAuth, routers.replication) + // Replication between nodes is not an HTTP route: it runs on the libp2p + // protocol in `src/storage/replicationProtocol.ts`, where the handshake + // already proves which peer is calling. app.use('/api/helia', adminAuth, routers.helia) app.use('/api/libp2p', adminAuth, routers.libp2p) diff --git a/src/security/replicationToken.ts b/src/security/replicationToken.ts deleted file mode 100644 index 7b1d3a7..0000000 --- a/src/security/replicationToken.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { createHmac, timingSafeEqual } from 'node:crypto' -import type { RequestHandler } from 'express' - -/** - * Create fail-closed middleware for peer replication requests. - * - * The token is shared by the ADAMANT nodes and authorizes exactly one - * operation: storing a copy of a CID. It is deliberately separate from - * `adminApiKey`, which would grant a peer far more than replication needs. - * - * @param configuredToken operator secret, or an empty value to refuse replication - * @returns Express middleware that accepts only a matching `x-replication-token` header - */ -export function createReplicationAuth(configuredToken: unknown): RequestHandler { - if (typeof configuredToken !== 'string' || configuredToken.length === 0) { - return (req, res): void => { - res.status(503).send({ error: 'Service not configured' }) - } - } - - const keyBytes = new TextEncoder().encode(configuredToken) - const configuredDigest = digest(configuredToken, keyBytes) - - return (req, res, next): void => { - const provided = req.headers['x-replication-token'] - if (typeof provided !== 'string') { - res.status(401).send({ error: 'Unauthorized' }) - return - } - - if (!timingSafeEqual(digest(provided, keyBytes), configuredDigest)) { - res.status(401).send({ error: 'Unauthorized' }) - return - } - - res.set('Cache-Control', 'no-store') - next() - } -} - -function digest(value: string, key: Uint8Array): Uint8Array { - return new Uint8Array(createHmac('sha256', key).update(value, 'utf8').digest()) -} diff --git a/src/storage/config.ts b/src/storage/config.ts index ad4c2c7..2fc4dd9 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -1,5 +1,8 @@ +import type { PlacementTier } from './placement.js' + const MiB = 1024 * 1024 const GiB = 1024 * MiB +const DAY = 24 * 60 * 60 * 1000 /** * Garbage collection thresholds, expressed in blockstore bytes. @@ -34,8 +37,13 @@ export interface StorageConfig { export interface ReplicationConfig { /** When false the node stores content best effort and never contacts peers. */ enabled: boolean - /** Number of ADAMANT nodes that must hold a copy, including this node. */ - factor: number + /** + * How many nodes should hold a file, including this one, by file age. + * + * Copies are reduced as content ages instead of being tracked by access time, + * which would record when users fetch their files. + */ + placement: PlacementTier[] /** Acknowledgements required before an upload is reported as durable. */ ackQuorum: number /** When true an upload fails unless the acknowledgement quorum is reached. */ @@ -44,17 +52,6 @@ export interface ReplicationConfig { requestTimeoutMs: number repairEnabled: boolean repairSchedule: string - /** - * Secret shared by the ADAMANT nodes, sent as `x-replication-token`. - * - * It is deliberately separate from `adminApiKey`: a peer only needs to ask - * this node to store a copy, so distributing the administrative key across - * the node set would grant far more than replication requires. - * - * Setting it opens the replication intake route, independently of `enabled`, - * so a node can accept copies without pushing any of its own. - */ - token: string } export const DEFAULT_STORAGE_CONFIG: StorageConfig = { @@ -73,13 +70,16 @@ export const DEFAULT_STORAGE_CONFIG: StorageConfig = { export const DEFAULT_REPLICATION_CONFIG: ReplicationConfig = { enabled: false, - factor: 2, + placement: [ + { minAgeMs: 0, copies: 4 }, + { minAgeMs: 180 * DAY, copies: 3 }, + { minAgeMs: 365 * DAY, copies: 2 } + ], ackQuorum: 1, requireQuorumOnUpload: false, requestTimeoutMs: 30000, repairEnabled: true, - repairSchedule: '0 */30 * * * *', - token: '' + repairSchedule: '0 */30 * * * *' } function fail(path: string, expectation: string): never { @@ -212,13 +212,52 @@ export function resolveStorageConfig(raw: unknown, uploadLimitSizeBytes: number) * * @param raw Value of the `replication` key from a parsed config file */ +/** + * Validate the placement tiers. + * + * The tiers must start at age zero and never go backwards, so that exactly one + * of them applies to any file and every node reaches the same answer. + */ +function resolvePlacement(raw: unknown, defaults: PlacementTier[]): PlacementTier[] { + if (raw === undefined) { + return defaults.map((tier) => ({ ...tier })) + } + + if (!Array.isArray(raw) || raw.length === 0) { + fail('replication.placement', 'must be a non-empty array of tiers') + } + + const tiers = raw.map((entry, index) => { + const path = `replication.placement[${index}]` + const tier = section(entry, path) + + return { + minAgeMs: optionalInteger(tier.minAgeMs, `${path}.minAgeMs`, 0, 0), + copies: optionalInteger(tier.copies, `${path}.copies`, 1, 1) + } + }) + + if (tiers[0].minAgeMs !== 0) { + fail('replication.placement[0].minAgeMs', 'must be 0 so that every file matches a tier') + } + + for (let index = 1; index < tiers.length; index += 1) { + if (tiers[index].minAgeMs <= tiers[index - 1].minAgeMs) { + fail(`replication.placement[${index}].minAgeMs`, 'must be greater than the previous tier') + } + } + + return tiers +} + export function resolveReplicationConfig(raw: unknown): ReplicationConfig { const input = section(raw, 'replication') const defaults = DEFAULT_REPLICATION_CONFIG + const placement = resolvePlacement(input.placement, defaults.placement) const replication: ReplicationConfig = { enabled: optionalBoolean(input.enabled, 'replication.enabled', defaults.enabled), - factor: optionalInteger(input.factor, 'replication.factor', defaults.factor, 1), + placement, ackQuorum: optionalInteger(input.ackQuorum, 'replication.ackQuorum', defaults.ackQuorum, 1), requireQuorumOnUpload: optionalBoolean( input.requireQuorumOnUpload, @@ -240,22 +279,12 @@ export function resolveReplicationConfig(raw: unknown): ReplicationConfig { input.repairSchedule, 'replication.repairSchedule', defaults.repairSchedule - ), - token: optionalString(input.token, 'replication.token', defaults.token) - } - - if (replication.ackQuorum > replication.factor) { - fail('replication.ackQuorum', 'must not exceed replication.factor') - } - - if (replication.enabled && replication.token === '') { - fail('replication.token', 'must be set when replication is enabled') + ) } - // A node that only accepts copies leaves `enabled` false but still needs the - // token, so the strength requirement applies whenever one is configured. - if (replication.token !== '' && replication.token.length < 32) { - fail('replication.token', 'must be a secret of at least 32 characters') + const mostCopies = Math.max(...placement.map((tier) => tier.copies)) + if (replication.ackQuorum > mostCopies) { + fail('replication.ackQuorum', 'must not exceed the largest copy count in replication.placement') } return replication diff --git a/src/storage/gc.ts b/src/storage/gc.ts index d18a571..ceaf65f 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -166,8 +166,14 @@ export async function runGarbageCollection(options: GcRunOptions): Promise item.state === 'confirmed')) { + // Guard rail: durable content this node holds must be pinned before anything + // is deleted. A file handed over to its designated holders is deliberately + // unpinned here, so restoring its pin would undo the handover. + const heldConfirmed = plan.retained.filter( + (item) => item.state === 'confirmed' && item.heldLocally + ) + + for (const record of heldConfirmed) { try { const cid = CID.parse(record.cid) if (await isProtected(options.node, cid)) { diff --git a/src/storage/placement.ts b/src/storage/placement.ts new file mode 100644 index 0000000..72ce4a8 --- /dev/null +++ b/src/storage/placement.ts @@ -0,0 +1,145 @@ +import { createHash } from 'node:crypto' + +/** + * How many nodes should hold a file of a given age, including this one. + * + * Fresh content is worth spreading widely; old content is rarely fetched, so + * fewer copies are enough and the rest of the network reclaims the space. + * + * Age is used rather than access time on purpose: recording when a file was + * last read would build a trail of user activity, and sharing that trail + * between nodes would leak it further. Creation time is already implied by the + * upload itself, so it reveals nothing new. + */ +export interface PlacementTier { + /** Smallest file age this tier applies to, in milliseconds. */ + minAgeMs: number + /** Desired holders, including this node. */ + copies: number +} + +/** + * Desired holders for a file of `ageMs`. + * + * @param tiers Tiers ordered by `minAgeMs`, starting at zero + */ +export function copiesForAge(tiers: PlacementTier[], ageMs: number): number { + let copies = tiers[0]?.copies ?? 1 + + for (const tier of tiers) { + if (ageMs >= tier.minAgeMs) { + copies = tier.copies + } + } + + return copies +} + +/** + * Copies that can actually exist. + * + * A network smaller than the desired count already stores the file everywhere + * it can, so asking for more only spams peers that cannot help. + * + * @param networkSize Nodes known to this one, including itself + */ +export function effectiveCopies(desired: number, networkSize: number): number { + return Math.max(1, Math.min(desired, networkSize)) +} + +/** Score of one node for one CID; higher wins. */ +function score(peerId: string, cid: string): bigint { + return createHash('sha256').update(peerId).update(cid).digest().readBigUInt64BE(0) +} + +/** + * Rank nodes for a CID with rendezvous hashing. + * + * Every node computes the same ranking without talking to anyone, so the set of + * designated holders is agreed by construction rather than negotiated. Adding + * or removing a node moves only the fraction of CIDs it wins or loses, instead + * of reshuffling all of them the way a modulo assignment would. + * + * @param peerIds Candidate nodes, including this one + * @returns The same identifiers, best first + */ +export function rankHolders(cid: string, peerIds: string[]): string[] { + return [...peerIds].sort((a, b) => { + const left = score(a, cid) + const right = score(b, cid) + + if (left !== right) { + return left > right ? -1 : 1 + } + + // Equal scores are vanishingly unlikely, but the order must still be total + // and identical on every node. + return a < b ? -1 : 1 + }) +} + +export interface PlacementInput { + cid: string + /** Age of the file in milliseconds. */ + ageMs: number + tiers: PlacementTier[] + /** This node's peer id. */ + selfPeerId: string + /** Peer ids of the other known ADAMANT nodes. */ + peerIds: string[] +} + +export interface Placement { + /** Holders the age policy asks for, including this node. */ + desiredCopies: number + /** Holders that can exist given the size of the network. */ + copies: number + /** Designated holders, best first. */ + holders: string[] + /** True when this node is one of them. */ + selfIsHolder: boolean + /** + * True when the network is no larger than the desired copy count, so every + * node is expected to hold the file and none of them may drop it. + */ + networkTooSmall: boolean +} + +/** Resolve where a file belongs right now. */ +export function placeFile(input: PlacementInput): Placement { + const candidates = [input.selfPeerId, ...input.peerIds] + const desiredCopies = copiesForAge(input.tiers, input.ageMs) + const copies = effectiveCopies(desiredCopies, candidates.length) + const holders = rankHolders(input.cid, candidates).slice(0, copies) + + return { + desiredCopies, + copies, + holders, + selfIsHolder: holders.includes(input.selfPeerId), + networkTooSmall: candidates.length <= desiredCopies + } +} + +/** + * Peers this node should ask to store a file, which is the designated holders + * minus itself. + */ +export function storageTargets(placement: Placement, selfPeerId: string): string[] { + return placement.holders.filter((peerId) => peerId !== selfPeerId) +} + +/** + * Whether this node may stop holding a file. + * + * Only a node outside the designated set may drop its copy, and the designated + * set is identical on every node, so the nodes that must keep the file never + * decide to drop it. That is what stops two nodes from dropping the last two + * copies at the same time, without any locking between them. + * + * A network no larger than the desired copy count never drops anything: every + * node is expected to hold the file, and there is nowhere for the copy to move. + */ +export function mayDemote(placement: Placement): boolean { + return !placement.networkTooSmall && !placement.selfIsHolder +} diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 462a773..aafeb8e 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -28,6 +28,14 @@ export interface FileRecord { storedBytes: number /** True while the root CID is pinned and therefore protected from collection. */ pinned: boolean + /** + * Whether this node is still one of the file's holders. + * + * A confirmed file whose copies live on other nodes is released here and + * `heldLocally` becomes false. The record stays so the node can still answer + * for the file and repair it, but its blocks are no longer protected. + */ + heldLocally: boolean /** Names of peer nodes that acknowledged holding a copy. */ replicas: string[] } @@ -156,11 +164,14 @@ export class FileRegistry { const existing = await this.get(file.cid) if (existing?.state === 'confirmed') { - // Re-uploading already durable content must not weaken its state. + // Re-uploading already durable content must not weaken its state, and it + // puts the blocks back on this node whether or not they were released. return this.save({ ...existing, storedBytes: Math.max(existing.storedBytes, file.storedBytes), - fileSize: file.fileSize > 0 ? file.fileSize : existing.fileSize + fileSize: file.fileSize > 0 ? file.fileSize : existing.fileSize, + pinned: true, + heldLocally: true }) } @@ -176,6 +187,7 @@ export class FileRegistry { fileSize: file.fileSize, storedBytes: file.storedBytes, pinned: true, + heldLocally: true, replicas: existing?.replicas ?? [] }) } @@ -196,7 +208,8 @@ export class FileRegistry { state: 'confirmed', expiresAt: null, confirmedAt: now, - pinned: true + pinned: true, + heldLocally: true }) } @@ -210,7 +223,18 @@ export class FileRegistry { return undefined } - return this.save({ ...record, state: 'expired', pinned: false }) + return this.save({ ...record, state: 'expired', pinned: false, heldLocally: false }) + } + + /** + * Record that the file now lives on other nodes only. + * + * The file stays `confirmed` because it is still durable in the network; this + * node simply stopped being one of its holders. + */ + async releaseLocalCopy(cid: string): Promise { + const record = await this.get(cid) + return record ? this.save({ ...record, pinned: false, heldLocally: false }) : undefined } async setPinned(cid: string, pinned: boolean): Promise { diff --git a/src/storage/replication.ts b/src/storage/replication.ts index 7332aa0..0ee2e97 100644 --- a/src/storage/replication.ts +++ b/src/storage/replication.ts @@ -1,74 +1,74 @@ +import type { Multiaddr } from '@multiformats/multiaddr' import type { ReplicationConfig } from './config.js' +import { placeFile, storageTargets, type Placement } from './placement.js' -export type ReplicationTarget = { +/** Another ADAMANT node this one can reach over libp2p. */ +export interface ReplicationPeer { name: string - apiUrl: string + peerId: string + multiAddr: Multiaddr } -export type ReplicationAck = { +export interface ReplicationAck { node: string ok: boolean error?: string } -export type ReplicationReport = { +export interface ReplicationReport { /** - * `quorum` when the node actively pushes copies to its peers, + * `quorum` when the node actively places copies on its peers, * `best-effort` when replication is disabled and durability is local only. */ mode: 'quorum' | 'best-effort' - /** Copies the policy requires, including the copy on this node */ - factor: number - /** Acknowledgements the policy requires before an upload is durable */ + /** Copies the age policy asks for, including this node. */ + desiredCopies: number + /** Copies that can exist given how many nodes are known. */ + copies: number + /** Acknowledgements needed for this upload to count as durable. */ required: number - /** Copies confirmed right now, including the copy on this node */ + /** Copies confirmed right now, including the one on this node. */ acknowledged: number - /** Peer nodes that acknowledged holding a copy */ + /** Peer nodes that acknowledged holding a copy. */ replicas: string[] satisfied: boolean + /** + * True when the network is no larger than the desired copy count, so every + * node already holds what it can and there is nowhere else to place a copy. + */ + networkTooSmall: boolean attempts: ReplicationAck[] } -export type ReplicateOptions = { +export interface ReplicateOptions { cid: string - targets: ReplicationTarget[] + /** Age of the file, which decides how many copies it deserves. */ + ageMs: number + selfPeerId: string + peers: ReplicationPeer[] config: ReplicationConfig - token: string - /** Injected for tests; defaults to the global fetch */ - request?: (target: ReplicationTarget, cid: string) => Promise + /** Asks one peer to store and pin the file. */ + store: (peer: ReplicationPeer, cid: string) => Promise } /** - * Ask a peer node to store a copy of the content. + * Acknowledgements this upload must collect. * - * The peer pulls the DAG over libp2p and pins it, so a successful response - * means the copy exists and is protected, not merely that the request arrived. + * Bounded by the copies that can actually exist: a three-node network can never + * satisfy a quorum of four, and reporting a permanent shortfall would be noise + * rather than information. */ -async function pushToPeer( - target: ReplicationTarget, - cid: string, - config: ReplicationConfig, - token: string -): Promise { - const url = `${target.apiUrl.replace(/\/+$/, '')}/api/replication/${cid}` - - const response = await fetch(url, { - method: 'POST', - headers: { - 'x-replication-token': token, - 'content-type': 'application/json' - }, - signal: AbortSignal.timeout(config.requestTimeoutMs) - }) - - if (!response.ok) { - throw new Error(`${target.name} responded with HTTP ${response.status}`) - } +export function requiredAcks(config: ReplicationConfig, placement: Placement): number { + return Math.max(1, Math.min(config.ackQuorum, placement.copies)) } /** - * Replicate content across the configured ADAMANT nodes and report whether the - * acknowledgement quorum was reached. + * Place copies of a file on the nodes that should hold it. + * + * Copies go to a deterministic subset rather than to every peer: with a large + * node set, copying everywhere costs bandwidth and disk on every node for no + * added durability. Every node derives the same subset from the CID, so no + * coordination is needed to agree on who is responsible. * * When replication is disabled the node reports `best-effort`: the content is * stored and pinned locally, and no durability claim is made beyond that. @@ -79,55 +79,66 @@ export async function replicate(options: ReplicateOptions): Promise pushToPeer(target, cid, config, options.token)) + const placement = placeFile({ + cid, + ageMs: options.ageMs, + tiers: config.placement, + selfPeerId: options.selfPeerId, + peerIds: options.peers.map((peer) => peer.peerId) + }) + + const targetIds = new Set(storageTargets(placement, options.selfPeerId)) + const targets = options.peers.filter((peer) => targetIds.has(peer.peerId)) const attempts = await Promise.all( - options.targets.map(async (target): Promise => { + targets.map(async (peer): Promise => { try { - await request(target, cid) - return { node: target.name, ok: true } + await options.store(peer, cid) + return { node: peer.name, ok: true } } catch (err) { - return { node: target.name, ok: false, error: (err as Error).message } + return { node: peer.name, ok: false, error: (err as Error).message } } }) ) const replicas = attempts.filter((attempt) => attempt.ok).map((attempt) => attempt.node) - // The local copy counts towards the quorum: it is pinned before peers are asked. + // The local copy counts: it is pinned before any peer is asked. const acknowledged = replicas.length + 1 + const required = requiredAcks(config, placement) return { mode: 'quorum', - factor: config.factor, - required: config.ackQuorum, + desiredCopies: placement.desiredCopies, + copies: placement.copies, + required, acknowledged, replicas, - satisfied: acknowledged >= config.ackQuorum, + satisfied: acknowledged >= required, + networkTooSmall: placement.networkTooSmall, attempts } } /** - * Copies still missing for a file, given the peers that already acknowledged. - * The local copy is always counted, so the result is what repair must create. + * Whether a file holds fewer copies than its placement asks for. + * + * @param acknowledgedPeers Peer nodes known to hold a copy, excluding this one */ -export function missingReplicas(config: ReplicationConfig, replicas: string[]): number { - return Math.max(0, config.factor - (replicas.length + 1)) -} - -/** Files that hold fewer copies than the replication factor requires. */ -export function selectUnderReplicated( - records: T[], - config: ReplicationConfig -): T[] { - return records.filter((record) => missingReplicas(config, record.replicas) > 0) +export function isUnderReplicated( + acknowledgedPeers: number, + placement: Placement, + selfPeerId: string +): boolean { + return acknowledgedPeers < storageTargets(placement, selfPeerId).length } diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts new file mode 100644 index 0000000..726376e --- /dev/null +++ b/src/storage/replicationProtocol.ts @@ -0,0 +1,199 @@ +import type { Connection, Stream } from '@libp2p/interface' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { PeerId } from '@libp2p/interface' +import type { IpfsNode } from '../ipfs-node.js' + +/** + * Protocol ADAMANT nodes use to place copies of a file. + * + * It runs over libp2p rather than over the REST API on purpose. The libp2p + * handshake already proves the remote peer id cryptographically, so no shared + * secret has to be distributed, no second port has to be exposed, and no peer + * has to publish an HTTP address for the others to reach it. + */ +export const REPLICATION_PROTOCOL = '/adamant/replication/1.0.0' + +/** Requests are two short fields; anything larger is a protocol violation. */ +const MAX_MESSAGE_BYTES = 4096 + +export type ReplicationRequest = + /** Store and pin `cid`, pulling the blocks from the requesting peer. */ + | { op: 'store'; cid: string } + /** Report whether this node deliberately holds `cid`. */ + | { op: 'have'; cid: string } + +export type ReplicationResponse = + | { ok: true; op: 'store'; storedBytes: number } + | { ok: true; op: 'have'; has: boolean } + | { ok: false; error: string } + +/** + * Node behaviour behind the protocol. + * + * Injected rather than imported so that the wire format can be exercised + * without a registry, a blockstore, or a running cron. + */ +export interface ReplicationHandlers { + /** + * Whether a peer may ask this node to spend disk on its behalf. + * + * Until file ownership is signed by the uploader, this is the only thing + * standing between the node and an arbitrary peer filling its disk. + */ + isAuthorized(peerId: string): boolean + /** Pin `cid` and register it, returning the bytes now held for it. */ + store(cid: string): Promise + /** Whether this node holds `cid` durably, not merely cached. */ + have(cid: string): Promise + onError?(message: string): void +} + +/** + * Flatten one stream chunk. + * + * The list type is described structurally rather than imported: libp2p resolves + * its own copy of that package, and a second import would be a different + * nominal type to TypeScript. + */ +function toBytes(chunk: Uint8Array | { subarray(): Uint8Array }): Uint8Array { + return chunk instanceof Uint8Array ? chunk : chunk.subarray() +} + +/** + * Read one message, which ends when the remote closes its writing end. + * + * Each stream carries exactly one request and one reply, so end-of-stream is + * the frame delimiter and no length prefix is needed. + */ +async function readMessage(stream: Stream): Promise { + const chunks: Uint8Array[] = [] + let total = 0 + + for await (const chunk of stream) { + const bytes = toBytes(chunk) + total += bytes.byteLength + + if (total > MAX_MESSAGE_BYTES) { + throw new Error('Replication message is too large') + } + + chunks.push(bytes) + } + + return JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown +} + +/** Send one message and close this end for writing. */ +async function sendMessage(stream: Stream, message: unknown): Promise { + stream.send(new TextEncoder().encode(JSON.stringify(message))) + await stream.close() +} + +function parseRequest(value: unknown): ReplicationRequest { + const message = value as Partial + + if (typeof message?.cid !== 'string' || message.cid === '') { + throw new Error('Replication request is missing a CID') + } + + if (message.op !== 'store' && message.op !== 'have') { + throw new Error('Unknown replication operation') + } + + return { op: message.op, cid: message.cid } +} + +async function respond( + stream: Stream, + connection: Connection, + handlers: ReplicationHandlers +): Promise { + const peerId = connection.remotePeer.toString() + + if (!handlers.isAuthorized(peerId)) { + await sendMessage(stream, { ok: false, error: 'Not authorized' }) + return + } + + const request = parseRequest(await readMessage(stream)) + + if (request.op === 'have') { + await sendMessage(stream, { ok: true, op: 'have', has: await handlers.have(request.cid) }) + return + } + + const storedBytes = await handlers.store(request.cid) + await sendMessage(stream, { ok: true, op: 'store', storedBytes }) +} + +/** Start answering replication requests from other ADAMANT nodes. */ +export async function registerReplicationProtocol( + node: IpfsNode, + handlers: ReplicationHandlers +): Promise { + await node.libp2p.handle(REPLICATION_PROTOCOL, (stream, connection) => { + respond(stream, connection, handlers).catch((err: Error) => { + handlers.onError?.(`Replication request failed: ${err.message}`) + stream.abort(err) + }) + }) +} + +export interface ReplicationCallOptions { + /** Bounds the dial, the request, and the peer's own work on it. */ + timeoutMs: number +} + +async function call( + node: IpfsNode, + peer: PeerId | Multiaddr, + request: ReplicationRequest, + options: ReplicationCallOptions +): Promise { + const signal = AbortSignal.timeout(options.timeoutMs) + const stream = await node.libp2p.dialProtocol(peer, REPLICATION_PROTOCOL, { signal }) + + const abortStream = (): void => stream.abort(new Error('Replication request timed out')) + signal.addEventListener('abort', abortStream, { once: true }) + + try { + await sendMessage(stream, request) + const response = (await readMessage(stream)) as ReplicationResponse + + if (response?.ok !== true) { + throw new Error(response?.error ?? 'Replication request was refused') + } + + return response + } finally { + signal.removeEventListener('abort', abortStream) + } +} + +/** + * Ask a peer to store a copy. + * + * The peer pulls the blocks over the connection this request arrived on, and + * pins them before answering, so a successful call means the copy is durable + * there and not merely queued. + */ +export async function requestStore( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + options: ReplicationCallOptions +): Promise { + const response = await call(node, peer, { op: 'store', cid }, options) + return response.ok && response.op === 'store' ? response.storedBytes : 0 +} + +/** Ask a peer whether it still holds a file durably. */ +export async function probeHave( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + options: ReplicationCallOptions +): Promise { + const response = await call(node, peer, { op: 'have', cid }, options) + return response.ok && response.op === 'have' ? response.has : false +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 21a5f62..9bc19fe 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -3,41 +3,76 @@ import { config } from '../config.js' import { helia, ifs } from '../helia.js' import { logger } from '../utils/logger.js' import { getNodesList } from '../utils/utils.js' -import { pinFile, unpinFile } from './pinning.js' +import { mayDemote, placeFile, storageTargets, type Placement } from './placement.js' +import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' +import type { FileRecord } from './registry.js' import { + isUnderReplicated, replicate, - selectUnderReplicated, - type ReplicationReport, - type ReplicationTarget + type ReplicationPeer, + type ReplicationReport } from './replication.js' -import type { FileRecord } from './registry.js' +import { + probeHave, + requestStore, + type ReplicationHandlers, + type ReplicationCallOptions +} from './replicationProtocol.js' import { fileRegistry } from './state.js' +const callOptions = (): ReplicationCallOptions => ({ + timeoutMs: config.replication.requestTimeoutMs +}) + +function selfPeerId(): string { + return helia.libp2p.peerId.toString() +} + /** - * Peer nodes that can accept a copy. + * The other ADAMANT nodes this one can place copies on. * - * A node without `apiUrl` cannot be reached by the replication control plane - * and is skipped, which keeps mixed configurations usable during a rollout. + * They come from the same `nodes` list that already seeds peer discovery, so a + * replication peer needs no extra address: the multiaddr identifies it and the + * libp2p handshake proves the peer id. */ -export function getReplicationTargets(): ReplicationTarget[] { - return getNodesList([helia.libp2p.peerId.toString()]) - .filter((node): node is typeof node & { apiUrl: string } => typeof node.apiUrl === 'string') - .map((node) => ({ name: node.name, apiUrl: node.apiUrl })) +export function getReplicationPeers(): ReplicationPeer[] { + return getNodesList([selfPeerId()]).map((node) => ({ + name: node.name, + peerId: node.peerId.toString(), + multiAddr: node.multiAddr + })) +} + +function placementFor(cid: string, createdAt: number, peers: ReplicationPeer[]): Placement { + return placeFile({ + cid, + ageMs: Math.max(0, Date.now() - createdAt), + tiers: config.replication.placement, + selfPeerId: selfPeerId(), + peerIds: peers.map((peer) => peer.peerId) + }) } /** - * Push a copy of `cid` to the peer nodes and store the acknowledgements. + * Place copies of a file on the nodes that should hold it, and record which of + * them acknowledged. * * Failures are reported, never thrown: an upload that is already stored and * pinned locally stays valid when a peer is unavailable, and the repair job * retries later. */ export async function replicateFile(cid: string): Promise { + const record = await fileRegistry.get(cid) + const report = await replicate({ cid, - targets: getReplicationTargets(), + ageMs: Math.max(0, Date.now() - (record?.createdAt ?? Date.now())), + selfPeerId: selfPeerId(), + peers: getReplicationPeers(), config: config.replication, - token: config.replication.token + store: async (peer) => { + await requestStore(helia, peer.multiAddr, cid, callOptions()) + } }) if (report.mode === 'quorum') { @@ -83,7 +118,7 @@ async function registerPinned(cid: CID, name: string): Promise { } /** - * Make a file durable and replicate it. + * Make a file durable and place its copies. * * @param options `registerUnknown` decides what happens for a CID the node * never accepted through an upload: the confirmation endpoint reports it as @@ -121,14 +156,26 @@ export async function releaseFile(cid: string): Promise return fileRegistry.release(cid) } +/** Store a copy requested by another ADAMANT node. */ +export async function acceptReplica(cid: string): Promise { + return registerPinned(CID.parse(cid), cid) +} + /** - * Store a copy requested by another ADAMANT node. + * Behaviour this node exposes on the replication protocol. * - * The DAG is pulled over libp2p and pinned before the response is sent, so an - * acknowledgement means the copy is durable on this node. + * Until file ownership is signed by the uploader, only the configured ADAMANT + * nodes may ask this one to spend disk. That keeps the current deployment + * closed, and is the piece an ownership signature replaces so that anyone can + * run a node without being handed a shared secret. */ -export async function acceptReplica(cid: string): Promise { - return registerPinned(CID.parse(cid), cid) +export function createReplicationHandlers(): ReplicationHandlers { + return { + isAuthorized: (peerId) => getReplicationPeers().some((peer) => peer.peerId === peerId), + store: async (cid) => (await acceptReplica(cid)).storedBytes, + have: async (cid) => isDirectlyPinned(helia, CID.parse(cid)), + onError: (message) => logger.warn(message) + } } export interface RepairReport { @@ -141,8 +188,9 @@ export interface RepairReport { /** * Detect and repair under-replicated durable content. * - * Only confirmed files are considered: temporary uploads may still disappear by - * policy, so spending peer bandwidth on them is not worthwhile. + * Only confirmed files this node still holds are considered: a temporary upload + * may disappear by policy, and a file this node released is another node's + * responsibility. */ export async function repairReplication(): Promise { const report: RepairReport = { checked: 0, underReplicated: 0, repaired: [], stillMissing: [] } @@ -151,20 +199,107 @@ export async function repairReplication(): Promise { return report } - const confirmed = (await fileRegistry.all()).filter((record) => record.state === 'confirmed') + const peers = getReplicationPeers() + const self = selfPeerId() + const candidates = (await fileRegistry.all()).filter( + (record) => record.state === 'confirmed' && record.heldLocally + ) - report.checked = confirmed.length - const candidates = selectUnderReplicated(confirmed, config.replication) - report.underReplicated = candidates.length + report.checked = candidates.length for (const record of candidates) { + const placement = placementFor(record.cid, record.createdAt, peers) + + if (!isUnderReplicated(record.replicas.length, placement, self)) { + continue + } + + report.underReplicated += 1 const result = await replicateFile(record.cid) - if (result.satisfied && result.acknowledged >= config.replication.factor) { - report.repaired.push(record.cid) - } else { + if (isUnderReplicated(result.replicas.length, placement, self)) { report.stillMissing.push(record.cid) + } else { + report.repaired.push(record.cid) + } + } + + return report +} + +export interface DemotionReport { + checked: number + /** Files whose local copy was released because other nodes hold them. */ + demoted: string[] + /** Files this node kept because the designated holders did not all confirm. */ + kept: string[] +} + +/** + * Release local copies of files that belong on other nodes. + * + * A copy is dropped only when this node is outside the file's designated + * holders and every one of those holders confirms it has the file. The + * designated set is derived from the CID and is the same on every node, so the + * nodes that must keep a file never consider dropping it, and a file cannot + * lose all of its copies to simultaneous decisions. + * + * Nothing is dropped while the network is no larger than the desired copy + * count: there is nowhere for the copy to go, and every node is expected to + * hold it. + */ +export async function demoteReleasableCopies(): Promise { + const report: DemotionReport = { checked: 0, demoted: [], kept: [] } + + if (!config.replication.enabled) { + return report + } + + const peers = getReplicationPeers() + const self = selfPeerId() + const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) + + const candidates = (await fileRegistry.all()).filter( + (record) => record.state === 'confirmed' && record.heldLocally + ) + + report.checked = candidates.length + + for (const record of candidates) { + const placement = placementFor(record.cid, record.createdAt, peers) + + if (!mayDemote(placement)) { + continue } + + const holders = storageTargets(placement, self) + .map((peerId) => byPeerId.get(peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) + + if (holders.length < placement.copies) { + report.kept.push(record.cid) + continue + } + + const confirmations = await Promise.all( + holders.map(async (peer) => { + try { + return await probeHave(helia, peer.multiAddr, record.cid, callOptions()) + } catch { + return false + } + }) + ) + + if (!confirmations.every(Boolean)) { + report.kept.push(record.cid) + continue + } + + await unpinFile(helia, CID.parse(record.cid)) + await fileRegistry.releaseLocalCopy(record.cid) + report.demoted.push(record.cid) + logger.info(`Released the local copy of ${record.cid}; ${holders.length} peers hold it`) } return report diff --git a/src/utils/types.ts b/src/utils/types.ts index 15b3f3e..b89fde3 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -6,14 +6,11 @@ export type NodeWithPeerId = { name: string multiAddr: Multiaddr peerId: PeerId - /** Base URL of the node REST API; only replication peers need one. */ - apiUrl?: string } export type ConfigNode = { name: string multiAddr: string - apiUrl?: string } export type UnixFsMulterFile = Express.Multer.File & { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 230ea1a..eabdd9e 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -30,8 +30,7 @@ export function parseNodes(): NodeWithPeerId[] { return config.nodes.map((node: ConfigNode) => ({ name: node.name, multiAddr: multiaddr(node.multiAddr), - peerId: peerIdFromMultiaddr(node.multiAddr), - apiUrl: node.apiUrl + peerId: peerIdFromMultiaddr(node.multiAddr) })) } diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 5065605..fe1845c 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -152,6 +152,7 @@ describe('expiry rules', () => { createdAt: 1000, confirmedAt: null, pinned: true, + heldLocally: true, replicas: [] } diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts index 4accad1..231dd1d 100644 --- a/test/garbageCollectionPlan.test.ts +++ b/test/garbageCollectionPlan.test.ts @@ -14,6 +14,7 @@ function record(overrides: Partial & Pick { + const dir = await mkdtemp(join(tmpdir(), 'ipfs-node-repl-')) + const blockstore = new FsBlockstore(join(dir, 'blockstore')) + const datastore = new FsDatastore(join(dir, 'datastore')) + await blockstore.open() + await datastore.open() + + const node = await createIpfsNode({ blockstore, datastore, listen: LISTEN }) + + return { node, blockstore, datastore, dir } +} + +async function stopNode(testNode: TestNode): Promise { + await testNode.node.stop() + await testNode.blockstore.close() + await testNode.datastore.close() + await rm(testNode.dir, { recursive: true, force: true }) +} + +let sender: TestNode +let holder: TestNode +let senderFs: UnixFS +let holderFs: UnixFS +let authorized: Set +let refusals: string[] + +before(async () => { + sender = await startNode() + holder = await startNode() + senderFs = unixfs(sender.node) + holderFs = unixfs(holder.node) + + authorized = new Set([sender.node.libp2p.peerId.toString()]) + refusals = [] + + const handlers: ReplicationHandlers = { + isAuthorized: (peerId) => authorized.has(peerId), + store: async (cid) => { + await pinFile(holder.node, CID.parse(cid), AbortSignal.timeout(CALL.timeoutMs)) + const stats = await holderFs.stat(CID.parse(cid), { extended: true, offline: true }) + return Number(stats.deduplicatedDagSize) + }, + have: async (cid) => isDirectlyPinned(holder.node, CID.parse(cid)), + onError: (message) => refusals.push(message) + } + + await registerReplicationProtocol(holder.node, handlers) + + // The holder pulls blocks over this connection, so it has to exist first + await sender.node.libp2p.dial(holder.node.libp2p.getMultiaddrs()) +}) + +after(async () => { + await stopNode(sender) + await stopNode(holder) +}) + +describe('replication over libp2p', () => { + it('places a copy on a peer, which pins it before answering', async () => { + const cid = await senderFs.addBytes(deterministicBytes(4096, 'placed')) + + const storedBytes = await requestStore( + sender.node, + holder.node.libp2p.getMultiaddrs()[0], + cid.toString(), + CALL + ) + + assert.ok(storedBytes > 0, 'the peer must report what it stored') + assert.equal(await isDirectlyPinned(holder.node, cid), true) + assert.equal(await holder.blockstore.has(cid), true) + }) + + it('carries a multi-block file in full', async () => { + const content = deterministicBytes(3_145_735, 'placed-large') + const cid = await senderFs.addBytes(content) + + await requestStore(sender.node, holder.node.libp2p.getMultiaddrs()[0], cid.toString(), CALL) + + const chunks: Uint8Array[] = [] + for await (const chunk of holderFs.cat(cid, { offline: true })) { + chunks.push(chunk) + } + + assert.deepEqual(Buffer.concat(chunks), content) + }) + + it('answers whether it holds a file', async () => { + const held = await senderFs.addBytes(deterministicBytes(1024, 'probe-held')) + const absent = await senderFs.addBytes(deterministicBytes(1024, 'probe-absent')) + const peer = holder.node.libp2p.getMultiaddrs()[0] + + await requestStore(sender.node, peer, held.toString(), CALL) + + assert.equal(await probeHave(sender.node, peer, held.toString(), CALL), true) + assert.equal(await probeHave(sender.node, peer, absent.toString(), CALL), false) + }) + + it('refuses a peer that is not an ADAMANT node', async () => { + const cid = await senderFs.addBytes(deterministicBytes(1024, 'unauthorized')) + const peer = holder.node.libp2p.getMultiaddrs()[0] + + authorized.clear() + try { + await assert.rejects( + () => requestStore(sender.node, peer, cid.toString(), CALL), + /Not authorized/ + ) + assert.equal(await isDirectlyPinned(holder.node, cid), false) + } finally { + authorized.add(sender.node.libp2p.peerId.toString()) + } + }) + + it('rejects a request for content no peer can serve', async () => { + // A CID nobody holds; the holder cannot pull it and must fail, not hang + const unknown = 'bafkreiapv3pyvtsdmvcqcgzvvjmayksybgcgfvkbfbbccjnisz3ndnvcam' + + await assert.rejects(() => + requestStore(sender.node, holder.node.libp2p.getMultiaddrs()[0], unknown, { + timeoutMs: 2000 + }) + ) + }) +}) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index c397e70..607c668 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -50,6 +50,7 @@ function record(overrides: Pick & Partial { + it('spreads fresh content widest', () => { + assert.equal(copiesForAge(TIERS, 0), 4) + assert.equal(copiesForAge(TIERS, 179 * DAY), 4) + }) + + it('reduces copies as a file ages', () => { + assert.equal(copiesForAge(TIERS, 180 * DAY), 3) + assert.equal(copiesForAge(TIERS, 364 * DAY), 3) + assert.equal(copiesForAge(TIERS, 365 * DAY), 2) + assert.equal(copiesForAge(TIERS, 10 * 365 * DAY), 2) + }) +}) + +describe('effectiveCopies', () => { + it('never asks for more copies than there are nodes', () => { + assert.equal(effectiveCopies(4, 2), 2) + assert.equal(effectiveCopies(4, 9), 4) + }) + + it('always keeps at least one copy', () => { + assert.equal(effectiveCopies(4, 0), 1) + }) +}) + +describe('rankHolders', () => { + it('is deterministic and independent of the input order', () => { + const forward = rankHolders(CID, PEERS) + const backward = rankHolders(CID, [...PEERS].reverse()) + + assert.deepEqual(forward, backward) + assert.equal(forward.length, PEERS.length) + }) + + it('ranks different CIDs differently, so load spreads across nodes', () => { + const winners = new Set( + Array.from({ length: 40 }, (unused, index) => rankHolders(`${CID}-${index}`, PEERS)[0]) + ) + + assert.ok(winners.size > 1, 'one node must not win every CID') + }) + + it('keeps the surviving order when a node leaves', () => { + const before = rankHolders(CID, PEERS) + const after = rankHolders( + CID, + PEERS.filter((peer) => peer !== before[0]) + ) + + assert.deepEqual(after, before.slice(1)) + }) +}) + +describe('placeFile', () => { + const place = (ageMs: number, peerIds = PEERS.slice(0, 5)) => + placeFile({ cid: CID, ageMs, tiers: TIERS, selfPeerId: 'self', peerIds }) + + it('designates the age-appropriate number of holders', () => { + assert.equal(place(0).holders.length, 4) + assert.equal(place(365 * DAY).holders.length, 2) + }) + + it('reaches the same answer on every node', () => { + const fromSelf = placeFile({ + cid: CID, + ageMs: 0, + tiers: TIERS, + selfPeerId: 'peer-a', + peerIds: ['self', 'peer-b', 'peer-c'] + }) + const fromPeer = placeFile({ + cid: CID, + ageMs: 0, + tiers: TIERS, + selfPeerId: 'peer-b', + peerIds: ['self', 'peer-a', 'peer-c'] + }) + + assert.deepEqual(fromSelf.holders.sort(), fromPeer.holders.sort()) + }) + + it('reports a network too small to place the desired copies', () => { + const placement = place(0, ['peer-a', 'peer-b']) + + assert.equal(placement.networkTooSmall, true) + assert.equal(placement.copies, 3) + assert.equal(placement.desiredCopies, 4) + }) + + it('never asks itself to store a copy it already has', () => { + const placement = place(0) + + assert.equal(storageTargets(placement, 'self').includes('self'), false) + assert.equal( + storageTargets(placement, 'self').length, + placement.selfIsHolder ? placement.copies - 1 : placement.copies + ) + }) +}) + +describe('mayDemote', () => { + const base = { desiredCopies: 4, copies: 4, holders: [] as string[] } + + it('lets a node outside the designated set release its copy', () => { + assert.equal(mayDemote({ ...base, selfIsHolder: false, networkTooSmall: false }), true) + }) + + it('never lets a designated holder release its copy', () => { + assert.equal(mayDemote({ ...base, selfIsHolder: true, networkTooSmall: false }), false) + }) + + it('never releases anything while the network is too small', () => { + assert.equal(mayDemote({ ...base, selfIsHolder: false, networkTooSmall: true }), false) + }) +}) diff --git a/test/replication.test.ts b/test/replication.test.ts index 07e67a5..5668e6e 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -1,32 +1,58 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' +import { multiaddr } from '@multiformats/multiaddr' import type { ReplicationConfig } from '../src/storage/config.js' -import { missingReplicas, replicate, selectUnderReplicated } from '../src/storage/replication.js' +import { placeFile, storageTargets } from '../src/storage/placement.js' +import { + isUnderReplicated, + replicate, + requiredAcks, + type ReplicationPeer +} from '../src/storage/replication.js' + +const DAY = 24 * 60 * 60 * 1000 +const CID = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' +const SELF = 'self-peer' const baseConfig: ReplicationConfig = { enabled: true, - factor: 3, + placement: [ + { minAgeMs: 0, copies: 3 }, + { minAgeMs: 365 * DAY, copies: 2 } + ], ackQuorum: 2, requireQuorumOnUpload: false, requestTimeoutMs: 1000, repairEnabled: true, - repairSchedule: '0 * * * * *', - token: 'r'.repeat(64) + repairSchedule: '0 * * * * *' } -const targets = [ - { name: 'ipfs2', apiUrl: 'https://ipfs2.example' }, - { name: 'ipfs3', apiUrl: 'https://ipfs3.example' } -] +const peer = (name: string): ReplicationPeer => ({ + name, + peerId: `peer-id-${name}`, + multiAddr: multiaddr('/ip4/127.0.0.1/tcp/4001') +}) + +const PEERS = [peer('ipfs2'), peer('ipfs3'), peer('ipfs4'), peer('ipfs5')] + +const placementFor = (ageMs: number, peers = PEERS) => + placeFile({ + cid: CID, + ageMs, + tiers: baseConfig.placement, + selfPeerId: SELF, + peerIds: peers.map((item) => item.peerId) + }) describe('replicate', () => { it('reports best effort storage when replication is disabled', async () => { const report = await replicate({ - cid: 'bafkrei1', - targets, + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, config: { ...baseConfig, enabled: false }, - token: baseConfig.token, - request: async () => assert.fail('peers must not be contacted') + store: async () => assert.fail('peers must not be contacted') }) assert.equal(report.mode, 'best-effort') @@ -34,78 +60,126 @@ describe('replicate', () => { assert.equal(report.satisfied, true) }) + it('places copies on a subset rather than on every peer', async () => { + const asked: string[] = [] + + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async (target) => { + asked.push(target.name) + } + }) + + // Four peers are available, but a fresh file only wants three holders + assert.ok(asked.length < PEERS.length, `asked ${asked.length} of ${PEERS.length} peers`) + assert.equal(asked.length, storageTargets(placementFor(0), SELF).length) + assert.equal(report.copies, 3) + }) + + it('asks fewer peers as a file ages', async () => { + const askedFresh: string[] = [] + const askedOld: string[] = [] + + await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async (target) => { + askedFresh.push(target.name) + } + }) + await replicate({ + cid: CID, + ageMs: 400 * DAY, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async (target) => { + askedOld.push(target.name) + } + }) + + assert.ok(askedOld.length < askedFresh.length) + }) + it('counts the local copy towards the quorum', async () => { const report = await replicate({ - cid: 'bafkrei1', - targets, + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, config: baseConfig, - token: baseConfig.token, - request: async (target) => { - if (target.name === 'ipfs3') { + store: async (target) => { + if (target.name !== storageTargets(placementFor(0), SELF)[0].replace('peer-id-', '')) { throw new Error('unreachable') } } }) - assert.equal(report.acknowledged, 2) - assert.deepEqual(report.replicas, ['ipfs2']) - assert.equal(report.satisfied, true) + assert.equal(report.acknowledged, report.replicas.length + 1) + assert.equal(report.satisfied, report.acknowledged >= report.required) }) - it('reports an unsatisfied quorum when too few peers acknowledge', async () => { + it('reports an unsatisfied quorum when no peer acknowledges', async () => { const report = await replicate({ - cid: 'bafkrei1', - targets, + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, config: baseConfig, - token: baseConfig.token, - request: async () => { - throw new Error('unreachable') + store: async () => { + throw new Error('connection refused') } }) assert.equal(report.acknowledged, 1) assert.equal(report.satisfied, false) - assert.deepEqual( - report.attempts.map((attempt) => attempt.ok), - [false, false] - ) + assert.ok(report.attempts.every((attempt) => !attempt.ok)) }) - it('survives a node that is offline', async () => { + it('does not demand a quorum the network cannot reach', async () => { + const single = [peer('ipfs2')] + const report = await replicate({ - cid: 'bafkrei1', - targets, - config: { ...baseConfig, factor: 2, ackQuorum: 1 }, - token: baseConfig.token, - request: async (target) => { - if (target.name === 'ipfs2') { - throw new Error('connection refused') - } - } + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: single, + config: { ...baseConfig, ackQuorum: 3 }, + store: async () => {} }) + assert.equal(report.networkTooSmall, true) + assert.equal(report.required, 2) assert.equal(report.satisfied, true) - assert.deepEqual(report.replicas, ['ipfs3']) }) }) -describe('under-replication detection', () => { - it('counts the copies still missing, including the local one', () => { - assert.equal(missingReplicas(baseConfig, []), 2) - assert.equal(missingReplicas(baseConfig, ['ipfs2']), 1) - assert.equal(missingReplicas(baseConfig, ['ipfs2', 'ipfs3']), 0) +describe('requiredAcks', () => { + it('never exceeds the copies that can exist', () => { + assert.equal(requiredAcks({ ...baseConfig, ackQuorum: 3 }, placementFor(0)), 3) + assert.equal(requiredAcks({ ...baseConfig, ackQuorum: 3 }, placementFor(0, [peer('only')])), 2) + }) +}) + +describe('isUnderReplicated', () => { + it('compares against the peers the placement designated', () => { + const placement = placementFor(0) + const targets = storageTargets(placement, SELF).length + + assert.equal(isUnderReplicated(targets, placement, SELF), false) + assert.equal(isUnderReplicated(targets - 1, placement, SELF), true) }) - it('selects only the files below the replication factor', () => { - const records = [ - { cid: 'a', replicas: ['ipfs2', 'ipfs3'] }, - { cid: 'b', replicas: ['ipfs2'] }, - { cid: 'c', replicas: [] } - ] - - assert.deepEqual( - selectUnderReplicated(records, baseConfig).map((item) => item.cid), - ['b', 'c'] - ) + it('reports nothing missing when the network is too small to place more', () => { + const placement = placementFor(0, [peer('only')]) + + assert.equal(isUnderReplicated(1, placement, SELF), false) }) }) diff --git a/test/security.test.ts b/test/security.test.ts index e3de6f8..e47ee28 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -10,7 +10,6 @@ import { validateSecurityConfig } from '../src/security/config.js' import { createCorsOriginDelegate, createOriginMatcher } from '../src/security/cors.js' import { getPublicError, InvalidRequestError } from '../src/security/errors.js' import { createRateLimiter } from '../src/security/rateLimit.js' -import { createReplicationAuth } from '../src/security/replicationToken.js' import { parseTrustProxy } from '../src/security/trustProxy.js' import { createMultipartLimits } from '../src/security/uploadLimits.js' import { FileNotFoundError } from '../src/utils/fileErrors.js' @@ -201,7 +200,6 @@ describe('administrative API key', () => { describe('route access policy', () => { const key = 'b'.repeat(64) - const replicationToken = 'c'.repeat(64) const app = express() let serverUrl = '' let closeServer: (() => Promise) | undefined @@ -218,7 +216,6 @@ describe('route access policy', () => { const debug = Router().get('/test', (req, res) => res.send({ admin: true })) const storage = Router().get('/metrics', (req, res) => res.send({ public: true })) const storageAdminRouter = Router().post('/gc', (req, res) => res.send({ admin: true })) - const replication = Router().post('/test', (req, res) => res.send({ peer: true })) mountApiRoutes( app, @@ -231,12 +228,10 @@ describe('route access policy', () => { libp2p, debug, storage, - storageAdminRouter, - replication + storageAdminRouter }, createApiKeyAuth(key), - false, - createReplicationAuth(replicationToken) + false ) const server = await startServer(app) @@ -263,37 +258,6 @@ describe('route access policy', () => { } }) - it('accepts replication only with the peer token, not with the admin key', async () => { - const path = `${serverUrl}/api/replication/test` - - assert.equal((await fetch(path, { method: 'POST' })).status, 401) - assert.equal((await fetch(path, { method: 'POST', headers: { 'x-api-key': key } })).status, 401) - assert.equal( - ( - await fetch(path, { - method: 'POST', - headers: { 'x-replication-token': replicationToken } - }) - ).status, - 200 - ) - }) - - it('protects node information, Helia, and libp2p routes', async () => { - for (const path of [ - '/api/node/info', - '/api/node/future', - '/api/helia/test', - '/api/libp2p/test' - ]) { - assert.equal((await fetch(`${serverUrl}${path}`)).status, 401) - assert.equal( - (await fetch(`${serverUrl}${path}`, { headers: { 'x-api-key': key } })).status, - 200 - ) - } - }) - it('does not mount the debug API by default', async () => { assert.equal((await fetch(`${serverUrl}/api/debug/test`)).status, 404) }) @@ -313,12 +277,10 @@ describe('route access policy', () => { libp2p: emptyRouter, debug, storage: emptyRouter, - storageAdminRouter: emptyRouter, - replication: emptyRouter + storageAdminRouter: emptyRouter }, createApiKeyAuth(key), - true, - createReplicationAuth('') + true ) const server = await startServer(debugApp) diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 824f60b..545375c 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -57,38 +57,68 @@ describe('resolveStorageConfig', () => { }) describe('resolveReplicationConfig', () => { - const token = 'r'.repeat(64) - it('defaults to disabled best-effort storage', () => { assert.deepEqual(resolveReplicationConfig(undefined), DEFAULT_REPLICATION_CONFIG) assert.equal(DEFAULT_REPLICATION_CONFIG.enabled, false) }) - it('rejects a quorum larger than the replication factor', () => { + it('reduces copies as files age', () => { + const tiers = DEFAULT_REPLICATION_CONFIG.placement + + assert.equal(tiers[0].minAgeMs, 0) + for (let index = 1; index < tiers.length; index += 1) { + assert.ok(tiers[index].minAgeMs > tiers[index - 1].minAgeMs) + assert.ok(tiers[index].copies <= tiers[index - 1].copies) + } + }) + + it('rejects tiers that do not start at age zero', () => { assert.throws( - () => resolveReplicationConfig({ enabled: true, factor: 2, ackQuorum: 3, token }), - /ackQuorum/ + () => resolveReplicationConfig({ placement: [{ minAgeMs: 1, copies: 2 }] }), + /placement\[0\].minAgeMs/ + ) + }) + + it('rejects tiers that go backwards', () => { + assert.throws( + () => + resolveReplicationConfig({ + placement: [ + { minAgeMs: 0, copies: 3 }, + { minAgeMs: 0, copies: 2 } + ] + }), + /placement\[1\].minAgeMs/ ) }) - it('rejects enabling replication without a peer token', () => { - assert.throws(() => resolveReplicationConfig({ enabled: true }), /replication.token/) + it('rejects an empty placement policy', () => { + assert.throws(() => resolveReplicationConfig({ placement: [] }), /replication.placement/) + }) + + it('rejects a quorum larger than any tier can provide', () => { assert.throws( - () => resolveReplicationConfig({ enabled: true, token: 'short' }), - /replication.token/ + () => + resolveReplicationConfig({ + placement: [{ minAgeMs: 0, copies: 2 }], + ackQuorum: 3 + }), + /ackQuorum/ ) }) it('accepts a complete policy', () => { const replication = resolveReplicationConfig({ enabled: true, - factor: 3, + placement: [ + { minAgeMs: 0, copies: 4 }, + { minAgeMs: 1000, copies: 2 } + ], ackQuorum: 2, - requireQuorumOnUpload: true, - token + requireQuorumOnUpload: true }) - assert.equal(replication.factor, 3) + assert.equal(replication.placement.length, 2) assert.equal(replication.ackQuorum, 2) assert.equal(replication.requireQuorumOnUpload, true) }) From 1f6047889fec068431602f324d4f99b489c41e83 Mon Sep 17 00:00:00 2001 From: adamantmm Date: Sat, 22 Aug 2026 21:52:49 +0300 Subject: [PATCH 104/167] docs(storage): correct what happens on a network smaller than the copy count The placement section claimed that no copy requests are sent when the network is no larger than the desired copy count. That is wrong: every node becomes a designated holder, so the file goes to all of them. Asking for four copies on three nodes places three, and the fourth is simply not looked for. What the small-network case does prevent is handover, because there is nowhere for a copy to move. Also records that a copy request made in the first seconds after a node starts can time out while bitswap is still learning which peers serve blocks, and that the repair job settles it. Observed on a four-node network, where only the first uploads were affected. Co-Authored-By: Claude Opus 5 --- docs/storage-lifecycle.md | 23 ++++++++++++++++++++--- test/placement.test.ts | 28 +++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 050593a..d2a3502 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -178,9 +178,19 @@ was last read would build a log of user activity, and sharing that log between nodes so they could agree on it would spread the leak further. Creation time is already implied by the upload, so it reveals nothing new. -When the network is no larger than the desired copy count, every node is -expected to hold the file: no copy requests are sent, because there is nowhere -for a copy to go, and nothing is ever handed over. +When the network is no larger than the desired copy count, every node becomes a +designated holder, so the file goes to all of them. Asking for four copies on a +three-node network places three, and the fourth is simply not looked for: the +count is capped at the nodes that exist rather than retried against a node that +does not. Nothing is ever handed over in that state either, because there is +nowhere for a copy to move. + +| Nodes | Desired copies | Holders | Peers asked | Handover | +| ----- | -------------- | -------------- | ----------- | ----------------------------------- | +| 1 | 4 | 1 | 0 | never | +| 3 | 4 | 3, all of them | 2 | never | +| 4 | 4 | 4, all of them | 3 | never | +| 6 | 4 | 4 | 3 or 4 | allowed for the two outside the set | ### Handing a copy over @@ -221,6 +231,13 @@ Replication needs the peers to be connected over libp2p, because the copy itself travels by bitswap. Keep every replication peer in `peerDiscovery.bootstrap` as well as in `nodes`. +A copy request made in the first seconds after a node starts can time out: the +connection is up, but bitswap has not yet learned which peers can serve blocks, +so the receiving node cannot pull the DAG in time. The upload still succeeds and +reports the shortfall, and the repair job places the missing copies on its next +pass. This is worth knowing when reading the logs of a node that has just been +restarted; it is not worth reacting to. + ### Why not Kubo with IPFS Cluster Helia has no native pin-orchestration protocol. Cross-node pinning has to come diff --git a/test/placement.test.ts b/test/placement.test.ts index 8ca86e1..816e68d 100644 --- a/test/placement.test.ts +++ b/test/placement.test.ts @@ -102,12 +102,34 @@ describe('placeFile', () => { assert.deepEqual(fromSelf.holders.sort(), fromPeer.holders.sort()) }) - it('reports a network too small to place the desired copies', () => { + it('puts the file on every node when the network is smaller than the desired count', () => { + // Three nodes in total, four copies wanted: all three hold it, and the + // fourth copy is not looked for on a node that does not exist const placement = place(0, ['peer-a', 'peer-b']) - assert.equal(placement.networkTooSmall, true) - assert.equal(placement.copies, 3) assert.equal(placement.desiredCopies, 4) + assert.equal(placement.copies, 3) + assert.deepEqual(placement.holders.sort(), ['peer-a', 'peer-b', 'self']) + assert.equal(placement.selfIsHolder, true) + assert.deepEqual(storageTargets(placement, 'self').sort(), ['peer-a', 'peer-b']) + assert.equal(placement.networkTooSmall, true) + assert.equal(mayDemote(placement), false) + }) + + it('asks nobody when it is the only node', () => { + const placement = place(0, []) + + assert.equal(placement.copies, 1) + assert.deepEqual(storageTargets(placement, 'self'), []) + assert.equal(mayDemote(placement), false) + }) + + it('stops growing the holder set once the network is large enough', () => { + const placement = place(0, ['peer-a', 'peer-b', 'peer-c', 'peer-d', 'peer-e']) + + assert.equal(placement.copies, 4) + assert.equal(placement.holders.length, 4) + assert.equal(placement.networkTooSmall, false) }) it('never asks itself to store a copy it already has', () => { From 64ac7eeda45a86b5a3c20ea4f99329d5efd1b577 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Mon, 24 Aug 2026 10:47:49 +0300 Subject: [PATCH 105/167] feat(storage): reach a file's holders before reading it A messenger sender and receiver use different nodes, and the receiver asks its own node for the file as soon as the message arrives. That read almost never hits a holder: with four copies spread over a large node set, the receiving node is simply not one of them. Bitswap asks the peers a node happens to be connected to, which works only while every node is connected to every other. A node connected to none of a file's holders waited for findFileTimeout and answered 408. Reproduced on a four-node network: a node with no connections, asked for a CID it did not hold, failed after 20 seconds. A node now opens connections to the nodes that should hold a CID before reading it. It can name them because holders are derived from the CID, and it does not need the file's age to do so: the holders of any age are a prefix of the same ranking, so the widest prefix covers all of them. The same read now returns in 30 to 80 milliseconds. Bootstrap also dials its peers once and never again, so nothing reconnected a peer that restarted and the mesh came apart silently. The node now redials the unconnected peers in `nodes`, once at startup before the API serves and then on `peeringSchedule`, bounded by the size of that list. Co-Authored-By: Claude Opus 5 --- README.md | 2 + config.default.json5 | 3 + config.test.json5 | 1 + docs/storage-lifecycle.md | 54 +++++++++---- src/api/file.ts | 13 +++- src/config.ts | 14 ++++ src/index.ts | 6 ++ src/peering.cron.ts | 68 ++++++++++++++++ src/storage/retrieval.ts | 96 +++++++++++++++++++++++ src/storage/service.ts | 14 ++++ test/integration/retrieval.test.ts | 121 +++++++++++++++++++++++++++++ test/retrieval.test.ts | 77 ++++++++++++++++++ 12 files changed, 453 insertions(+), 16 deletions(-) create mode 100644 src/peering.cron.ts create mode 100644 src/storage/retrieval.ts create mode 100644 test/integration/retrieval.test.ts create mode 100644 test/retrieval.test.ts diff --git a/README.md b/README.md index d2db2e3..e94517a 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, }, "adminApiKey": "", "enableDebugApi": false, + "peeringSchedule": "*/30 * * * * *", "storage": { "maxRequestSizeBytes": 536870912, "maxConcurrentUploads": 4, @@ -109,6 +110,7 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - Leave `storage.gc.enabled` as `false` until a deletion policy is agreed; `POST /api/storage/gc` runs on demand meanwhile - Set `replication.enabled` on every node that should place copies; replication needs no key and no extra address, because it runs on a libp2p protocol between the peers already listed in `nodes` - Tune `replication.placement` if the deployment wants a different number of copies per file age +- `peeringSchedule` is optional and defaults to every thirty seconds; it redials the peers in `nodes` that are not connected, which `autoPeeringPeriod` never did Invalid CORS, proxy, API-key, or rate-limit configuration stops the process instead of silently weakening the boundary. diff --git a/config.default.json5 b/config.default.json5 index 242f9f4..c7535e5 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -25,6 +25,9 @@ }, serverPort: 4000, diskUsageScanPeriod: '*/30 * * * * *', + // Redials the nodes above that are not connected. Bootstrap dials once and + // never again, so without this a mesh never recovers from a restart. + peeringSchedule: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. maxFileCount: 10, findFileTimeout: 20000, diff --git a/config.test.json5 b/config.test.json5 index cebd636..1de222f 100644 --- a/config.test.json5 +++ b/config.test.json5 @@ -11,6 +11,7 @@ }, serverPort: 4000, diskUsageScanPeriod: '*/30 * * * * *', + peeringSchedule: '*/30 * * * * *', uploadLimitSizeBytes: 268435456, // 256 Mb. maxFileCount: 10, findFileTimeout: 20000, diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index d2a3502..4b3499b 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -214,12 +214,38 @@ its holders. Routing stays the node's problem, not the client's. A client asks whichever node it likes for a CID; if that node does not hold the file, it fetches the blocks -over bitswap from the peers that do and streams them on. Placement does not -change that contract — it only makes it likely that a connected peer has the -file, and it tells the node which peers to expect it from. - -A node that cannot retrieve content answers `408` after `findFileTimeout`. It -never answers `404`, because it cannot know that content does not exist. +over bitswap and streams them on. This matters for the messenger, where the +sender and the receiver are usually on different nodes: the receiver learns the +CID as soon as the message arrives and asks its own node for it immediately, +long before any background job could have moved a copy there. + +Bitswap asks the peers a node happens to be connected to. That is enough while +every node is connected to every other, and it stops being enough as soon as the +network outgrows the connection limit: a node connected to none of a file's +holders waits for `findFileTimeout` and answers `408`. + +So before reading a CID it does not hold, a node opens connections to the nodes +that should hold it. It can name them because holders are derived from the CID, +and it does not need to know how old the file is: the holders of a file of any +age are a prefix of the same ranking, so the widest prefix any tier can ask for +covers all of them. The read then talks to a handful of named nodes instead of +depending on who happens to be connected. + +A node that still cannot retrieve content answers `408` after `findFileTimeout`. +It never answers `404`, because it cannot know that content does not exist. + +### Keeping the mesh connected + +`@libp2p/bootstrap` dials its peers once, shortly after start, and never again. +Nothing else reconnects a peer that restarted or dropped, so a mesh quietly +comes apart over time. It is easy to miss, because uploads keep working: the +damage shows up later as slow retrieval and as replication that cannot place +copies. + +The node therefore redials the peers in `nodes` that are not connected, once at +startup before the API starts serving and then on `peeringSchedule`. The work is +bounded by the size of `nodes`, which is the operator's own peer list, so it +never becomes network-wide dialling. ### Repair @@ -228,15 +254,13 @@ lists confirmed files this node holds whose designated peers have not all acknowledged, and places the missing copies again. Replication needs the peers to be connected over libp2p, because the copy itself -travels by bitswap. Keep every replication peer in `peerDiscovery.bootstrap` as -well as in `nodes`. - -A copy request made in the first seconds after a node starts can time out: the -connection is up, but bitswap has not yet learned which peers can serve blocks, -so the receiving node cannot pull the DAG in time. The upload still succeeds and -reports the shortfall, and the repair job places the missing copies on its next -pass. This is worth knowing when reading the logs of a node that has just been -restarted; it is not worth reacting to. +travels by bitswap. Startup peering opens those connections before the API +starts serving, so the first upload after a restart already has somewhere to +place its copies. + +A copy request that a peer cannot answer in time is reported as a shortfall +rather than failing the upload, and the repair job places the missing copies on +its next pass. ### Why not Kubo with IPFS Cluster diff --git a/src/api/file.ts b/src/api/file.ts index c52c36b..501fd0c 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -5,7 +5,12 @@ import { admitUpload, getUploadSession } from '../middleware/uploadGuards.js' import { readLimiter, uploadLimiter } from '../middleware/rateLimiter.js' import { multerStorage } from '../multer.js' import { pinFile } from '../storage/pinning.js' -import { confirmFile, releaseFile, replicateFile } from '../storage/service.js' +import { + confirmFile, + prepareFileRetrieval, + releaseFile, + replicateFile +} from '../storage/service.js' import { fileRegistry } from '../storage/state.js' import { parseCid } from '../utils/cid.js' import { sendDownloadStream } from '../utils/downloadResponse.js' @@ -144,6 +149,12 @@ router.get('/:cid/status', readLimiter, async (req, res, next) => { router.get('/:cid', readLimiter, async (req, res, next) => { try { const cid = parseCid(req.params.cid) + + // Reach the file's holders first. Without this the read only succeeds if a + // peer that has the file is already connected, which stops being true as + // soon as the network is larger than the connection limit. + await prepareFileRetrieval(cid) + const fileStats = await getFileStats(cid) const stream = downloadFile(cid) diff --git a/src/config.ts b/src/config.ts index bac457e..2b4eeeb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -37,6 +37,9 @@ function findRootDir(start: string): string { const currDir = dirname(fileURLToPath(import.meta.url)) +/** Redial unconnected ADAMANT nodes every half minute unless configured otherwise. */ +const DEFAULT_PEERING_SCHEDULE = '*/30 * * * * *' + /** Log levels accepted by `pino`, ordered from least to most verbose. */ const LOG_LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'] as const @@ -64,6 +67,12 @@ export interface Config { serverPort: number /** Disk space scanning period in cron format. */ diskUsageScanPeriod: string + /** + * How often the node redials the peers in `nodes` that are not connected, + * in cron format. Bootstrap only dials once, so without this a mesh never + * recovers from a restart. + */ + peeringSchedule: string /** Maximum size of a single uploaded file, in bytes. */ uploadLimitSizeBytes: number /** Maximum number of files accepted per upload request. */ @@ -186,6 +195,10 @@ export function validateConfig(raw: unknown): Config { const storeFolder = requireString(root.storeFolder, 'storeFolder') const serverPort = requireInteger(root.serverPort, 'serverPort', 1) const diskUsageScanPeriod = requireString(root.diskUsageScanPeriod, 'diskUsageScanPeriod') + const peeringSchedule = + root.peeringSchedule === undefined + ? DEFAULT_PEERING_SCHEDULE + : requireString(root.peeringSchedule, 'peeringSchedule') const findFileTimeout = requireInteger(root.findFileTimeout, 'findFileTimeout', 1) const bootstrap = requireStringArray(peerDiscovery.bootstrap, 'peerDiscovery.bootstrap') @@ -208,6 +221,7 @@ export function validateConfig(raw: unknown): Config { peerDiscovery: { bootstrap, listen }, serverPort, diskUsageScanPeriod, + peeringSchedule, uploadLimitSizeBytes: root.uploadLimitSizeBytes as number, maxFileCount: root.maxFileCount as number, findFileTimeout, diff --git a/src/index.ts b/src/index.ts index 8ac6709..15533fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import express from 'express' import { httpLogger, logger } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' +import { peeringCron, peerWithKnownNodes } from './peering.cron.js' import { helia } from './helia.js' import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' @@ -19,6 +20,11 @@ logger.info(`Using config file: ${CONFIG_FILE_NAME}`) diskUsageCron.start() +// Bootstrap dials the peers once and never again, so the mesh is kept together +// here instead. Retrieval and replication both depend on it being intact. +peeringCron.start() +await peerWithKnownNodes() + // Deletion is opt-in: the collector only runs once an operator has agreed to a // deletion policy and enabled it. Until then the authorized // POST /api/storage/gc endpoint stays the only way to reclaim space. diff --git a/src/peering.cron.ts b/src/peering.cron.ts new file mode 100644 index 0000000..6d8dd2f --- /dev/null +++ b/src/peering.cron.ts @@ -0,0 +1,68 @@ +import { CronJob } from 'cron' +import { config } from './config.js' +import { helia } from './helia.js' +import { logger } from './utils/logger.js' +import { getNodesList } from './utils/utils.js' + +/** + * Time allowed for one peering dial. A node that is down must not hold up the + * others, and the next tick will try it again. + */ +const DIAL_TIMEOUT_MS = 10000 + +let running = false +let lastConnected = 0 + +/** + * Dial the configured ADAMANT nodes that are not currently connected. + * + * `@libp2p/bootstrap` emits its peers once shortly after start and never again, + * so nothing reconnects a peer that restarted or dropped. A mesh that quietly + * comes apart still serves uploads, which makes the failure easy to miss: it + * shows up later as slow retrieval and as replication that cannot place copies. + * + * The work is bounded by the size of `nodes`, which is the operator's own peer + * list, so this never turns into network-wide dialling. + * + * @returns How many nodes are connected after this pass + */ +export async function peerWithKnownNodes(): Promise { + const known = getNodesList([helia.libp2p.peerId.toString()]) + const connected = new Set(helia.libp2p.getPeers().map((peer) => peer.toString())) + const missing = known.filter((node) => !connected.has(node.peerId.toString())) + + const results = await Promise.allSettled( + missing.map(async (node) => { + await helia.libp2p.dial(node.multiAddr, { signal: AbortSignal.timeout(DIAL_TIMEOUT_MS) }) + return node.name + }) + ) + + for (const [index, result] of results.entries()) { + if (result.status === 'rejected') { + logger.debug(`Peering with ${missing[index].name} failed: ${String(result.reason)}`) + } + } + + lastConnected = connected.size + results.filter((r) => r.status === 'fulfilled').length + return lastConnected +} + +export const peeringCron = new CronJob(config.peeringSchedule, () => { + if (running) { + return + } + + running = true + peerWithKnownNodes() + .catch((err: Error) => logger.error(`${err.message}\n${err.stack}`)) + .finally(() => (running = false)) +}) + +export function getPeeringState() { + return { + schedule: config.peeringSchedule, + knownNodes: getNodesList([helia.libp2p.peerId.toString()]).length, + connectedNodes: lastConnected + } +} diff --git a/src/storage/retrieval.ts b/src/storage/retrieval.ts new file mode 100644 index 0000000..c712849 --- /dev/null +++ b/src/storage/retrieval.ts @@ -0,0 +1,96 @@ +import type { CID } from 'multiformats/cid' +import type { IpfsNode } from '../ipfs-node.js' +import type { ReplicationConfig } from './config.js' +import { rankHolders } from './placement.js' +import type { ReplicationPeer } from './replication.js' + +/** + * Time allowed for opening connections to a file's holders before retrieval + * starts. Short on purpose: a peer that cannot be reached quickly is not worth + * waiting for when others may already be connected. + */ +export const HOLDER_DIAL_TIMEOUT_MS = 5000 + +/** + * The nodes worth asking for a CID this node does not hold. + * + * The holders of a file are a prefix of its rendezvous ranking, and the ranking + * does not depend on age — only how much of it is used does. Taking the largest + * prefix any tier can ask for therefore covers the holders of a file of any + * age, without knowing when it was uploaded. That matters here, because a node + * asked for a file it never stored has no record of it. + * + * With replication disabled there are no designated holders, so every known + * node is a candidate. + */ +export function retrievalTargets( + cid: string, + config: ReplicationConfig, + selfPeerId: string, + peers: ReplicationPeer[] +): ReplicationPeer[] { + if (!config.enabled) { + return peers + } + + const widest = Math.max(...config.placement.map((tier) => tier.copies)) + const ranked = rankHolders(cid, [selfPeerId, ...peers.map((peer) => peer.peerId)]).slice( + 0, + widest + ) + const wanted = new Set(ranked) + + return peers.filter((peer) => wanted.has(peer.peerId)) +} + +/** + * Open connections to the nodes that should hold `cid`. + * + * Without this, retrieval relies on the file being reachable through whichever + * peers happen to be connected. That holds while every node is connected to + * every other, and stops holding as soon as the network outgrows it: bitswap + * asks the peers it has, and a node that is connected to none of the holders + * waits for the retrieval timeout and answers `408`. + * + * Failures are ignored. This is a best-effort warm-up before retrieval, and a + * peer that is already connected, unreachable, or slow must not delay the read. + */ +export async function connectToHolders( + node: IpfsNode, + targets: ReplicationPeer[], + timeoutMs: number = HOLDER_DIAL_TIMEOUT_MS +): Promise { + const connected = new Set(node.libp2p.getPeers().map((peer) => peer.toString())) + const missing = targets.filter((peer) => !connected.has(peer.peerId)) + + if (missing.length === 0) { + return 0 + } + + const results = await Promise.allSettled( + missing.map(async (peer) => + node.libp2p.dial(peer.multiAddr, { signal: AbortSignal.timeout(timeoutMs) }) + ) + ) + + return results.filter((result) => result.status === 'fulfilled').length +} + +/** + * Make sure a CID can be retrieved before asking for it. + * + * Does nothing when the block is already here, which is the common case for a + * designated holder and costs one blockstore lookup for everyone else. + */ +export async function prepareRetrieval( + node: IpfsNode, + cid: CID, + targets: () => ReplicationPeer[], + timeoutMs: number = HOLDER_DIAL_TIMEOUT_MS +): Promise { + if (await node.blockstore.has(cid)) { + return + } + + await connectToHolders(node, targets(), timeoutMs) +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 9bc19fe..4c27414 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -18,6 +18,7 @@ import { type ReplicationHandlers, type ReplicationCallOptions } from './replicationProtocol.js' +import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { fileRegistry } from './state.js' const callOptions = (): ReplicationCallOptions => ({ @@ -89,6 +90,19 @@ export async function replicateFile(cid: string): Promise { return report } +/** + * Connect to the nodes that should hold `cid` before reading it. + * + * A client asks whichever node it likes for a file, and that node is usually + * not one of its holders. Placement tells it exactly which nodes to reach, so + * the read does not depend on a holder happening to be connected already. + */ +export async function prepareFileRetrieval(cid: CID): Promise { + await prepareRetrieval(helia, cid, () => + retrievalTargets(cid.toString(), config.replication, selfPeerId(), getReplicationPeers()) + ) +} + /** * Pin content that is not in the registry yet and record it as durable. * diff --git a/test/integration/retrieval.test.ts b/test/integration/retrieval.test.ts new file mode 100644 index 0000000..1ea45ce --- /dev/null +++ b/test/integration/retrieval.test.ts @@ -0,0 +1,121 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { unixfs, type UnixFS } from '@helia/unixfs' +import { FsBlockstore } from 'blockstore-fs' +import { FsDatastore } from 'datastore-fs' +import type { CID } from 'multiformats/cid' +import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' +import type { ReplicationPeer } from '../../src/storage/replication.js' +import { connectToHolders } from '../../src/storage/retrieval.js' +import { deterministicBytes } from '../fixtures.js' + +/** Only loopback, and port 0 so the OS picks a free port. */ +const LISTEN = ['/ip4/127.0.0.1/tcp/0'] + +interface TestNode { + node: IpfsNode + blockstore: FsBlockstore + datastore: FsDatastore + dir: string +} + +async function startNode(): Promise { + const dir = await mkdtemp(join(tmpdir(), 'ipfs-node-retrieval-')) + const blockstore = new FsBlockstore(join(dir, 'blockstore')) + const datastore = new FsDatastore(join(dir, 'datastore')) + await blockstore.open() + await datastore.open() + + return { + node: await createIpfsNode({ blockstore, datastore, listen: LISTEN }), + blockstore, + datastore, + dir + } +} + +async function stopNode(testNode: TestNode): Promise { + await testNode.node.stop() + await testNode.blockstore.close() + await testNode.datastore.close() + await rm(testNode.dir, { recursive: true, force: true }) +} + +/** Read a file with a deadline, reporting whether it arrived. */ +async function canRead(fs: UnixFS, cid: CID, timeoutMs: number): Promise { + try { + const signal = AbortSignal.timeout(timeoutMs) + let total = 0 + for await (const chunk of fs.cat(cid, { signal })) { + total += chunk.byteLength + } + return total > 0 + } catch { + return false + } +} + +let holder: TestNode +let reader: TestNode +let holderFs: UnixFS +let readerFs: UnixFS + +before(async () => { + holder = await startNode() + reader = await startNode() + holderFs = unixfs(holder.node) + readerFs = unixfs(reader.node) +}) + +after(async () => { + await stopNode(reader) + await stopNode(holder) +}) + +describe('retrieval from an unconnected holder', () => { + it('cannot read the file while no connection exists', async () => { + const cid = await holderFs.addBytes(deterministicBytes(2048, 'unreachable')) + + assert.equal(reader.node.libp2p.getPeers().length, 0) + assert.equal(await canRead(readerFs, cid, 1500), false) + }) + + it('reads the file after connecting to the node that holds it', async () => { + const cid = await holderFs.addBytes(deterministicBytes(2048, 'reachable')) + const target: ReplicationPeer = { + name: 'holder', + peerId: holder.node.libp2p.peerId.toString(), + multiAddr: holder.node.libp2p.getMultiaddrs()[0] + } + + assert.equal(await canRead(readerFs, cid, 1500), false) + + const opened = await connectToHolders(reader.node, [target]) + assert.equal(opened, 1) + + assert.equal(await canRead(readerFs, cid, 10000), true) + }) + + it('opens nothing when the holder is already connected', async () => { + const target: ReplicationPeer = { + name: 'holder', + peerId: holder.node.libp2p.peerId.toString(), + multiAddr: holder.node.libp2p.getMultiaddrs()[0] + } + + assert.equal(await connectToHolders(reader.node, [target]), 0) + }) + + it('ignores a peer it cannot reach', async () => { + const unreachable: ReplicationPeer = { + name: 'gone', + peerId: '12D3KooWJw99nqrQ2L2joFuGCF9EN9EyF8ZrvGr1odQ61HoPrbXd', + multiAddr: holder.node.libp2p.getMultiaddrs()[0] + } + + await assert.doesNotReject(() => connectToHolders(reader.node, [unreachable], 1000)) + }) +}) diff --git a/test/retrieval.test.ts b/test/retrieval.test.ts new file mode 100644 index 0000000..587f762 --- /dev/null +++ b/test/retrieval.test.ts @@ -0,0 +1,77 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { multiaddr } from '@multiformats/multiaddr' +import type { ReplicationConfig } from '../src/storage/config.js' +import { placeFile } from '../src/storage/placement.js' +import type { ReplicationPeer } from '../src/storage/replication.js' +import { retrievalTargets } from '../src/storage/retrieval.js' + +const DAY = 24 * 60 * 60 * 1000 +const CID = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' +const SELF = 'self-peer' + +const config: ReplicationConfig = { + enabled: true, + placement: [ + { minAgeMs: 0, copies: 4 }, + { minAgeMs: 180 * DAY, copies: 3 }, + { minAgeMs: 365 * DAY, copies: 2 } + ], + ackQuorum: 1, + requireQuorumOnUpload: false, + requestTimeoutMs: 1000, + repairEnabled: true, + repairSchedule: '0 * * * * *' +} + +const peer = (name: string): ReplicationPeer => ({ + name, + peerId: `peer-id-${name}`, + multiAddr: multiaddr('/ip4/127.0.0.1/tcp/4001') +}) + +const PEERS = Array.from({ length: 9 }, (unused, index) => peer(`n${index}`)) + +describe('retrievalTargets', () => { + it('asks a handful of nodes rather than the whole network', () => { + const targets = retrievalTargets(CID, config, SELF, PEERS) + + assert.ok(targets.length <= 4, `asked ${targets.length} of ${PEERS.length}`) + assert.ok(targets.length > 0) + }) + + it('covers the holders of a file of any age', () => { + // A node asked for a file it never stored does not know how old it is, so + // the target set has to be right without that knowledge + const targets = new Set(retrievalTargets(CID, config, SELF, PEERS).map((item) => item.peerId)) + + for (const ageMs of [0, 200 * DAY, 400 * DAY, 10 * 365 * DAY]) { + const holders = placeFile({ + cid: CID, + ageMs, + tiers: config.placement, + selfPeerId: SELF, + peerIds: PEERS.map((item) => item.peerId) + }).holders.filter((peerId) => peerId !== SELF) + + for (const holder of holders) { + assert.ok(targets.has(holder), `age ${ageMs} holder ${holder} is not a retrieval target`) + } + } + }) + + it('never returns itself', () => { + const targets = retrievalTargets(CID, config, SELF, PEERS) + + assert.equal( + targets.some((item) => item.peerId === SELF), + false + ) + }) + + it('falls back to every known node when replication is disabled', () => { + const targets = retrievalTargets(CID, { ...config, enabled: false }, SELF, PEERS) + + assert.deepEqual(targets, PEERS) + }) +}) From f92753979f97eda059d238cf6ca7e96609570fe7 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Mon, 24 Aug 2026 12:20:31 +0300 Subject: [PATCH 106/167] feat(storage): free blocks only when space is short Collection deleted every unpinned block as soon as anything was released, which threw away two useful things: the read cache, and the copies a node had just handed over to their designated holders. Both would then be fetched over the network again the next time somebody asked for them. Releasing and deleting are now separate. Losing a pin stays policy and happens whenever the rules say so; deleting waits until space is actually short. Until then an unpinned block stays on disk and keeps answering reads for free, which also means a node that handed a copy over still serves it. Space is short when the blockstore passes `highWatermarkBytes` or when free space falls into `storage.diskReserveBytes`. The second trigger matters on a volume smaller than the configured watermark, where the ceiling would never be reached and the disk would fill instead. Reports say which applied. With deletion gated this way the scheduled collector costs nothing while there is room, so it is now on by default. It still never selects a confirmed file this node holds, and still restores a missing pin before deleting anything. Co-Authored-By: Claude Opus 5 --- README.md | 4 +- config.default.json5 | 7 +-- config.test.json5 | 2 +- docs/storage-lifecycle.md | 40 ++++++++++++--- src/api/storage.ts | 1 + src/gc.cron.ts | 3 ++ src/storage/config.ts | 10 +++- src/storage/gc.ts | 61 ++++++++++++++++++++--- test/integration/storageLifecycle.test.ts | 50 ++++++++++++++++++- 9 files changed, 153 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e94517a..14b5237 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "confirmationRequired": false, "temporaryTtlMs": 86400000, "gc": { - "enabled": false, + "enabled": true, "schedule": "0 */15 * * * *", "highWatermarkBytes": 53687091200, "lowWatermarkBytes": 42949672960 @@ -107,7 +107,7 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - Set `enableDebugApi: true` only when the authenticated debug route is operationally required - Tune the endpoint-specific `rateLimits` for the deployment perimeter - Review `storage.diskReserveBytes` and `storage.maxRequestSizeBytes` for the deployment volume; the defaults suit a dedicated disk -- Leave `storage.gc.enabled` as `false` until a deletion policy is agreed; `POST /api/storage/gc` runs on demand meanwhile +- `storage.gc.enabled` is on by default and frees blocks only when the blockstore passes `highWatermarkBytes` or free space falls into `diskReserveBytes`; released files stay readable until then - Set `replication.enabled` on every node that should place copies; replication needs no key and no extra address, because it runs on a libp2p protocol between the peers already listed in `nodes` - Tune `replication.placement` if the deployment wants a different number of copies per file age - `peeringSchedule` is optional and defaults to every thirty seconds; it redials the peers in `nodes` that are not connected, which `autoPeeringPeriod` never did diff --git a/config.default.json5 b/config.default.json5 index c7535e5..7050d3d 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -65,9 +65,10 @@ confirmationRequired: false, temporaryTtlMs: 86400000, // 24 h. Lifetime of an unconfirmed upload gc: { - // Controls the scheduled collector only. POST /api/storage/gc always runs - // on demand. Enable this after agreeing on a deletion policy. - enabled: false, + // Controls the scheduled collector only; POST /api/storage/gc always runs + // on demand. Blocks are freed only when space is short, so leaving this on + // costs nothing until one of the thresholds below is reached. + enabled: true, schedule: '0 */15 * * * *', highWatermarkBytes: 53687091200, // 50 GiB. Collection starts above this blockstore size lowWatermarkBytes: 42949672960 // 40 GiB. Collection reclaims down to this size diff --git a/config.test.json5 b/config.test.json5 index 1de222f..0762b67 100644 --- a/config.test.json5 +++ b/config.test.json5 @@ -33,7 +33,7 @@ confirmationRequired: false, temporaryTtlMs: 3600000, gc: { - enabled: false, + enabled: true, schedule: '0 */15 * * * *', highWatermarkBytes: 10485760, lowWatermarkBytes: 5242880 diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 4b3499b..a4a7344 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -115,18 +115,38 @@ Collection applies three independent rules: from four holders to two without ever losing durability; see [Replication and durability](#replication-and-durability). -Released files lose their pin, then Helia deletes every unpinned block. That -pass also reclaims blocks cached while serving other peers. +### Releasing is not deleting + +Losing a pin and losing the bytes are separate steps, and only the first one +follows from the rules above. + +A released file stays on disk, unprotected. It keeps answering reads for free, +so a node that handed a copy over still serves it until something else needs the +space. Deleting it eagerly would only mean fetching it again over the network +later. + +Blocks are deleted when space is actually short, which is either of: + +- the blockstore grew above `storage.gc.highWatermarkBytes`, or +- free space on the volume fell into `storage.diskReserveBytes` + +The second case matters on a disk smaller than the configured watermark, where +the ceiling would never be reached and the volume would fill instead. A report +says which of the two applied in its `trigger` field. + +That pass also reclaims blocks cached while serving reads for content this node +does not hold, which is the only thing that bounds the read cache. Confirmed files are never selected by the planner, and Helia never deletes a pinned block. Before deleting anything, the collector verifies that every -confirmed file is still protected and restores a missing pin first. +confirmed file it holds is still protected and restores a missing pin first. -`storage.gc.enabled` controls the **scheduled** collector and is `false` by -default: deletion in production must be agreed with the maintainers first. The -authorized `POST /api/storage/gc` endpoint always runs on demand, and -`?dryRun=true` reports the exact CIDs that would be released and retained without -touching a block. +`storage.gc.enabled` controls the **scheduled** collector. It is on by default, +which costs nothing while there is room: the collector releases what the rules +say, and frees bytes only under the pressure described above. The authorized +`POST /api/storage/gc` endpoint always runs on demand, `?dryRun=true` reports the +exact CIDs that would be released and retained without touching a block, and +`?force=true` frees them regardless of pressure. The `removedCids` field of a collection report identifies blocks, not files. The blockstore addresses content by multihash and rebuilds a CID with its own default @@ -231,6 +251,10 @@ age are a prefix of the same ranking, so the widest prefix any tier can ask for covers all of them. The read then talks to a handful of named nodes instead of depending on who happens to be connected. +Blocks fetched this way are written into the local blockstore, so the next read +of the same file is served locally instead of over the network again. They are +not pinned, which means they are reclaimed once space is short and not before. + A node that still cannot retrieve content answers `408` after `findFileTimeout`. It never answers `404`, because it cannot know that content does not exist. diff --git a/src/api/storage.ts b/src/api/storage.ts index 7ce657c..499512e 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -78,6 +78,7 @@ storageAdminRouter.post('/gc', async (req, res, next) => { durationMs: report.durationMs, dryRun: report.dryRun, collected: report.collected, + trigger: report.trigger, blockstoreBytesBefore: report.blockstoreBytesBefore, estimatedBytesAfter: report.estimatedBytesAfter, releasedCids: report.releasedCids, diff --git a/src/gc.cron.ts b/src/gc.cron.ts index e692f64..8619ae4 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -52,6 +52,8 @@ export async function collectGarbage( registry: fileRegistry, watermarks: config.storage.gc, blockstoreBytes: metrics.blockstoreBytes, + availableBytes: metrics.availableBytes, + reserveBytes: config.storage.diskReserveBytes, dryRun: options.dryRun, force: options.force, pinTimeoutMs: config.replication.requestTimeoutMs, @@ -92,6 +94,7 @@ export function getGarbageCollectionState() { startedAt: lastReport.startedAt, durationMs: lastReport.durationMs, collected: lastReport.collected, + trigger: lastReport.trigger, releasedFiles: lastReport.releasedCids.length, demotedFiles: lastReport.demoted.length, removedBlocks: lastReport.removedBlocks, diff --git a/src/storage/config.ts b/src/storage/config.ts index 2fc4dd9..06673cf 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -12,7 +12,13 @@ const DAY = 24 * 60 * 60 * 1000 * the two stops the collector from running on every tick near one threshold. */ export interface GarbageCollectionConfig { - /** Whether the scheduled collector runs. The admin endpoint works regardless. */ + /** + * Whether the scheduled collector runs. The admin endpoint works regardless. + * + * The collector frees blocks only when space is short, so leaving it on costs + * nothing until the blockstore passes `highWatermarkBytes` or free space + * falls into `storage.diskReserveBytes`. + */ enabled: boolean /** Collection schedule in cron format. */ schedule: string @@ -61,7 +67,7 @@ export const DEFAULT_STORAGE_CONFIG: StorageConfig = { confirmationRequired: false, temporaryTtlMs: 24 * 60 * 60 * 1000, gc: { - enabled: false, + enabled: true, schedule: '0 */15 * * * *', highWatermarkBytes: 50 * GiB, lowWatermarkBytes: 40 * GiB diff --git a/src/storage/gc.ts b/src/storage/gc.ts index ceaf65f..00b2ee4 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -10,14 +10,23 @@ export interface GcPlanInput { /** Current blockstore size in bytes. */ blockstoreBytes: number watermarks: Watermarks + /** Free bytes on the blockstore filesystem, when known. */ + availableBytes?: number + /** Free bytes uploads must never consume, when known. */ + reserveBytes?: number /** Every registered file; confirmed files are filtered out by the planner. */ records: FileRecord[] now?: number } +/** Why blocks are being deleted, or why they are not. */ +export type CollectionTrigger = 'none' | 'watermark' | 'disk-reserve' | 'forced' + export interface GcPlan { - /** True once the blockstore grew past the high watermark. */ + /** True once space is actually short. */ shouldCollect: boolean + /** What made collection necessary. */ + trigger: CollectionTrigger /** Files released by the TTL policy, independent of the watermarks. */ expired: FileRecord[] /** Additional files evicted to bring the blockstore below the low watermark. */ @@ -34,10 +43,10 @@ export interface GcPlan { * * 1. TTL: an abandoned upload is released once it outlives its expiry, whatever * the blockstore size is. - * 2. Watermarks: when the blockstore grows above the high watermark, the oldest - * unconfirmed files are evicted until the estimate drops below the low - * watermark. Hysteresis between the two thresholds stops the collector from - * running on every tick. + * 2. Watermarks: when the blockstore grows above the high watermark, or free + * space falls into the disk reserve, the oldest unconfirmed files are evicted + * until the estimate drops below the low watermark. Hysteresis between the + * two thresholds stops the collector from running on every tick. * * Confirmed files are never selected, so durable content cannot be reclaimed. */ @@ -56,7 +65,21 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { estimatedBytesAfter -= record.storedBytes } - const shouldCollect = input.blockstoreBytes > highWatermarkBytes + // Space is short when the blockstore passed its ceiling, or when the volume + // itself is running out. The second case matters on a disk smaller than the + // configured watermark, where the ceiling would never be reached. + const overWatermark = input.blockstoreBytes > highWatermarkBytes + const underReserve = + input.availableBytes !== undefined && + input.reserveBytes !== undefined && + input.availableBytes < input.reserveBytes + + const shouldCollect = overWatermark || underReserve + const trigger: CollectionTrigger = overWatermark + ? 'watermark' + : underReserve + ? 'disk-reserve' + : 'none' const evicted: FileRecord[] = [] if (shouldCollect) { @@ -78,6 +101,7 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { return { shouldCollect, + trigger, expired, evicted, retained: [...confirmed, ...alive.filter((record) => !releasedCids.has(record.cid))], @@ -90,6 +114,8 @@ export interface GcReport { durationMs: number dryRun: boolean collected: boolean + /** What made this run delete blocks, or why it did not. */ + trigger: CollectionTrigger blockstoreBytesBefore: number estimatedBytesAfter: number releasedCids: string[] @@ -116,6 +142,10 @@ export interface GcRunOptions { registry: FileRegistry watermarks: Watermarks blockstoreBytes: number + /** Free bytes on the blockstore filesystem. */ + availableBytes?: number + /** Free bytes uploads must never consume. */ + reserveBytes?: number /** Report what would happen without unpinning or deleting anything. */ dryRun?: boolean /** Collect even when the blockstore is below the high watermark. */ @@ -144,18 +174,26 @@ export async function runGarbageCollection(options: GcRunOptions): Promise 0 + + // Releasing and deleting are separate decisions. Losing a pin is policy: the + // file is no longer protected. Deleting is reclamation, and it waits until + // space is actually short, so an unpinned block keeps serving reads for free + // instead of being thrown away and fetched over the network again. + const collect = options.force === true || plan.shouldCollect const report: GcReport = { startedAt, durationMs: 0, dryRun: options.dryRun === true, collected: false, + trigger: options.force === true ? 'forced' : plan.trigger, blockstoreBytesBefore: options.blockstoreBytes, estimatedBytesAfter: plan.estimatedBytesAfter, releasedCids: released.map((record) => record.cid), @@ -195,7 +233,7 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { if (event.type === 'helia:gc:deleted') { diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 607c668..d13a10b 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -17,6 +17,11 @@ import { deterministicBytes } from '../fixtures.js' /** Only loopback, and port 0 so the OS picks a free port. */ const LISTEN = ['/ip4/127.0.0.1/tcp/0'] const WATERMARKS = { highWatermarkBytes: 1_000_000_000, lowWatermarkBytes: 500_000_000 } +/** + * Space is short, but releasing the expired file alone brings the estimate back + * under the low watermark, so nothing else has to be evicted. + */ +const TIGHT = { highWatermarkBytes: 3500, lowWatermarkBytes: 3100 } let storeDir: string let blockstore: FsBlockstore @@ -265,12 +270,13 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, - watermarks: WATERMARKS, + watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 }) assert.equal(report.collected, true) + assert.equal(report.trigger, 'watermark') assert.deepEqual(report.releasedCids, [cids.abandoned]) assert.deepEqual(report.retainedCids.sort(), [cids.confirmed, cids.live].sort()) assert.deepEqual(report.errors, []) @@ -285,6 +291,48 @@ describe('garbage collection against fixture data', () => { assert.equal((await registry.get(cids.confirmed))?.state, 'confirmed') }) + it('keeps unpinned blocks on disk while there is still room', async () => { + const { registry, cids } = await createFixture() + + const report = await runGarbageCollection({ + node, + registry, + watermarks: WATERMARKS, + blockstoreBytes: 4096, + now: 1000 + }) + + // The abandoned upload loses its pin, because that is policy. Its blocks and + // the unpinned cache stay, because reclaiming them would only cost a refetch + assert.equal(report.collected, false) + assert.equal(report.trigger, 'none') + assert.deepEqual(report.releasedCids, [cids.abandoned]) + assert.equal(report.removedBlocks, 0) + + const remaining = await storedDigests() + assert.equal(remaining.has(digestOf(cids.abandoned)), true) + assert.equal(remaining.has(digestOf(cids.cached)), true) + assert.equal((await registry.get(cids.abandoned))?.pinned, false) + }) + + it('reclaims once free space falls into the disk reserve', async () => { + const { registry, cids } = await createFixture() + + const report = await runGarbageCollection({ + node, + registry, + watermarks: WATERMARKS, + blockstoreBytes: 4096, + availableBytes: 1024, + reserveBytes: 4096, + now: 1000 + }) + + assert.equal(report.collected, true) + assert.equal(report.trigger, 'disk-reserve') + assert.equal((await storedDigests()).has(digestOf(cids.cached)), false) + }) + it('evicts a live upload too when the blockstore is above the high watermark', async () => { const { registry, cids } = await createFixture() From 4ee221dbe97bf6f6b0cf014dbfcc9df68e47e7fb Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Mon, 24 Aug 2026 12:55:02 +0300 Subject: [PATCH 107/167] feat(storage): ask a peer before sending it a file, and take orphans back Repair transferred a whole file to every designated peer that did not have it, on every pass, with no memory of what happened last time. A peer that was full, unreachable, or refusing was re-sent every affected file every thirty minutes, indefinitely. On a few thousand files that is a repeating transfer nobody asked for and a hosting bill nobody expected. Placement now asks first. A peer that already holds the file needs nothing, and a peer with no room is skipped after one short message instead of after a file it will refuse. Capacity is answered from the same disk reserve that refuses uploads, so a full node stops being chosen rather than accepting content it cannot keep. Retrieval reaches a little past a file's designated holders. The holders are computed from the current membership, and a file stored before a node joined or left can sit just outside that set; widening the ranking by a couple of places covers the drift. It is done in one pass, because a dial to an already-connected peer is skipped and a second round would add its own wait to every miss. Repair also takes back files that lost every copy. It only ever looked at files a node holds, so a file handed over to peers that later left was nobody's responsibility and disappeared while every registry still called it confirmed. A node now checks the files it released, and pins one again when no other node answers that it has it. The check is cheap: it starts with a local lookup and skips anything already reclaimed, and blocks survive until space runs short, so shortly after a handover they usually are still there. Co-Authored-By: Claude Opus 5 --- src/storage/replicationProtocol.ts | 37 +++++- src/storage/retrieval.ts | 22 +++- src/storage/service.ts | 123 ++++++++++++++++++- test/integration/replicationProtocol.test.ts | 21 +++- test/integration/retrieval.test.ts | 4 +- test/retrieval.test.ts | 12 +- 6 files changed, 209 insertions(+), 10 deletions(-) diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index 726376e..e88f7e1 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -21,10 +21,13 @@ export type ReplicationRequest = | { op: 'store'; cid: string } /** Report whether this node deliberately holds `cid`. */ | { op: 'have'; cid: string } + /** Report whether this node has room to take another copy. */ + | { op: 'accept'; cid: string } export type ReplicationResponse = | { ok: true; op: 'store'; storedBytes: number } | { ok: true; op: 'have'; has: boolean } + | { ok: true; op: 'accept'; willAccept: boolean } | { ok: false; error: string } /** @@ -45,6 +48,13 @@ export interface ReplicationHandlers { store(cid: string): Promise /** Whether this node holds `cid` durably, not merely cached. */ have(cid: string): Promise + /** + * Whether this node has room for another copy. + * + * Asked before a transfer starts, so a node that is full costs one short + * message instead of a whole file that it cannot keep. + */ + willAccept(): Promise onError?(message: string): void } @@ -96,7 +106,7 @@ function parseRequest(value: unknown): ReplicationRequest { throw new Error('Replication request is missing a CID') } - if (message.op !== 'store' && message.op !== 'have') { + if (message.op !== 'store' && message.op !== 'have' && message.op !== 'accept') { throw new Error('Unknown replication operation') } @@ -122,6 +132,15 @@ async function respond( return } + if (request.op === 'accept') { + await sendMessage(stream, { + ok: true, + op: 'accept', + willAccept: await handlers.willAccept() + }) + return + } + const storedBytes = await handlers.store(request.cid) await sendMessage(stream, { ok: true, op: 'store', storedBytes }) } @@ -187,6 +206,22 @@ export async function requestStore( return response.ok && response.op === 'store' ? response.storedBytes : 0 } +/** + * Ask a peer whether it has room for another copy. + * + * A peer that answers no is skipped, which is the difference between one short + * message and a whole file transferred to a node that will refuse it. + */ +export async function probeAccept( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + options: ReplicationCallOptions +): Promise { + const response = await call(node, peer, { op: 'accept', cid }, options) + return response.ok && response.op === 'accept' ? response.willAccept : false +} + /** Ask a peer whether it still holds a file durably. */ export async function probeHave( node: IpfsNode, diff --git a/src/storage/retrieval.ts b/src/storage/retrieval.ts index c712849..c24a289 100644 --- a/src/storage/retrieval.ts +++ b/src/storage/retrieval.ts @@ -11,6 +11,19 @@ import type { ReplicationPeer } from './replication.js' */ export const HOLDER_DIAL_TIMEOUT_MS = 5000 +/** + * Nodes contacted beyond a file's designated holders. + * + * The holders are computed from the current membership, and a file stored + * before a node joined or left can sit just outside that set. Widening the + * ranking by a couple of places covers that drift. + * + * The widening is applied in one pass rather than after a failed attempt: a + * dial to an already-connected peer is skipped, so the extra names usually cost + * nothing, and a second round would add its own wait to every miss. + */ +export const RETRIEVAL_WIDENING = 2 + /** * The nodes worth asking for a CID this node does not hold. * @@ -20,6 +33,10 @@ export const HOLDER_DIAL_TIMEOUT_MS = 5000 * age, without knowing when it was uploaded. That matters here, because a node * asked for a file it never stored has no record of it. * + * A few places beyond the widest tier are included as well, so a file that sits + * just outside the current holder set is still found; see + * {@link RETRIEVAL_WIDENING}. + * * With replication disabled there are no designated holders, so every known * node is a candidate. */ @@ -27,7 +44,8 @@ export function retrievalTargets( cid: string, config: ReplicationConfig, selfPeerId: string, - peers: ReplicationPeer[] + peers: ReplicationPeer[], + widening: number = RETRIEVAL_WIDENING ): ReplicationPeer[] { if (!config.enabled) { return peers @@ -36,7 +54,7 @@ export function retrievalTargets( const widest = Math.max(...config.placement.map((tier) => tier.copies)) const ranked = rankHolders(cid, [selfPeerId, ...peers.map((peer) => peer.peerId)]).slice( 0, - widest + widest + widening ) const wanted = new Set(ranked) diff --git a/src/storage/service.ts b/src/storage/service.ts index 4c27414..b049d25 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -2,7 +2,9 @@ import { CID } from 'multiformats/cid' import { config } from '../config.js' import { helia, ifs } from '../helia.js' import { logger } from '../utils/logger.js' +import { availableStorageSize } from '../utils/utils.js' import { getNodesList } from '../utils/utils.js' +import { blockstorePath } from '../store.js' import { mayDemote, placeFile, storageTargets, type Placement } from './placement.js' import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' import type { FileRecord } from './registry.js' @@ -13,6 +15,7 @@ import { type ReplicationReport } from './replication.js' import { + probeAccept, probeHave, requestStore, type ReplicationHandlers, @@ -72,6 +75,18 @@ export async function replicateFile(cid: string): Promise { peers: getReplicationPeers(), config: config.replication, store: async (peer) => { + // Ask before sending. A peer that already holds the file needs nothing, + // and a peer with no room should cost one short message rather than a + // whole transfer it will refuse. Repair runs on a schedule, so without + // this a peer that cannot take a file is re-sent it forever. + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return + } + + if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { + throw new Error('peer has no room for another copy') + } + await requestStore(helia, peer.multiAddr, cid, callOptions()) } }) @@ -188,15 +203,108 @@ export function createReplicationHandlers(): ReplicationHandlers { isAuthorized: (peerId) => getReplicationPeers().some((peer) => peer.peerId === peerId), store: async (cid) => (await acceptReplica(cid)).storedBytes, have: async (cid) => isDirectlyPinned(helia, CID.parse(cid)), + willAccept: hasRoomForAnotherCopy, onError: (message) => logger.warn(message) } } +/** + * Whether this node can take another copy without eating into the reserve. + * + * The same reserve that refuses uploads refuses copies from peers, so a full + * node stops being chosen instead of accepting content it cannot keep. + */ +export async function hasRoomForAnotherCopy(): Promise { + try { + const available = Number(await availableStorageSize(blockstorePath)) + return available > config.storage.diskReserveBytes + } catch { + return false + } +} + export interface RepairReport { checked: number underReplicated: number repaired: string[] stillMissing: string[] + /** Files nobody was found holding, whose local blocks were pinned again. */ + rescued: string[] +} + +/** + * Files checked per pass for having lost every copy. + * + * Bounded so the probes stay cheap on a node with many records. Candidates + * rotate, so everything is covered across a few passes. + */ +const RESCUE_BATCH = 50 + +/** + * Take a released file back when no other node is holding it. + * + * Repair only looks at files a node holds, so a file handed over to peers that + * later left is nobody's responsibility and disappears silently while every + * registry still calls it confirmed. + * + * The check is cheap because it starts with a local lookup: a node can only + * rescue a file whose blocks it still has, and blocks are kept until space runs + * short, so shortly after a handover they usually are. Anything already + * reclaimed is skipped without touching the network. + */ +async function rescueOrphanedFiles( + records: FileRecord[], + peers: ReplicationPeer[] +): Promise { + const self = selfPeerId() + const rescued: string[] = [] + const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) + + const candidates = records + .filter((record) => record.state === 'confirmed' && !record.heldLocally) + .slice(0, RESCUE_BATCH) + + for (const record of candidates) { + const cid = CID.parse(record.cid) + + if (!(await helia.blockstore.has(cid))) { + continue + } + + const holders = storageTargets(placementFor(record.cid, record.createdAt, peers), self) + .map((peerId) => byPeerId.get(peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) + + const answers = await Promise.all( + holders.map(async (peer) => { + try { + return await probeHave(helia, peer.multiAddr, record.cid, callOptions()) + } catch { + return false + } + }) + ) + + if (answers.some(Boolean)) { + continue + } + + try { + // Only worth pinning if the whole DAG is still here; an offline stat says + // so without going near the network. + await ifs.stat(cid, { extended: true, offline: true }) + await pinFile(helia, cid) + await fileRegistry.setPinned(record.cid, true) + await fileRegistry.save({ ...record, pinned: true, heldLocally: true }) + rescued.push(record.cid) + logger.warn(`No node was holding ${record.cid}; kept the local copy instead`) + } catch { + // The blocks are only partly here, so there is nothing to rescue + continue + } + } + + return rescued } /** @@ -207,7 +315,13 @@ export interface RepairReport { * responsibility. */ export async function repairReplication(): Promise { - const report: RepairReport = { checked: 0, underReplicated: 0, repaired: [], stillMissing: [] } + const report: RepairReport = { + checked: 0, + underReplicated: 0, + repaired: [], + stillMissing: [], + rescued: [] + } if (!config.replication.enabled) { return report @@ -215,9 +329,8 @@ export async function repairReplication(): Promise { const peers = getReplicationPeers() const self = selfPeerId() - const candidates = (await fileRegistry.all()).filter( - (record) => record.state === 'confirmed' && record.heldLocally - ) + const records = await fileRegistry.all() + const candidates = records.filter((record) => record.state === 'confirmed' && record.heldLocally) report.checked = candidates.length @@ -238,6 +351,8 @@ export async function repairReplication(): Promise { } } + report.rescued = await rescueOrphanedFiles(records, peers) + return report } diff --git a/test/integration/replicationProtocol.test.ts b/test/integration/replicationProtocol.test.ts index 5ce8c6a..85240a8 100644 --- a/test/integration/replicationProtocol.test.ts +++ b/test/integration/replicationProtocol.test.ts @@ -10,6 +10,7 @@ import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { isDirectlyPinned, pinFile } from '../../src/storage/pinning.js' import { + probeAccept, probeHave, registerReplicationProtocol, requestStore, @@ -53,6 +54,7 @@ let senderFs: UnixFS let holderFs: UnixFS let authorized: Set let refusals: string[] +let hasRoom = true before(async () => { sender = await startNode() @@ -71,6 +73,7 @@ before(async () => { return Number(stats.deduplicatedDagSize) }, have: async (cid) => isDirectlyPinned(holder.node, CID.parse(cid)), + willAccept: async () => hasRoom, onError: (message) => refusals.push(message) } @@ -126,7 +129,23 @@ describe('replication over libp2p', () => { assert.equal(await probeHave(sender.node, peer, absent.toString(), CALL), false) }) - it('refuses a peer that is not an ADAMANT node', async () => { + it('says whether it has room before anything is transferred', async () => { + const cid = await senderFs.addBytes(deterministicBytes(1024, 'capacity')) + const peer = holder.node.libp2p.getMultiaddrs()[0] + + assert.equal(await probeAccept(sender.node, peer, cid.toString(), CALL), true) + + hasRoom = false + try { + assert.equal(await probeAccept(sender.node, peer, cid.toString(), CALL), false) + // The question is answered without the file moving anywhere + assert.equal(await isDirectlyPinned(holder.node, cid), false) + } finally { + hasRoom = true + } + }) + + it('refuses a peer that is not a known node', async () => { const cid = await senderFs.addBytes(deterministicBytes(1024, 'unauthorized')) const peer = holder.node.libp2p.getMultiaddrs()[0] diff --git a/test/integration/retrieval.test.ts b/test/integration/retrieval.test.ts index 1ea45ce..ee39fbe 100644 --- a/test/integration/retrieval.test.ts +++ b/test/integration/retrieval.test.ts @@ -96,7 +96,9 @@ describe('retrieval from an unconnected holder', () => { const opened = await connectToHolders(reader.node, [target]) assert.equal(opened, 1) - assert.equal(await canRead(readerFs, cid, 10000), true) + // Generous: the point is that connecting makes the read possible at all, + // not how quickly it finishes on a loaded machine running other suites + assert.equal(await canRead(readerFs, cid, 30000), true) }) it('opens nothing when the holder is already connected', async () => { diff --git a/test/retrieval.test.ts b/test/retrieval.test.ts index 587f762..ce08a2a 100644 --- a/test/retrieval.test.ts +++ b/test/retrieval.test.ts @@ -36,10 +36,20 @@ describe('retrievalTargets', () => { it('asks a handful of nodes rather than the whole network', () => { const targets = retrievalTargets(CID, config, SELF, PEERS) - assert.ok(targets.length <= 4, `asked ${targets.length} of ${PEERS.length}`) + assert.ok(targets.length <= 6, `asked ${targets.length} of ${PEERS.length}`) assert.ok(targets.length > 0) }) + it('reaches a little past the designated holders, to cover membership drift', () => { + const exact = retrievalTargets(CID, config, SELF, PEERS, 0) + const widened = retrievalTargets(CID, config, SELF, PEERS) + + assert.ok(widened.length > exact.length) + for (const target of exact) { + assert.ok(widened.some((item) => item.peerId === target.peerId)) + } + }) + it('covers the holders of a file of any age', () => { // A node asked for a file it never stored does not know how old it is, so // the target set has to be right without that knowledge From df30792c4a531158ab013131e8692384ff1b2ccc Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Mon, 24 Aug 2026 15:24:15 +0300 Subject: [PATCH 108/167] fix(storage): bound the periodic sweeps and let them advance Repair and handover walked every registered file on every pass, asking peers about each one. On a node with many records that is steady chatter forever, and repair may also transfer a file, so the cost is not only messages. The rescue sweep was capped, but the cap was a slice from the front of a stable list, so it examined the same fifty files on every pass and never reached the rest. A cap without a moving cursor is not a batch, it is a blind spot. All three sweeps now take a bounded batch that wraps around and remembers where it stopped, so everything is covered across passes and no pass is unbounded. Repair gets the smallest batch because it is the one that can move a file. Co-Authored-By: Claude Opus 5 --- src/storage/service.ts | 28 ++++++++++----------- src/storage/sweep.ts | 42 +++++++++++++++++++++++++++++++ test/sweepBatch.test.ts | 56 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 src/storage/sweep.ts create mode 100644 test/sweepBatch.test.ts diff --git a/src/storage/service.ts b/src/storage/service.ts index b049d25..12f51b6 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -23,6 +23,7 @@ import { } from './replicationProtocol.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { fileRegistry } from './state.js' +import { nextSweepBatch } from './sweep.js' const callOptions = (): ReplicationCallOptions => ({ timeoutMs: config.replication.requestTimeoutMs @@ -232,14 +233,6 @@ export interface RepairReport { rescued: string[] } -/** - * Files checked per pass for having lost every copy. - * - * Bounded so the probes stay cheap on a node with many records. Candidates - * rotate, so everything is covered across a few passes. - */ -const RESCUE_BATCH = 50 - /** * Take a released file back when no other node is holding it. * @@ -260,9 +253,10 @@ async function rescueOrphanedFiles( const rescued: string[] = [] const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) - const candidates = records - .filter((record) => record.state === 'confirmed' && !record.heldLocally) - .slice(0, RESCUE_BATCH) + const candidates = nextSweepBatch( + 'rescue', + records.filter((record) => record.state === 'confirmed' && !record.heldLocally) + ) for (const record of candidates) { const cid = CID.parse(record.cid) @@ -330,7 +324,10 @@ export async function repairReplication(): Promise { const peers = getReplicationPeers() const self = selfPeerId() const records = await fileRegistry.all() - const candidates = records.filter((record) => record.state === 'confirmed' && record.heldLocally) + const candidates = nextSweepBatch( + 'repair', + records.filter((record) => record.state === 'confirmed' && record.heldLocally) + ) report.checked = candidates.length @@ -388,8 +385,11 @@ export async function demoteReleasableCopies(): Promise { const self = selfPeerId() const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) - const candidates = (await fileRegistry.all()).filter( - (record) => record.state === 'confirmed' && record.heldLocally + const candidates = nextSweepBatch( + 'demote', + (await fileRegistry.all()).filter( + (record) => record.state === 'confirmed' && record.heldLocally + ) ) report.checked = candidates.length diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts new file mode 100644 index 0000000..03bc19c --- /dev/null +++ b/src/storage/sweep.ts @@ -0,0 +1,42 @@ +/** + * How many files each periodic sweep looks at per pass. + * + * Every sweep asks peers something, so an unbounded one turns a node with many + * records into a source of steady chatter. Repair may transfer a file, so it + * gets the smallest batch; the others only exchange short messages. + */ +export const SWEEP_BATCHES = { repair: 50, demote: 200, rescue: 50 } + +export type SweepName = keyof typeof SWEEP_BATCHES + +/** Where each sweep stopped last time, so the next pass continues from there. */ +const cursors = new Map() + +/** + * Take the next batch of a periodic sweep, continuing where it left off. + * + * Slicing from the front would examine the same files on every pass and never + * reach the rest, so the batch wraps around the end and the cursor moves with + * it. A cursor pointing at a record that has since gone simply starts over. + * + * @param sweep Which sweep is asking; each keeps its own position + * @param records Every candidate, in a stable order + */ +export function nextSweepBatch(sweep: SweepName, records: T[]): T[] { + const size = SWEEP_BATCHES[sweep] + + if (records.length <= size) { + cursors.delete(sweep) + return records + } + + const previous = cursors.get(sweep) + const resumeAt = + previous === undefined ? 0 : records.findIndex((item) => item.cid === previous) + 1 + const start = resumeAt > 0 && resumeAt < records.length ? resumeAt : 0 + + const batch = [...records.slice(start), ...records.slice(0, start)].slice(0, size) + cursors.set(sweep, batch[batch.length - 1].cid) + + return batch +} diff --git a/test/sweepBatch.test.ts b/test/sweepBatch.test.ts new file mode 100644 index 0000000..76ecc1d --- /dev/null +++ b/test/sweepBatch.test.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { nextSweepBatch, SWEEP_BATCHES } from '../src/storage/sweep.js' + +const records = (count: number) => + Array.from({ length: count }, (unused, i) => ({ cid: `cid-${i}` })) + +describe('nextSweepBatch', () => { + it('returns everything when the set fits in one pass', () => { + const all = records(3) + + assert.deepEqual(nextSweepBatch('rescue', all), all) + }) + + it('covers every record across passes instead of repeating the first ones', () => { + const size = SWEEP_BATCHES.rescue + const all = records(size * 2 + 7) + const seen = new Set() + + // Three passes is more than enough to walk a set of this size + for (let pass = 0; pass < 3; pass += 1) { + for (const record of nextSweepBatch('rescue', all)) { + seen.add(record.cid) + } + } + + assert.equal(seen.size, all.length, 'every record must be looked at') + }) + + it('never hands back more than the batch size', () => { + const all = records(SWEEP_BATCHES.demote * 3) + + assert.equal(nextSweepBatch('demote', all).length, SWEEP_BATCHES.demote) + }) + + it('keeps a cursor per sweep, so one does not move another', () => { + const all = records(SWEEP_BATCHES.repair + 5) + + const firstRepair = nextSweepBatch('repair', all) + nextSweepBatch('rescue', all) + const secondRepair = nextSweepBatch('repair', all) + + assert.notDeepEqual(firstRepair[0], secondRepair[0]) + }) + + it('starts over when the record it stopped at is gone', () => { + const all = records(SWEEP_BATCHES.rescue + 3) + nextSweepBatch('rescue', all) + + const replaced = records(SWEEP_BATCHES.rescue + 3).map((record) => ({ + cid: `${record.cid}-new` + })) + + assert.equal(nextSweepBatch('rescue', replaced).length, SWEEP_BATCHES.rescue) + }) +}) From 9841f94a3cd9f94d3876b51d13df3898466b2550 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Mon, 24 Aug 2026 18:29:02 +0300 Subject: [PATCH 109/167] feat(storage): spread a file even when peers do not know the node yet A node accepts responsibility for a file only from a peer it was configured with, so a node nobody has listed kept everything uploaded to it in a single copy. Measured on four nodes: the push was refused as "Not authorized", the file lived on one node, and killing that node lost it. Such a node could not be given to users, because uploading to it had no redundancy at all. A peer that refuses responsibility is now asked for something weaker: hold the blocks without pinning them. It can serve the file from then on and promises nothing, so the copy sits in the same tier as read cache and goes when that node needs the space. This grants a stranger nothing new. Anyone who knows a CID can already make a node fetch and cache those blocks by asking it to serve them; this is the same effect with a different trigger, bounded by the same disk reserve. Pinning, registration, and counting as a holder all stay behind the configuration check. Nothing is counted as durable that is not: reports list such peers under `cached`, separately from `replicas`, and the acknowledged count ignores them. Verified on four nodes, three configured and one known to nobody: a file uploaded to the outsider is now held by a configured node as well, and survives the outsider being killed, reading back byte-identical from all three. Co-Authored-By: Claude Opus 5 --- docs/storage-lifecycle.md | 24 +++++++ src/storage/replication.ts | 38 ++++++++-- src/storage/replicationProtocol.ts | 63 ++++++++++++++-- src/storage/service.ts | 75 ++++++++++++++++---- test/integration/replicationProtocol.test.ts | 7 ++ test/replication.test.ts | 44 +++++++++++- 6 files changed, 223 insertions(+), 28 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index a4a7344..9307c87 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -212,6 +212,30 @@ nowhere for a copy to move. | 4 | 4 | 4, all of them | 3 | never | | 6 | 4 | 4 | 3 or 4 | allowed for the two outside the set | +### A node its peers do not know yet + +A node accepts responsibility for a file only from a peer it has been +configured with, because storing content permanently is a cost somebody has to +have agreed to. A node that nobody has listed yet would therefore keep +everything uploaded to it in a single copy, which disappears with it. + +So a peer that refuses responsibility is asked for something weaker instead: to +hold the blocks without pinning them. It can serve the file from then on, and it +promises nothing — the copy sits in the same tier as read cache and goes when +that node needs the space. Nothing is counted as durable that is not: an upload +report lists such peers under `cached`, separately from `replicas`, and the +acknowledged count ignores them. + +This grants a stranger nothing it did not already have. Anyone who knows a CID +can already make a node fetch and cache those blocks by asking it to serve them; +this is the same effect with a different trigger, bounded by the same disk +reserve. What stays behind the configuration check is everything that makes a +node responsible: pinning, registration, and being counted as a holder. + +It is a stopgap, not the answer. A file spread this way survives the loss of the +node it was uploaded to, but only until its peers need the space, and no repair +job will notice if the copies go. Node discovery removes the need for it. + ### Handing a copy over As a file ages its desired holder count shrinks, so nodes that are no longer diff --git a/src/storage/replication.ts b/src/storage/replication.ts index 0ee2e97..487cfe3 100644 --- a/src/storage/replication.ts +++ b/src/storage/replication.ts @@ -9,9 +9,19 @@ export interface ReplicationPeer { multiAddr: Multiaddr } +/** + * What a peer did with a copy. + * + * `stored` means it pinned the file and is responsible for it. `cached` means + * it holds the blocks and can serve them, but promises nothing: the copy sits + * in the same tier as read cache and goes when the peer needs the space. + */ +export type PlacementOutcome = 'stored' | 'cached' + export interface ReplicationAck { node: string ok: boolean + outcome?: PlacementOutcome error?: string } @@ -29,8 +39,15 @@ export interface ReplicationReport { required: number /** Copies confirmed right now, including the one on this node. */ acknowledged: number - /** Peer nodes that acknowledged holding a copy. */ + /** Peer nodes that took responsibility for a copy. */ replicas: string[] + /** + * Peer nodes holding an unpinned copy. + * + * They keep the file readable without promising to keep it, which is what a + * node gets when its peers do not know it yet. + */ + cached: string[] satisfied: boolean /** * True when the network is no larger than the desired copy count, so every @@ -47,8 +64,8 @@ export interface ReplicateOptions { selfPeerId: string peers: ReplicationPeer[] config: ReplicationConfig - /** Asks one peer to store and pin the file. */ - store: (peer: ReplicationPeer, cid: string) => Promise + /** Asks one peer to take a copy, and reports what it agreed to. */ + store: (peer: ReplicationPeer, cid: string) => Promise } /** @@ -84,6 +101,7 @@ export async function replicate(options: ReplicateOptions): Promise => { try { - await options.store(peer, cid) - return { node: peer.name, ok: true } + return { node: peer.name, ok: true, outcome: await options.store(peer, cid) } } catch (err) { return { node: peer.name, ok: false, error: (err as Error).message } } }) ) - const replicas = attempts.filter((attempt) => attempt.ok).map((attempt) => attempt.node) + // Only a pinned copy counts towards durability; a cached one can vanish the + // moment its peer needs the space. + const replicas = attempts + .filter((attempt) => attempt.outcome === 'stored') + .map((attempt) => attempt.node) + const cached = attempts + .filter((attempt) => attempt.outcome === 'cached') + .map((attempt) => attempt.node) + // The local copy counts: it is pinned before any peer is asked. const acknowledged = replicas.length + 1 const required = requiredAcks(config, placement) @@ -124,6 +149,7 @@ export async function replicate(options: ReplicateOptions): Promise= required, networkTooSmall: placement.networkTooSmall, attempts diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index e88f7e1..b1c7059 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -23,11 +23,20 @@ export type ReplicationRequest = | { op: 'have'; cid: string } /** Report whether this node has room to take another copy. */ | { op: 'accept'; cid: string } + /** + * Hold a copy without taking responsibility for it. + * + * Accepted from any peer, because it grants nothing a reader does not already + * have: the blocks are unpinned, so they are reclaimed as soon as space is + * short, exactly like content cached while answering a read. + */ + | { op: 'cache'; cid: string } export type ReplicationResponse = | { ok: true; op: 'store'; storedBytes: number } | { ok: true; op: 'have'; has: boolean } | { ok: true; op: 'accept'; willAccept: boolean } + | { ok: true; op: 'cache'; cachedBytes: number } | { ok: false; error: string } /** @@ -55,6 +64,13 @@ export interface ReplicationHandlers { * message instead of a whole file that it cannot keep. */ willAccept(): Promise + /** + * Pull `cid` into the local blockstore without pinning or registering it. + * + * The node can serve the file from now on, and gives up nothing: the blocks + * live in the same tier as read cache and go when space is needed. + */ + cacheCopy(cid: string): Promise onError?(message: string): void } @@ -106,11 +122,14 @@ function parseRequest(value: unknown): ReplicationRequest { throw new Error('Replication request is missing a CID') } - if (message.op !== 'store' && message.op !== 'have' && message.op !== 'accept') { + const operations: ReplicationRequest['op'][] = ['store', 'have', 'accept', 'cache'] + const op = operations.find((known) => known === message.op) + + if (op === undefined) { throw new Error('Unknown replication operation') } - return { op: message.op, cid: message.cid } + return { op, cid: message.cid } } async function respond( @@ -118,14 +137,29 @@ async function respond( connection: Connection, handlers: ReplicationHandlers ): Promise { - const peerId = connection.remotePeer.toString() + const request = parseRequest(await readMessage(stream)) - if (!handlers.isAuthorized(peerId)) { - await sendMessage(stream, { ok: false, error: 'Not authorized' }) + // Holding an extra copy is open to anyone, because it costs no more than a + // read from the same peer would. Everything that makes this node responsible + // for content stays behind the authorization check. + if (request.op === 'cache') { + if (!(await handlers.willAccept())) { + await sendMessage(stream, { ok: false, error: 'No room for another copy' }) + return + } + + await sendMessage(stream, { + ok: true, + op: 'cache', + cachedBytes: await handlers.cacheCopy(request.cid) + }) return } - const request = parseRequest(await readMessage(stream)) + if (!handlers.isAuthorized(connection.remotePeer.toString())) { + await sendMessage(stream, { ok: false, error: 'Not authorized' }) + return + } if (request.op === 'have') { await sendMessage(stream, { ok: true, op: 'have', has: await handlers.have(request.cid) }) @@ -222,6 +256,23 @@ export async function probeAccept( return response.ok && response.op === 'accept' ? response.willAccept : false } +/** + * Ask a peer to hold an extra copy it is not responsible for. + * + * Used when a peer will not accept a copy it has to keep, which is what happens + * while this node is not in its configuration. The file still spreads and stays + * readable from that peer; nobody promises to keep it there. + */ +export async function requestCache( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + options: ReplicationCallOptions +): Promise { + const response = await call(node, peer, { op: 'cache', cid }, options) + return response.ok && response.op === 'cache' ? response.cachedBytes : 0 +} + /** Ask a peer whether it still holds a file durably. */ export async function probeHave( node: IpfsNode, diff --git a/src/storage/service.ts b/src/storage/service.ts index 12f51b6..c4fa8df 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -11,12 +11,14 @@ import type { FileRecord } from './registry.js' import { isUnderReplicated, replicate, + type PlacementOutcome, type ReplicationPeer, type ReplicationReport } from './replication.js' import { probeAccept, probeHave, + requestCache, requestStore, type ReplicationHandlers, type ReplicationCallOptions @@ -75,21 +77,7 @@ export async function replicateFile(cid: string): Promise { selfPeerId: selfPeerId(), peers: getReplicationPeers(), config: config.replication, - store: async (peer) => { - // Ask before sending. A peer that already holds the file needs nothing, - // and a peer with no room should cost one short message rather than a - // whole transfer it will refuse. Repair runs on a schedule, so without - // this a peer that cannot take a file is re-sent it forever. - if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { - return - } - - if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { - throw new Error('peer has no room for another copy') - } - - await requestStore(helia, peer.multiAddr, cid, callOptions()) - } + store: (peer) => placeCopy(peer, cid) }) if (report.mode === 'quorum') { @@ -119,6 +107,42 @@ export async function prepareFileRetrieval(cid: CID): Promise { ) } +/** + * Ask one peer to take a copy, and report what it agreed to. + * + * A peer that already holds the file needs nothing, and a peer with no room is + * skipped after one short message rather than after a transfer it will refuse. + * Repair runs on a schedule, so without those questions a peer that cannot take + * a file is re-sent it forever. + * + * A peer that does not know this node refuses to become responsible for the + * file, which is what happens to a node nobody has configured yet. Its content + * would otherwise exist in one copy and disappear with it. So the file is + * offered as an unpinned copy instead: the peer can serve it from then on, and + * nothing is counted as durable that is not. + */ +async function placeCopy(peer: ReplicationPeer, cid: string): Promise { + try { + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return 'stored' + } + + if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { + throw new Error('peer has no room for another copy') + } + + await requestStore(helia, peer.multiAddr, cid, callOptions()) + return 'stored' + } catch (err) { + if (!(err as Error).message.includes('Not authorized')) { + throw err + } + + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return 'cached' + } +} + /** * Pin content that is not in the registry yet and record it as durable. * @@ -205,6 +229,7 @@ export function createReplicationHandlers(): ReplicationHandlers { store: async (cid) => (await acceptReplica(cid)).storedBytes, have: async (cid) => isDirectlyPinned(helia, CID.parse(cid)), willAccept: hasRoomForAnotherCopy, + cacheCopy: cacheFileLocally, onError: (message) => logger.warn(message) } } @@ -224,6 +249,26 @@ export async function hasRoomForAnotherCopy(): Promise { } } +/** + * Pull a file into the blockstore without pinning or registering it. + * + * Draining the content is what fetches every block of the DAG, and it leaves + * them exactly where a read would: unpinned, reclaimed when space is short. The + * node can serve the file from now on without promising to keep it. + * + * @returns Bytes pulled + */ +export async function cacheFileLocally(cid: string): Promise { + const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) + let bytes = 0 + + for await (const chunk of ifs.cat(CID.parse(cid), { signal })) { + bytes += chunk.byteLength + } + + return bytes +} + export interface RepairReport { checked: number underReplicated: number diff --git a/test/integration/replicationProtocol.test.ts b/test/integration/replicationProtocol.test.ts index 85240a8..72e2f02 100644 --- a/test/integration/replicationProtocol.test.ts +++ b/test/integration/replicationProtocol.test.ts @@ -74,6 +74,13 @@ before(async () => { }, have: async (cid) => isDirectlyPinned(holder.node, CID.parse(cid)), willAccept: async () => hasRoom, + cacheCopy: async (cid) => { + let bytes = 0 + for await (const chunk of holderFs.cat(CID.parse(cid))) { + bytes += chunk.byteLength + } + return bytes + }, onError: (message) => refusals.push(message) } diff --git a/test/replication.test.ts b/test/replication.test.ts index 5668e6e..16afa3d 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -71,6 +71,7 @@ describe('replicate', () => { config: baseConfig, store: async (target) => { asked.push(target.name) + return 'stored' } }) @@ -92,6 +93,7 @@ describe('replicate', () => { config: baseConfig, store: async (target) => { askedFresh.push(target.name) + return 'stored' } }) await replicate({ @@ -102,6 +104,7 @@ describe('replicate', () => { config: baseConfig, store: async (target) => { askedOld.push(target.name) + return 'stored' } }) @@ -119,6 +122,7 @@ describe('replicate', () => { if (target.name !== storageTargets(placementFor(0), SELF)[0].replace('peer-id-', '')) { throw new Error('unreachable') } + return 'stored' } }) @@ -152,7 +156,7 @@ describe('replicate', () => { selfPeerId: SELF, peers: single, config: { ...baseConfig, ackQuorum: 3 }, - store: async () => {} + store: async () => 'stored' }) assert.equal(report.networkTooSmall, true) @@ -161,6 +165,44 @@ describe('replicate', () => { }) }) +describe('placement outcomes', () => { + it('does not count an unpinned copy towards durability', async () => { + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async () => 'cached' + }) + + // Peers hold the file and can serve it, but none of them promised to keep it + assert.deepEqual(report.replicas, []) + assert.ok(report.cached.length > 0) + assert.equal(report.acknowledged, 1) + }) + + it('reports stored and cached copies separately', async () => { + let first = true + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async () => { + const outcome: 'stored' | 'cached' = first ? 'stored' : 'cached' + first = false + return outcome + } + }) + + assert.equal(report.replicas.length, 1) + assert.equal(report.cached.length, report.attempts.length - 1) + assert.equal(report.acknowledged, 2) + }) +}) + describe('requiredAcks', () => { it('never exceeds the copies that can exist', () => { assert.equal(requiredAcks({ ...baseConfig, ackQuorum: 3 }, placementFor(0)), 3) From 2d2b8a0b79aaf2b51aaa357679b50e31760e1b00 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Mon, 24 Aug 2026 18:46:16 +0300 Subject: [PATCH 110/167] fix(storage): frame replication messages by length, not by end of stream The protocol used the end of the stream as the message delimiter: each side sent its message and closed its writing end, and the reader consumed until EOF. That only works when the half-close reaches the other side promptly. When it did not, the reader waited for bytes that had already been sent and the writer waited for a reply, until the call hit its fifteen second timeout and reset the stream. It surfaced as replication that intermittently placed nothing. Repair took forty-five seconds to repair none of three files, while the receiving node logged a reset without ever having parsed the request. An earlier occurrence of the same signature was put down to bitswap needing time to learn its peers after a restart; that explanation was wrong, and this is the actual cause. Messages now carry a four byte length, so a reader knows when one is complete without depending on the stream ending. The same repair pass now finishes in under a third of a second. Also spreads a file nobody will pin more widely than a pinned one, since several copies that may be reclaimed at any time outlive a single one, and logs which peer was refused what, so a node that is not in a peer's configuration is visible rather than silent. Co-Authored-By: Claude Opus 5 --- docs/storage-lifecycle.md | 10 +++- src/storage/replication.ts | 45 +++++++++++++- src/storage/replicationProtocol.ts | 95 ++++++++++++++++++++---------- src/storage/service.ts | 9 ++- test/replication.test.ts | 40 +++++++++++++ 5 files changed, 163 insertions(+), 36 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 9307c87..f88e4f9 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -220,7 +220,9 @@ have agreed to. A node that nobody has listed yet would therefore keep everything uploaded to it in a single copy, which disappears with it. So a peer that refuses responsibility is asked for something weaker instead: to -hold the blocks without pinning them. It can serve the file from then on, and it +hold the blocks without pinning them. Because such a copy lasts only until its +node needs the space, the file is spread to a few more nodes than a pinned one +would be: several fragile copies outlive a single one. It can serve the file from then on, and it promises nothing — the copy sits in the same tier as read cache and goes when that node needs the space. Nothing is counted as durable that is not: an upload report lists such peers under `cached`, separately from `replicas`, and the @@ -236,6 +238,12 @@ It is a stopgap, not the answer. A file spread this way survives the loss of the node it was uploaded to, but only until its peers need the space, and no repair job will notice if the copies go. Node discovery removes the need for it. +Nothing has to be redone once the node becomes known. Repair already treats such +a file as under-replicated, so its next pass asks the same peers again, and this +time they accept responsibility. The blocks are already on those nodes, so +pinning them is a local operation: no file crosses the network a second time, +and no node has to be restarted for it. + ### Handing a copy over As a file ages its desired holder count shrinks, so nodes that are no longer diff --git a/src/storage/replication.ts b/src/storage/replication.ts index 487cfe3..2c79ffd 100644 --- a/src/storage/replication.ts +++ b/src/storage/replication.ts @@ -1,6 +1,15 @@ import type { Multiaddr } from '@multiformats/multiaddr' import type { ReplicationConfig } from './config.js' -import { placeFile, storageTargets, type Placement } from './placement.js' +import { placeFile, rankHolders, storageTargets, type Placement } from './placement.js' + +/** + * Extra nodes asked to hold an unpinned copy. + * + * An unpinned copy lasts only until its node needs the space, so spreading a + * file that nobody will pin is worth doing more widely than one that is pinned. + * Several fragile copies outlive a single one. + */ +export const CACHE_WIDENING = 2 /** Another ADAMANT node this one can reach over libp2p. */ export interface ReplicationPeer { @@ -66,6 +75,8 @@ export interface ReplicateOptions { config: ReplicationConfig /** Asks one peer to take a copy, and reports what it agreed to. */ store: (peer: ReplicationPeer, cid: string) => Promise + /** Asks one peer for an unpinned copy, used to widen a spread nobody pins. */ + cacheOnly?: (peer: ReplicationPeer, cid: string) => Promise } /** @@ -138,6 +149,38 @@ export async function replicate(options: ReplicateOptions): Promise attempt.outcome === 'cached') .map((attempt) => attempt.node) + // Nobody took responsibility, which is what a node its peers have not been + // configured with gets. Its file now rests on copies that may be reclaimed at + // any time, so it is spread wider to make up for how little each one promises. + if (replicas.length === 0 && cached.length > 0 && options.cacheOnly !== undefined) { + const tried = new Set(targets.map((peer) => peer.peerId)) + const ranked = rankHolders( + cid, + options.peers.map((peer) => peer.peerId) + ) + const extra = ranked + .filter((peerId) => !tried.has(peerId)) + .slice(0, CACHE_WIDENING) + .map((peerId) => options.peers.find((peer) => peer.peerId === peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) + + const widened = await Promise.all( + extra.map(async (peer): Promise => { + try { + await options.cacheOnly?.(peer, cid) + return { node: peer.name, ok: true, outcome: 'cached' } + } catch (err) { + return { node: peer.name, ok: false, error: (err as Error).message } + } + }) + ) + + attempts.push(...widened) + cached.push( + ...widened.filter((attempt) => attempt.outcome === 'cached').map((attempt) => attempt.node) + ) + } + // The local copy counts: it is pinned before any peer is asked. const acknowledged = replicas.length + 1 const required = requiredAcks(config, placement) diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index b1c7059..c81b5bd 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -72,6 +72,8 @@ export interface ReplicationHandlers { */ cacheCopy(cid: string): Promise onError?(message: string): void + /** Called when a peer asks for something it is not allowed to ask for. */ + onRefused?(peerId: string, op: string): void } /** @@ -85,34 +87,65 @@ function toBytes(chunk: Uint8Array | { subarray(): Uint8Array }): Uint8Array { return chunk instanceof Uint8Array ? chunk : chunk.subarray() } +/** Bytes of the length prefix that precedes every message. */ +const LENGTH_PREFIX_BYTES = 4 + /** - * Read one message, which ends when the remote closes its writing end. + * Frame one message with its length. * - * Each stream carries exactly one request and one reply, so end-of-stream is - * the frame delimiter and no length prefix is needed. + * The length is what tells the reader when a message is complete. Ending the + * stream would work too, but only if the half-close reaches the other side + * promptly, and when it does not both ends wait for each other until the call + * times out. */ +function encodeMessage(message: unknown): Uint8Array { + const body = new TextEncoder().encode(JSON.stringify(message)) + const frame = new Uint8Array(LENGTH_PREFIX_BYTES + body.byteLength) + + new DataView(frame.buffer).setUint32(0, body.byteLength) + frame.set(body, LENGTH_PREFIX_BYTES) + + return frame +} + +/** Read exactly one framed message, without waiting for the stream to end. */ async function readMessage(stream: Stream): Promise { const chunks: Uint8Array[] = [] - let total = 0 + let received = 0 for await (const chunk of stream) { const bytes = toBytes(chunk) - total += bytes.byteLength + chunks.push(bytes) + received += bytes.byteLength - if (total > MAX_MESSAGE_BYTES) { + if (received > MAX_MESSAGE_BYTES + LENGTH_PREFIX_BYTES) { throw new Error('Replication message is too large') } - chunks.push(bytes) + if (received < LENGTH_PREFIX_BYTES) { + continue + } + + const buffer = Buffer.concat(chunks) + const length = buffer.readUInt32BE(0) + + if (length > MAX_MESSAGE_BYTES) { + throw new Error('Replication message is too large') + } + + if (buffer.byteLength >= LENGTH_PREFIX_BYTES + length) { + return JSON.parse( + buffer.subarray(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + length).toString('utf8') + ) as unknown + } } - return JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown + throw new Error('Replication stream ended before a complete message arrived') } -/** Send one message and close this end for writing. */ -async function sendMessage(stream: Stream, message: unknown): Promise { - stream.send(new TextEncoder().encode(JSON.stringify(message))) - await stream.close() +/** Send one framed message. The stream stays open for the reply. */ +function sendMessage(stream: Stream, message: unknown): void { + stream.send(encodeMessage(message)) } function parseRequest(value: unknown): ReplicationRequest { @@ -144,39 +177,34 @@ async function respond( // for content stays behind the authorization check. if (request.op === 'cache') { if (!(await handlers.willAccept())) { - await sendMessage(stream, { ok: false, error: 'No room for another copy' }) + sendMessage(stream, { ok: false, error: 'No room for another copy' }) return } - await sendMessage(stream, { - ok: true, - op: 'cache', - cachedBytes: await handlers.cacheCopy(request.cid) - }) + const cachedBytes = await handlers.cacheCopy(request.cid) + sendMessage(stream, { ok: true, op: 'cache', cachedBytes }) return } - if (!handlers.isAuthorized(connection.remotePeer.toString())) { - await sendMessage(stream, { ok: false, error: 'Not authorized' }) + const peerId = connection.remotePeer.toString() + if (!handlers.isAuthorized(peerId)) { + handlers.onRefused?.(peerId, request.op) + sendMessage(stream, { ok: false, error: 'Not authorized' }) return } if (request.op === 'have') { - await sendMessage(stream, { ok: true, op: 'have', has: await handlers.have(request.cid) }) + sendMessage(stream, { ok: true, op: 'have', has: await handlers.have(request.cid) }) return } if (request.op === 'accept') { - await sendMessage(stream, { - ok: true, - op: 'accept', - willAccept: await handlers.willAccept() - }) + sendMessage(stream, { ok: true, op: 'accept', willAccept: await handlers.willAccept() }) return } const storedBytes = await handlers.store(request.cid) - await sendMessage(stream, { ok: true, op: 'store', storedBytes }) + sendMessage(stream, { ok: true, op: 'store', storedBytes }) } /** Start answering replication requests from other ADAMANT nodes. */ @@ -185,10 +213,12 @@ export async function registerReplicationProtocol( handlers: ReplicationHandlers ): Promise { await node.libp2p.handle(REPLICATION_PROTOCOL, (stream, connection) => { - respond(stream, connection, handlers).catch((err: Error) => { - handlers.onError?.(`Replication request failed: ${err.message}`) - stream.abort(err) - }) + respond(stream, connection, handlers) + .then(async () => stream.close()) + .catch((err: Error) => { + handlers.onError?.(`Replication request failed: ${err.message}`) + stream.abort(err) + }) }) } @@ -210,7 +240,7 @@ async function call( signal.addEventListener('abort', abortStream, { once: true }) try { - await sendMessage(stream, request) + sendMessage(stream, request) const response = (await readMessage(stream)) as ReplicationResponse if (response?.ok !== true) { @@ -220,6 +250,7 @@ async function call( return response } finally { signal.removeEventListener('abort', abortStream) + await stream.close().catch(() => {}) } } diff --git a/src/storage/service.ts b/src/storage/service.ts index c4fa8df..54f6743 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -77,7 +77,10 @@ export async function replicateFile(cid: string): Promise { selfPeerId: selfPeerId(), peers: getReplicationPeers(), config: config.replication, - store: (peer) => placeCopy(peer, cid) + store: (peer) => placeCopy(peer, cid), + cacheOnly: async (peer) => { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + } }) if (report.mode === 'quorum') { @@ -230,7 +233,9 @@ export function createReplicationHandlers(): ReplicationHandlers { have: async (cid) => isDirectlyPinned(helia, CID.parse(cid)), willAccept: hasRoomForAnotherCopy, cacheCopy: cacheFileLocally, - onError: (message) => logger.warn(message) + onError: (message) => logger.warn(message), + onRefused: (peerId, op) => + logger.info(`Refused "${op}" from an unknown peer ${peerId}; offering a cached copy instead`) } } diff --git a/test/replication.test.ts b/test/replication.test.ts index 16afa3d..d81e16c 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -182,6 +182,46 @@ describe('placement outcomes', () => { assert.equal(report.acknowledged, 1) }) + it('spreads an unpinned file wider than a pinned one', async () => { + const cachedOn: string[] = [] + + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async () => 'cached', + cacheOnly: async (peer) => { + cachedOn.push(peer.name) + } + }) + + // Nobody pinned it, so it rests on copies that can go at any moment; more + // of them is the only compensation available + assert.deepEqual(report.replicas, []) + assert.ok(cachedOn.length > 0, 'extra peers must be asked') + assert.ok(report.cached.length > storageTargets(placementFor(0), SELF).length) + }) + + it('does not widen when a peer took responsibility', async () => { + const cachedOn: string[] = [] + + await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: baseConfig, + store: async () => 'stored', + cacheOnly: async (peer) => { + cachedOn.push(peer.name) + } + }) + + assert.deepEqual(cachedOn, []) + }) + it('reports stored and cached copies separately', async () => { let first = true const report = await replicate({ From 4533fe0fc9cb7c8b141fa5f811214a514d092ca0 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Mon, 24 Aug 2026 19:11:11 +0300 Subject: [PATCH 111/167] chore: release 0.1.0 and version the replication protocol on its own The node moves to 0.1.0, the first version to carry a storage lifecycle. The replication protocol keeps its own version, deliberately not the package version. A protocol identifier states what two nodes must agree on to talk to each other, and that changes far less often than the software does: deriving it from `package.json` would break interoperability on every release for nothing. It is raised only when an older node can no longer read what a newer one sends. The handler and the dialler both work from a list of supported versions, so adding an older one alongside the current one is all it takes to upgrade a network node by node. Only the current version is offered today, which means an upgrade has to reach every node, and the storage report now names the protocol so a mixed deployment is visible instead of silent. Also stops a pre-existing disk usage test from comparing two `statfs` readings for equality. They are taken a moment apart on a live volume, so anything else writing to disk makes them differ; it now checks that both measure the same filesystem. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/storage-lifecycle.md | 24 +++++++++++++++++ package.json | 2 +- src/replication.cron.ts | 4 +++ src/storage/replicationProtocol.ts | 32 ++++++++++++++++++++--- test/diskUsage.test.ts | 16 +++++++++++- test/replicationProtocolVersion.test.ts | 34 +++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 test/replicationProtocolVersion.test.ts diff --git a/README.md b/README.md index 14b5237..faaeaeb 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,7 @@ curl --fail-with-body https://ipfs.example.org/api/storage/metrics curl --fail-with-body https://ipfs.example.org/api/storage/policy ``` -`metrics` reports pinned, reclaimable, available, and reserved bytes together with the lifecycle counters. `policy` reports the limits and the durability mode a client should expect before uploading. +`metrics` reports pinned, reclaimable, available, and reserved bytes together with the lifecycle counters, and the libp2p replication protocol this node speaks — nodes on different protocol versions cannot place copies on each other, so a mixed deployment is visible here. `policy` reports the limits and the durability mode a client should expect before uploading. ### Download a file diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index f88e4f9..3317e40 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -318,6 +318,30 @@ A copy request that a peer cannot answer in time is reported as a shortfall rather than failing the upload, and the repair job places the missing copies on its next pass. +### Protocol version + +Copies are placed over the libp2p protocol `/adamant/replication/1.0.0`. The +version in that identifier belongs to the wire format, not to the release: it +says what two nodes must agree on to talk to each other, and that changes far +less often than the software. Taking it from `package.json` would break +interoperability on every release for no reason. + +Raise it when an older node can no longer read what a newer one sends — a new +required field, different framing, or an operation whose meaning changed. Adding +an operation that older nodes never send does not require it. + +libp2p negotiates the newest version both ends offer, so listing an older one +alongside the current one is what allows a network to be upgraded one node at a +time. Only the current version is offered today, which means an upgrade has to +be applied everywhere: nodes on different versions cannot place copies on each +other. `GET /api/storage/metrics` reports the protocol a node speaks, so a mixed +deployment is visible rather than silent. + +Messages are framed with a four byte length. The obvious alternative, treating +the end of the stream as the delimiter, requires the half-close to arrive +promptly, and when it does not both ends wait for each other until the call +times out. + ### Why not Kubo with IPFS Cluster Helia has no native pin-orchestration protocol. Cross-node pinning has to come diff --git a/package.json b/package.json index 75fe60f..1d70e0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs-node", - "version": "0.0.1", + "version": "0.1.0", "description": "IPFS decentralized file storage and transfers", "main": "dist/index.js", "private": true, diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 844315d..278680d 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -1,5 +1,6 @@ import { CronJob } from 'cron' import { config } from './config.js' +import { REPLICATION_PROTOCOL } from './storage/replicationProtocol.js' import { repairReplication, type RepairReport } from './storage/service.js' import { logger } from './utils/logger.js' @@ -51,6 +52,9 @@ export const replicationRepairCron = new CronJob(config.replication.repairSchedu export function getReplicationState() { return { enabled: config.replication.enabled, + // Shown so a mixed deployment is visible: nodes on different protocol + // versions cannot place copies on each other. + protocol: REPLICATION_PROTOCOL, placement: config.replication.placement, ackQuorum: config.replication.ackQuorum, requireQuorumOnUpload: config.replication.requireQuorumOnUpload, diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index c81b5bd..cc94dfb 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -4,14 +4,38 @@ import type { PeerId } from '@libp2p/interface' import type { IpfsNode } from '../ipfs-node.js' /** - * Protocol ADAMANT nodes use to place copies of a file. + * Version of the replication wire format. + * + * Deliberately not the package version. It describes what two nodes have to + * agree on to talk to each other, and that changes far less often than the + * software does: taking it from `package.json` would break interoperability on + * every release, for no reason. + * + * Raise it when the wire format changes in a way an older node cannot read — + * a new required field, a different framing, or an operation whose meaning + * changed. Adding an operation an older node simply never sends does not + * require it. + */ +export const REPLICATION_PROTOCOL_VERSION = '1.0.0' + +/** + * Protocol nodes use to place copies of a file. * * It runs over libp2p rather than over the REST API on purpose. The libp2p * handshake already proves the remote peer id cryptographically, so no shared * secret has to be distributed, no second port has to be exposed, and no peer * has to publish an HTTP address for the others to reach it. */ -export const REPLICATION_PROTOCOL = '/adamant/replication/1.0.0' +export const REPLICATION_PROTOCOL = `/adamant/replication/${REPLICATION_PROTOCOL_VERSION}` + +/** + * Versions this node speaks, newest first. + * + * libp2p negotiates the first entry both ends support, so keeping an older + * version in this list is what lets a network be upgraded one node at a time. + * Until there is a second entry, an upgrade has to be applied everywhere. + */ +export const SUPPORTED_REPLICATION_PROTOCOLS = [REPLICATION_PROTOCOL] /** Requests are two short fields; anything larger is a protocol violation. */ const MAX_MESSAGE_BYTES = 4096 @@ -212,7 +236,7 @@ export async function registerReplicationProtocol( node: IpfsNode, handlers: ReplicationHandlers ): Promise { - await node.libp2p.handle(REPLICATION_PROTOCOL, (stream, connection) => { + await node.libp2p.handle(SUPPORTED_REPLICATION_PROTOCOLS, (stream, connection) => { respond(stream, connection, handlers) .then(async () => stream.close()) .catch((err: Error) => { @@ -234,7 +258,7 @@ async function call( options: ReplicationCallOptions ): Promise { const signal = AbortSignal.timeout(options.timeoutMs) - const stream = await node.libp2p.dialProtocol(peer, REPLICATION_PROTOCOL, { signal }) + const stream = await node.libp2p.dialProtocol(peer, SUPPORTED_REPLICATION_PROTOCOLS, { signal }) const abortStream = (): void => stream.abort(new Error('Replication request timed out')) signal.addEventListener('abort', abortStream, { once: true }) diff --git a/test/diskUsage.test.ts b/test/diskUsage.test.ts index 43e6d32..e2024b5 100644 --- a/test/diskUsage.test.ts +++ b/test/diskUsage.test.ts @@ -69,7 +69,21 @@ describe('availableStorageSize', () => { // The store directory does not exist yet, as on a first start const missing = join(parent, 'store', 'blockstore') - assert.equal(await availableStorageSize(missing), await availableStorageSize(parent)) + const [fromMissing, fromParent] = [ + await availableStorageSize(missing), + await availableStorageSize(parent) + ] + + // Both must measure the same filesystem. They are not compared for + // equality: the two readings are taken a moment apart on a live volume, + // so anything else writing to disk moves one of them. + const drift = fromMissing > fromParent ? fromMissing - fromParent : fromParent - fromMissing + + assert.ok(fromMissing > 0n) + assert.ok( + drift < fromParent / 1000n, + `readings differ by ${drift} bytes, which is more than ordinary drift` + ) } finally { await rm(parent, { recursive: true, force: true }) } diff --git a/test/replicationProtocolVersion.test.ts b/test/replicationProtocolVersion.test.ts new file mode 100644 index 0000000..a60b255 --- /dev/null +++ b/test/replicationProtocolVersion.test.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { describe, it } from 'node:test' +import { + REPLICATION_PROTOCOL, + REPLICATION_PROTOCOL_VERSION, + SUPPORTED_REPLICATION_PROTOCOLS +} from '../src/storage/replicationProtocol.js' + +describe('replication protocol version', () => { + it('is a libp2p protocol id carrying its own version', () => { + assert.equal(REPLICATION_PROTOCOL, `/adamant/replication/${REPLICATION_PROTOCOL_VERSION}`) + assert.match(REPLICATION_PROTOCOL_VERSION, /^\d+\.\d+\.\d+$/) + }) + + it('does not follow the package version', () => { + // The wire format is what two nodes must agree on, and it changes far less + // often than the software. Tying them would break interoperability on every + // release for no reason. + // The suites run from the project root, where `package.json` lives + const pkg = JSON.parse(readFileSync('package.json', 'utf8')) as { version: string } + + assert.notEqual(REPLICATION_PROTOCOL_VERSION, pkg.version) + }) + + it('offers the current version and can offer older ones alongside it', () => { + assert.ok(SUPPORTED_REPLICATION_PROTOCOLS.includes(REPLICATION_PROTOCOL)) + assert.equal(SUPPORTED_REPLICATION_PROTOCOLS[0], REPLICATION_PROTOCOL) + assert.equal( + new Set(SUPPORTED_REPLICATION_PROTOCOLS).size, + SUPPORTED_REPLICATION_PROTOCOLS.length + ) + }) +}) From 98a292edc057a1a96fd6fc5f5a50d2d9b68a85b8 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Tue, 25 Aug 2026 00:53:41 +0300 Subject: [PATCH 112/167] fix(after review): increase maxConcurrentUploads to 32 and enable replication by default feat(storage): implement backfillRegistryFromPins to register existing pins in the lifecycle fix(storage): add claimSpace function to manage disk space reservations during uploads fix(storage): update garbage collection to abandon runs when confirmed files cannot be protected fix(storage): improve error handling and logging in replication and upload processes --- README.md | 6 +- config.default.json5 | 10 ++- docs/storage-lifecycle.md | 41 ++++++++-- src/api/file.ts | 42 ++++++++-- src/index.ts | 35 ++++++--- src/middleware/uploadGuards.ts | 23 ++++-- src/storage/backfill.ts | 95 +++++++++++++++++++++++ src/storage/config.ts | 28 ++++++- src/storage/gc.ts | 50 +++++++++--- src/storage/reservation.ts | 65 ++++++++++++++++ src/storage/service.ts | 70 +++++++++++++++-- src/storage/state.ts | 23 ++++++ test/integration/storageLifecycle.test.ts | 62 +++++++++++++++ test/reservation.test.ts | 56 +++++++++++++ test/storageConfig.test.ts | 18 ++++- 15 files changed, 564 insertions(+), 60 deletions(-) create mode 100644 src/storage/backfill.ts create mode 100644 src/storage/reservation.ts create mode 100644 test/reservation.test.ts diff --git a/README.md b/README.md index faaeaeb..e3f721e 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "peeringSchedule": "*/30 * * * * *", "storage": { "maxRequestSizeBytes": 536870912, - "maxConcurrentUploads": 4, + "maxConcurrentUploads": 32, "diskReserveBytes": 5368709120, "confirmationRequired": false, "temporaryTtlMs": 86400000, @@ -72,7 +72,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, } }, "replication": { - "enabled": false, + "enabled": true, "placement": [ { "minAgeMs": 0, "copies": 4 }, { "minAgeMs": 15552000000, "copies": 3 }, @@ -108,7 +108,7 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - Tune the endpoint-specific `rateLimits` for the deployment perimeter - Review `storage.diskReserveBytes` and `storage.maxRequestSizeBytes` for the deployment volume; the defaults suit a dedicated disk - `storage.gc.enabled` is on by default and frees blocks only when the blockstore passes `highWatermarkBytes` or free space falls into `diskReserveBytes`; released files stay readable until then -- Set `replication.enabled` on every node that should place copies; replication needs no key and no extra address, because it runs on a libp2p protocol between the peers already listed in `nodes` +- `replication.enabled` is on by default and needs no key and no extra address: copies travel on a libp2p protocol between the peers already listed in `nodes`. Turning it off leaves every file in a single copy - Tune `replication.placement` if the deployment wants a different number of copies per file age - `peeringSchedule` is optional and defaults to every thirty seconds; it redials the peers in `nodes` that are not connected, which `autoPeeringPeriod` never did diff --git a/config.default.json5 b/config.default.json5 index 7050d3d..ab4e813 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -58,7 +58,7 @@ // existing configuration file keeps working without this section. storage: { maxRequestSizeBytes: 536870912, // 512 MiB. Combined size of all files in one request - maxConcurrentUploads: 4, // Uploads writing into the blockstore at the same time + maxConcurrentUploads: 32, // Uploads writing into the blockstore at the same time; over this, 429 diskReserveBytes: 5368709120, // 5 GiB of free space uploads must never consume // true keeps an upload temporary until POST /api/file/:cid/confirm. // Enabling it is a protocol change: clients must perform that call. @@ -76,10 +76,12 @@ }, // Replication across the nodes listed in `nodes`. Copies travel over libp2p, // so a peer needs no HTTP address and no shared secret: the handshake already - // proves which node is calling. - // Disabled by default: the node then stores content best effort, locally only. + // proves which node is calling, and no extra configuration is needed. replication: { - enabled: false, + // Whether this node places copies of its own content. Answering a peer that + // wants to place a copy here does not depend on it. Turning this off leaves + // every file in a single copy, on this node only. + enabled: true, // How many nodes should hold a file, including this one, by file age. // Copies shrink as content ages instead of being tracked by access time, // which would record when users fetch their files. diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 3317e40..0779c3d 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -31,8 +31,20 @@ and against the bytes actually streamed, because a chunked request declares no size. Free space is measured on the filesystem that holds the blockstore, not on the -root filesystem. A request is admitted only when -`freeSpace - declaredRequestSize >= storage.diskReserveBytes`. +root filesystem. One reading of it is not a reservation, though: two uploads +that both see a gigabyte free would both be admitted and could cross the reserve +together. Each request therefore claims the bytes it may write — its declared +size, or the aggregate limit when it declares none — and the claim is counted +against free space until the request ends, in success or failure. A request is +admitted only when +`freeSpace - alreadyClaimed - requestSize >= storage.diskReserveBytes`. + +Copies arriving from peers are uploads as far as the disk is concerned, so they +take the same claim and the same aggregate limit. Their concurrency is a quarter +of `storage.maxConcurrentUploads`, because a copy cannot declare its size and so +claims the aggregate limit for its whole transfer, and because refusing one +costs nothing a person can see: the sender still holds the file, and repair +places another copy on its next pass. ## Cleanup of failed uploads @@ -381,17 +393,31 @@ also included in `GET /api/node/info`. ## Recovery and rollback -Read this before enabling `storage.gc.enabled` in production. +The scheduled collector is on by default, so read this before the first upgrade +rather than before enabling anything. -### Before enabling collection +What it can delete with the shipped defaults is narrow. It never selects a +confirmed file this node holds, and with `storage.confirmationRequired` off no +upload ever expires, so the only unpinned blocks are read cache and content an +operator released on purpose. Content pinned before this release is protected +too: Helia never deletes a pinned block, and startup records those pins in the +registry so they appear in dry runs and in the storage report. + +Deletion also waits for pressure. Nothing is freed until the blockstore passes +`storage.gc.highWatermarkBytes` or free space falls into +`storage.diskReserveBytes`. + +### Before the first collection 1. Run `POST /api/storage/gc?dryRun=true` and review `releasedCids` and `retainedCids`. Nothing is deleted by a dry run. 2. Confirm that every CID that must survive appears in `retainedCids`. If a CID is missing, it is not confirmed: pin it with `POST /api/helia/pin/:cid` or confirm it with `POST /api/file/:cid/confirm` first. -3. Confirm `repairedPins` is empty. A non-empty list means confirmed content had - lost its pin, which must be understood before deleting anything. +3. Confirm `repairedPins` and `unprotected` are both empty. Either being + non-empty means confirmed content had lost its pin. A run that cannot restore + such a pin abandons itself before deleting anything, so an `unprotected` list + is a signal to investigate, not a loss. 4. Back up the datastore directory. It holds the pinset and the lifecycle registry, which are what protects content from deletion. @@ -413,7 +439,8 @@ Read this before enabling `storage.gc.enabled` in production. ### Operational safeguards -- Keep `storage.gc.enabled` false until a deletion policy is agreed +- Set `storage.gc.enabled` to `false` if the deployment wants deletion to be an + explicit decision rather than a default; nothing else changes when it is off - Keep the smallest tier of `replication.placement` at two copies or more, so a mistaken deletion on one node is recoverable from another - Run the dry run again after changing the watermarks or the TTL diff --git a/src/api/file.ts b/src/api/file.ts index 501fd0c..be19498 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -4,7 +4,7 @@ import { helia } from '../helia.js' import { admitUpload, getUploadSession } from '../middleware/uploadGuards.js' import { readLimiter, uploadLimiter } from '../middleware/rateLimiter.js' import { multerStorage } from '../multer.js' -import { pinFile } from '../storage/pinning.js' +import { pinFile, unpinFile } from '../storage/pinning.js' import { confirmFile, prepareFileRetrieval, @@ -49,6 +49,11 @@ router.post( const session = getUploadSession(req) + // A pin protects blocks from the session cleanup, and a registry entry + // claims disk in the storage report. Both are created before the request is + // known to succeed, so both have to be undone if it does not. + const undo: Array<() => Promise> = [] + try { const files = flatFiles(req.files as UnixFsMulterFile[]) logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) @@ -58,7 +63,19 @@ router.post( const cid = file.cid.toString() logger.info(`Successfully added file ${cid}`) - await pinFile(helia, file.cid) + const knownBefore = await fileRegistry.get(cid) + + // Only a pin this request created may be removed again; content that + // was already durable keeps the pin it had. + if (await pinFile(helia, file.cid)) { + undo.push(async () => { + await unpinFile(helia, file.cid) + }) + } + + if (!knownBefore) { + undo.push(() => fileRegistry.remove(cid)) + } records.push( await fileRegistry.register( @@ -77,8 +94,10 @@ router.post( } // Every file is stored and pinned: the blocks written by this request must - // now survive, so ownership passes from the session to the registry. + // now survive, so ownership passes from the session to the registry and + // there is nothing left to undo. session.commit() + undo.length = 0 const durable = records.filter((record) => record.state === 'confirmed') const replication = await Promise.all(durable.map((record) => replicateFile(record.cid))) @@ -106,8 +125,17 @@ router.post( replication: replication[0] ?? null }) } catch (err) { - // The session is still open, so the response listener reclaims every block - // this request created. + // Undo first, then let the response listener reclaim the blocks: cleanup + // deliberately skips anything pinned, so a pin left behind here would + // keep the failed upload on disk forever. + for (const step of undo.reverse()) { + try { + await step() + } catch (undoError) { + logger.error(`Could not undo a failed upload: ${(undoError as Error).message}`) + } + } + next(err) } } @@ -136,7 +164,9 @@ router.get('/:cid/status', readLimiter, async (req, res, next) => { expiresAt: record.expiresAt, confirmedAt: record.confirmedAt, replication: { - acknowledged: record.replicas.length + 1, + // A file handed over to other nodes keeps its record but no longer + // counts here, so the local copy is only added while it is still held. + acknowledged: record.replicas.length + (record.heldLocally ? 1 : 0), required: config.replication.enabled ? config.replication.ackQuorum : 1, heldLocally: record.heldLocally } diff --git a/src/index.ts b/src/index.ts index 15533fc..7dddc0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,11 @@ import { httpLogger, logger } from './utils/logger.js' import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' import { peeringCron, peerWithKnownNodes } from './peering.cron.js' -import { helia } from './helia.js' +import { helia, ifs } from './helia.js' +import { backfillRegistryFromPins } from './storage/backfill.js' import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' +import { fileRegistry } from './storage/state.js' import { garbageCollectionCron } from './gc.cron.js' import { replicationRepairCron } from './replication.cron.js' import cors from 'cors' @@ -25,24 +27,37 @@ diskUsageCron.start() peeringCron.start() await peerWithKnownNodes() -// Deletion is opt-in: the collector only runs once an operator has agreed to a -// deletion policy and enabled it. Until then the authorized -// POST /api/storage/gc endpoint stays the only way to reclaim space. +// Content pinned before this release has no registry entry, which would leave +// it out of the storage report, out of dry runs, and out of replication repair. +// Recording it is idempotent and runs before anything acts on the registry. +await backfillRegistryFromPins({ + node: helia, + unixfs: ifs, + registry: fileRegistry, + log: (message) => logger.info(message) +}).catch((err: Error) => logger.error(`Registry backfill failed: ${err.message}`)) + +// The collector frees blocks only when space is short: above the high watermark +// or once free space falls into the disk reserve. It never selects a confirmed +// file this node holds, so leaving it on costs nothing while there is room. if (config.storage.gc.enabled) { logger.info( - `Garbage collection enabled: high watermark ${config.storage.gc.highWatermarkBytes} bytes, ` + - `low watermark ${config.storage.gc.lowWatermarkBytes} bytes` + `Garbage collection enabled: frees blocks above ${config.storage.gc.highWatermarkBytes} ` + + `bytes of blockstore, or when free space falls into the ` + + `${config.storage.diskReserveBytes} byte reserve` ) garbageCollectionCron.start() } else { logger.info('Garbage collection is disabled. Storage grows until an operator collects it.') } -if (config.replication.enabled) { - // Answering the protocol is what lets peers place copies here, so it is - // registered before the HTTP server starts accepting uploads. - await registerReplicationProtocol(helia, createReplicationHandlers()) +// Answering the protocol is what lets peers place copies here, and it is +// registered whether or not this node places copies of its own. A node that +// only accepted copies when it was also sending them could not be added to a +// network without every other node being reconfigured first. +await registerReplicationProtocol(helia, createReplicationHandlers()) +if (config.replication.enabled) { if (config.replication.repairEnabled) { replicationRepairCron.start() } diff --git a/src/middleware/uploadGuards.ts b/src/middleware/uploadGuards.ts index a8f0f10..a69e5bf 100644 --- a/src/middleware/uploadGuards.ts +++ b/src/middleware/uploadGuards.ts @@ -2,7 +2,8 @@ import type { NextFunction, Request, RequestHandler, Response } from 'express' import { config } from '../config.js' import { helia } from '../helia.js' import { blockstore, blockstorePath } from '../store.js' -import { checkDiskReserve, checkRequestSize, parseContentLength } from '../storage/limits.js' +import { checkRequestSize, parseContentLength } from '../storage/limits.js' +import { claimSpace } from '../storage/reservation.js' import { uploadLimiter } from '../storage/state.js' import { UploadSession } from '../storage/uploadSession.js' import { parseCid } from '../utils/cid.js' @@ -54,11 +55,13 @@ export const admitUpload: RequestHandler = ( } let released = false + let claimed: { release(): void } | undefined const release = (): void => { if (released) { return } released = true + claimed?.release() uploadLimiter.release() } @@ -72,14 +75,21 @@ export const admitUpload: RequestHandler = ( availableStorageSize(blockstorePath) .then((available) => { - const reserveCheck = checkDiskReserve({ + // A chunked request declares no size, so the most it could write is what + // its aggregate limit allows. Claiming that much keeps the reserve honest + // for a request whose size is unknown, and claiming at all is what stops + // several uploads from each seeing the same free space and passing. + const claim = claimSpace({ + bytes: declaredBytes ?? storage.maxRequestSizeBytes, availableBytes: Number(available), - reserveBytes: storage.diskReserveBytes, - requestedBytes: declaredBytes + reserveBytes: storage.diskReserveBytes }) - if (!reserveCheck.allowed) { - logger.warn(`Upload rejected: ${reserveCheck.reason}`) + if (claim === undefined) { + logger.warn( + `Upload rejected: free space ${String(available)} bytes would fall into the ` + + `${storage.diskReserveBytes} byte reserve` + ) release() res.status(507).send({ error: 'Insufficient storage' }) return @@ -93,6 +103,7 @@ export const admitUpload: RequestHandler = ( parseCid, onCleanupError: (err) => logger.error(`Upload cleanup error: ${err.message}`) }) + claimed = claim ;(req as RequestWithSession)[SESSION_KEY] = session // Covers every way a request can end without a durable result: a parser diff --git a/src/storage/backfill.ts b/src/storage/backfill.ts new file mode 100644 index 0000000..30bc92a --- /dev/null +++ b/src/storage/backfill.ts @@ -0,0 +1,95 @@ +import { CID } from 'multiformats/cid' +import type { UnixFS } from '@helia/unixfs' +import type { IpfsNode } from '../ipfs-node.js' +import type { FileRegistry } from './registry.js' + +export interface BackfillReport { + /** Direct pins found on the node. */ + pins: number + /** Pins that gained a registry entry in this pass. */ + registered: number + /** Pins already known to the registry. */ + known: number + /** + * Pins whose content is not fully present locally. + * + * They keep their pin and stay protected, but calling them confirmed would + * claim more than the node can serve, so they are left alone. + */ + incomplete: number + errors: string[] +} + +export interface BackfillOptions { + node: IpfsNode + unixfs: UnixFS + registry: FileRegistry + log?: (message: string) => void +} + +/** + * Give existing pins a place in the lifecycle registry. + * + * A node upgraded to this release already has a pinset, and an empty + * `/adm/files`. Those files keep working — Helia pins protect them and the + * collector only ever selects registry entries — but nothing else sees them: + * they are missing from `GET /api/file/:cid/status`, from the storage report, + * from a dry run's retained list, and from replication repair. In other words + * the new guarantees would quietly apply to new uploads only. + * + * This walks the pinset once and records what is missing, as `confirmed`, which + * is what a pinned file is. It is idempotent: a pin already in the registry is + * left exactly as it is, so running it again after a restart changes nothing. + */ +export async function backfillRegistryFromPins(options: BackfillOptions): Promise { + const log = options.log ?? ((): void => {}) + const report: BackfillReport = { pins: 0, registered: 0, known: 0, incomplete: 0, errors: [] } + const now = Date.now() + + for await (const pin of options.node.pins.ls()) { + report.pins += 1 + const cid = pin.cid.toString() + + try { + if (await options.registry.get(cid)) { + report.known += 1 + continue + } + + // Offline on purpose: a pin whose blocks are not all here must not send + // the node looking for them on the network at startup. + const stats = await options.unixfs.stat(CID.parse(cid), { extended: true, offline: true }) + + await options.registry.save({ + cid, + // Nothing recorded the original name, and inventing one would be worse + // than admitting it is unknown. + name: cid, + state: 'confirmed', + // The upload happened before this release; its time is not recoverable, + // so the file counts as new and is placed as widely as a fresh one. + createdAt: now, + expiresAt: null, + confirmedAt: now, + fileSize: Number(stats.size), + storedBytes: Number(stats.deduplicatedDagSize), + pinned: true, + heldLocally: true, + replicas: [] + }) + + report.registered += 1 + } catch { + report.incomplete += 1 + } + } + + if (report.registered > 0 || report.incomplete > 0) { + log( + `Registry backfill: ${report.registered} pins recorded, ${report.known} already known, ` + + `${report.incomplete} skipped as incomplete` + ) + } + + return report +} diff --git a/src/storage/config.ts b/src/storage/config.ts index 06673cf..e686ca2 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -29,7 +29,19 @@ export interface GarbageCollectionConfig { export interface StorageConfig { /** Maximum combined size of all files accepted in a single upload request. */ maxRequestSizeBytes: number - /** Maximum number of upload requests writing into the blockstore at once. */ + /** + * Maximum number of upload requests writing into the blockstore at once. + * + * A saturation backstop rather than the disk guard: every request claims the + * bytes it may write against `diskReserveBytes` atomically, so space is + * already bounded without this. What is left for this limit is keeping a node + * from opening more concurrent writers than it can serve. + * + * Several clients uploading to the same node at the same time is ordinary + * traffic rather than a burst, so it sits well above what fairness alone + * would ask for: a limit low enough to be reached by normal use turns into + * `429` for a person who did nothing wrong. + */ maxConcurrentUploads: number /** Free space on the blockstore filesystem that uploads must never consume. */ diskReserveBytes: number @@ -41,7 +53,15 @@ export interface StorageConfig { } export interface ReplicationConfig { - /** When false the node stores content best effort and never contacts peers. */ + /** + * Whether this node places copies of its own content on its peers. + * + * Answering a peer that wants to place a copy here does not depend on it: a + * node that only accepted copies while it was also sending them could not + * join a network without every other node being reconfigured first. + * + * When false the node stores content best effort and never contacts peers. + */ enabled: boolean /** * How many nodes should hold a file, including this one, by file age. @@ -62,7 +82,7 @@ export interface ReplicationConfig { export const DEFAULT_STORAGE_CONFIG: StorageConfig = { maxRequestSizeBytes: 512 * MiB, - maxConcurrentUploads: 4, + maxConcurrentUploads: 32, diskReserveBytes: 5 * GiB, confirmationRequired: false, temporaryTtlMs: 24 * 60 * 60 * 1000, @@ -75,7 +95,7 @@ export const DEFAULT_STORAGE_CONFIG: StorageConfig = { } export const DEFAULT_REPLICATION_CONFIG: ReplicationConfig = { - enabled: false, + enabled: true, placement: [ { minAgeMs: 0, copies: 4 }, { minAgeMs: 180 * DAY, copies: 3 }, diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 00b2ee4..deaa970 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -129,8 +129,14 @@ export interface GcReport { * read differently from the CID a file was uploaded under. */ removedCids: string[] - /** Confirmed files whose pin was missing and had to be restored. */ + /** Confirmed files whose pin was missing and was restored. */ repairedPins: string[] + /** + * Confirmed files this node holds whose protection could not be established. + * + * A non-empty list means the run stopped before deleting anything. + */ + unprotected: string[] errors: string[] } @@ -201,6 +207,7 @@ export async function runGarbageCollection(options: GcRunOptions): Promise 0) { + log(`Collection abandoned: ${report.unprotected.length} confirmed files could not be protected`) + report.durationMs = Date.now() - startedAt + return report + } + + const releasedCleanly: FileRecord[] = [] + for (const record of released) { try { await unpinFile(options.node, CID.parse(record.cid)) await options.registry.release(record.cid) + releasedCleanly.push(record) } catch (err) { errors.push(`Release failed for ${record.cid}: ${(err as Error).message}`) } @@ -269,9 +296,10 @@ export async function runGarbageCollection(options: GcRunOptions): Promise ({ @@ -248,7 +249,10 @@ export function createReplicationHandlers(): ReplicationHandlers { export async function hasRoomForAnotherCopy(): Promise { try { const available = Number(await availableStorageSize(blockstorePath)) - return available > config.storage.diskReserveBytes + // Space already promised to work in progress is not free. Without this, + // several transfers each see the same headroom and cross the reserve + // together. + return available - claimedBytes() > config.storage.diskReserveBytes } catch { return false } @@ -264,14 +268,42 @@ export async function hasRoomForAnotherCopy(): Promise { * @returns Bytes pulled */ export async function cacheFileLocally(cid: string): Promise { + // This is the one operation any peer may ask for, so it carries the limits an + // upload carries: how many may run at once, and how much space they may take + // together. The size is unknown before the transfer, so the aggregate request + // limit is claimed pessimistically and given back at the end. + if (!incomingCopyLimiter.tryAcquire()) { + throw new Error('Too many copies are already being received') + } + + const claim = claimSpace({ + bytes: config.storage.maxRequestSizeBytes, + availableBytes: Number(await availableStorageSize(blockstorePath)), + reserveBytes: config.storage.diskReserveBytes + }) + + if (claim === undefined) { + incomingCopyLimiter.release() + throw new Error('No room for another copy') + } + const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) let bytes = 0 - for await (const chunk of ifs.cat(CID.parse(cid), { signal })) { - bytes += chunk.byteLength - } + try { + for await (const chunk of ifs.cat(CID.parse(cid), { signal })) { + bytes += chunk.byteLength + + if (bytes > config.storage.maxRequestSizeBytes) { + throw new Error('Copy is larger than this node accepts') + } + } - return bytes + return bytes + } finally { + claim.release() + incomingCopyLimiter.release() + } } export interface RepairReport { @@ -381,10 +413,34 @@ export async function repairReplication(): Promise { report.checked = candidates.length + const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) + for (const record of candidates) { const placement = placementFor(record.cid, record.createdAt, peers) - if (!isUnderReplicated(record.replicas.length, placement, self)) { + // `replicas` is a record of what peers once said. A holder that lost its + // blockstore, restarted empty, or left the configuration never changes that + // number, and the file would be considered healthy forever. Ask instead. + const holders = storageTargets(placement, self) + .map((peerId) => byPeerId.get(peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) + + const answers = await Promise.all( + holders.map(async (peer) => { + try { + return (await probeHave(helia, peer.multiAddr, record.cid, callOptions())) + ? peer.name + : undefined + } catch { + return undefined + } + }) + ) + + const live = answers.filter((name): name is string => name !== undefined) + await fileRegistry.setReplicas(record.cid, live) + + if (!isUnderReplicated(live.length, placement, self)) { continue } diff --git a/src/storage/state.ts b/src/storage/state.ts index ba1923a..d9e3bb9 100644 --- a/src/storage/state.ts +++ b/src/storage/state.ts @@ -11,3 +11,26 @@ export const fileRegistry = new FileRegistry(datastore) /** Guards how many uploads may write into the blockstore at the same time. */ export const uploadLimiter = new ConcurrencyLimiter(config.storage.maxConcurrentUploads) + +/** + * Share of the upload limit that incoming copies may occupy. + * + * Copies are held to a fraction of what clients get for two reasons. A copy + * cannot declare its size before the transfer, so it claims the whole aggregate + * request limit for its whole duration and over-claims disk far more than an + * upload does. And refusing one is cheap: the sender still holds the file and + * repair places another copy on the next pass, whereas refusing an upload is + * visible to a person. + */ +export const COPY_INTAKE_SHARE = 4 + +/** + * Guards how many copies peers may push into the blockstore at once. + * + * A copy is an upload as far as the disk is concerned, and it arrives over + * libp2p where the HTTP rate limiter does not apply, so it needs a limit of its + * own. + */ +export const incomingCopyLimiter = new ConcurrencyLimiter( + Math.max(1, Math.floor(config.storage.maxConcurrentUploads / COPY_INTAKE_SHARE)) +) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index d13a10b..3d78fd9 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -8,6 +8,7 @@ import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' +import { backfillRegistryFromPins } from '../../src/storage/backfill.js' import { runGarbageCollection } from '../../src/storage/gc.js' import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' @@ -352,6 +353,36 @@ describe('garbage collection against fixture data', () => { assert.equal(remaining.has(digestOf(cids.live)), false) }) + it('abandons the run when a confirmed file cannot be protected', async () => { + const { registry, cids } = await createFixture() + + // A record whose blocks are not here: its pin cannot be restored, because + // nothing can fetch a CID nobody holds + const unreachable = 'bafkreiapv3pyvtsdmvcqcgzvvjmayksybgcgfvkbfbbccjnisz3ndnvcam' + await registry.save(record({ cid: unreachable, state: 'confirmed', storedBytes: 1024 })) + + const before = await storedDigests() + + const report = await runGarbageCollection({ + node, + registry, + watermarks: TIGHT, + blockstoreBytes: 4096, + pinTimeoutMs: 1500, + now: 1000 + }) + + // Collection deletes every unpinned block, so it must not run at all while + // a confirmed file is unprotected + assert.deepEqual(report.unprotected, [unreachable]) + assert.equal(report.collected, false) + assert.equal(report.removedBlocks, 0) + assert.deepEqual([...(await storedDigests())].sort(), [...before].sort()) + assert.equal((await registry.get(cids.abandoned))?.pinned, true) + + await registry.remove(unreachable) + }) + it('restores a missing pin on confirmed content before deleting anything', async () => { const { registry, cids } = await createFixture() await unpinFile(node, CID.parse(cids.confirmed)) @@ -369,3 +400,34 @@ describe('garbage collection against fixture data', () => { assert.equal((await storedDigests()).has(digestOf(cids.confirmed)), true) }) }) + +describe('registry backfill', () => { + it('records a pin that predates the registry, and does so only once', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'legacy-pin')) + await pinFile(node, cid) + + const first = await backfillRegistryFromPins({ node, unixfs: ifs, registry }) + const recorded = await registry.get(cid.toString()) + + assert.ok(first.registered > 0) + assert.equal(recorded?.state, 'confirmed') + assert.equal(recorded?.heldLocally, true) + assert.ok((recorded?.storedBytes ?? 0) > 0) + + const second = await backfillRegistryFromPins({ node, unixfs: ifs, registry }) + assert.equal(second.registered, 0) + assert.ok(second.known > 0) + }) + + it('leaves an existing record untouched', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'already-known')) + await pinFile(node, cid) + await registry.save(record({ cid: cid.toString(), state: 'confirmed', name: 'mine.bin' })) + + await backfillRegistryFromPins({ node, unixfs: ifs, registry }) + + assert.equal((await registry.get(cid.toString()))?.name, 'mine.bin') + }) +}) diff --git a/test/reservation.test.ts b/test/reservation.test.ts new file mode 100644 index 0000000..749f9ce --- /dev/null +++ b/test/reservation.test.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict' +import { beforeEach, describe, it } from 'node:test' +import { claimedBytes, claimSpace, resetClaims } from '../src/storage/reservation.js' + +const RESERVE = 1000 + +beforeEach(() => resetClaims()) + +describe('claimSpace', () => { + it('grants space while the reserve survives', () => { + const claim = claimSpace({ bytes: 500, availableBytes: 2000, reserveBytes: RESERVE }) + + assert.notEqual(claim, undefined) + assert.equal(claimedBytes(), 500) + }) + + it('refuses a claim that would break the reserve', () => { + assert.equal( + claimSpace({ bytes: 1001, availableBytes: 2000, reserveBytes: RESERVE }), + undefined + ) + assert.equal(claimedBytes(), 0) + }) + + it('counts what is already promised, so two claims cannot both fit', () => { + // Both see the same free space; only the first may have it + const first = claimSpace({ bytes: 900, availableBytes: 2000, reserveBytes: RESERVE }) + const second = claimSpace({ bytes: 900, availableBytes: 2000, reserveBytes: RESERVE }) + + assert.notEqual(first, undefined) + assert.equal(second, undefined) + }) + + it('lets a later claim through once an earlier one is given back', () => { + const first = claimSpace({ bytes: 900, availableBytes: 2000, reserveBytes: RESERVE }) + first?.release() + + assert.notEqual( + claimSpace({ bytes: 900, availableBytes: 2000, reserveBytes: RESERVE }), + undefined + ) + }) + + it('gives a claim back only once', () => { + const claim = claimSpace({ bytes: 400, availableBytes: 2000, reserveBytes: RESERVE }) + + claim?.release() + claim?.release() + + assert.equal(claimedBytes(), 0) + }) + + it('refuses everything once free space is already inside the reserve', () => { + assert.equal(claimSpace({ bytes: 1, availableBytes: 500, reserveBytes: RESERVE }), undefined) + }) +}) diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 545375c..5252a70 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -51,15 +51,29 @@ describe('resolveStorageConfig', () => { assert.throws(() => resolveStorageConfig({ confirmationRequired: 'yes' }, UPLOAD_LIMIT)) }) + it('admits enough concurrent uploads for several clients at once', () => { + // Several clients uploading to one node at the same time is ordinary + // traffic. A limit low enough to be reached by it answers 429 to a person + // who did nothing wrong, and disk is bounded by the space claim anyway. + assert.ok( + DEFAULT_STORAGE_CONFIG.maxConcurrentUploads >= 16, + 'the default must not turn normal simultaneous use into 429' + ) + }) + it('accepts a zero disk reserve, which only disables the reserve check', () => { assert.equal(resolveStorageConfig({ diskReserveBytes: 0 }, UPLOAD_LIMIT).diskReserveBytes, 0) }) }) describe('resolveReplicationConfig', () => { - it('defaults to disabled best-effort storage', () => { + it('places copies by default, since doing so needs no configuration', () => { assert.deepEqual(resolveReplicationConfig(undefined), DEFAULT_REPLICATION_CONFIG) - assert.equal(DEFAULT_REPLICATION_CONFIG.enabled, false) + assert.equal(DEFAULT_REPLICATION_CONFIG.enabled, true) + }) + + it('can be turned off, leaving every file in a single copy', () => { + assert.equal(resolveReplicationConfig({ enabled: false }).enabled, false) }) it('reduces copies as files age', () => { From ccac5d5ac844aff01875aed2a07e8a3e5d606afa Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Tue, 25 Aug 2026 09:07:47 +0300 Subject: [PATCH 113/167] fix(after review): enhance storage lifecycle management and intake budget - Added detailed explanations for garbage collection runs triggered by free space reclamation in storage lifecycle documentation. - Updated replication logic to ensure durability guarantees are enforced and clarified in the API. - Introduced an intake budget system to manage bandwidth and disk usage for cached copies, preventing abuse by peers. - Implemented error handling for registry backfill processes, ensuring failures are reported correctly without misclassifying content as incomplete. - Enhanced garbage collection to retain records when deletion encounters errors, allowing for retries in subsequent runs. - Added tests to validate new intake budget functionality and garbage collection behavior under various conditions. --- docs/storage-lifecycle.md | 72 ++++++++++++-- src/api/file.ts | 48 ++++++++-- src/api/storage.ts | 1 + src/index.ts | 78 +++++++++------ src/middleware/uploadGuards.ts | 53 +++++++---- src/storage/backfill.ts | 34 ++++++- src/storage/gc.ts | 49 +++++++++- src/storage/intakeBudget.ts | 102 ++++++++++++++++++++ src/storage/replicationProtocol.ts | 9 +- src/storage/service.ts | 110 +++++++++++++++++----- src/storage/state.ts | 13 ++- test/garbageCollectionPlan.test.ts | 62 ++++++++++++ test/intakeBudget.test.ts | 68 +++++++++++++ test/integration/storageLifecycle.test.ts | 54 +++++++++++ 14 files changed, 653 insertions(+), 100 deletions(-) create mode 100644 src/storage/intakeBudget.ts create mode 100644 test/intakeBudget.test.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 0779c3d..15197b1 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -168,17 +168,38 @@ uploaded under. Collection takes the Helia blockstore write lock, so uploads wait while it runs. Schedule it accordingly. +### What a run triggered by free space reclaims + +The two triggers have different targets. A run above the high watermark evicts +until the blockstore estimate drops below the low watermark. A run caused by +free space falling into `storage.diskReserveBytes` evicts until the reserve is +honoured again with a 25% margin, because the watermarks say nothing about the +volume: on a disk whose blockstore is already below the low watermark, aiming at +the watermark would select nothing and reclaim not a byte while the disk stayed +full. + +A collection that Helia could not complete keeps every record it released, and +reports `collected: false` with the failures in `errors`. Which file a surviving +block belongs to cannot be known — blocks are addressed by multihash and shared +between files — so the records stay for the next run to retry. + ## Replication and durability -`replication.enabled` is `false` by default. The node then stores content -**best effort**: one copy, on this node, pinned, with no promise that any other -node holds it. `GET /api/storage/policy` reports `"mode": "best-effort"` so -clients can see what they are getting. +`replication.enabled` is `true` by default: placing copies needs no +configuration beyond the `nodes` list a node already has, and a durability +guarantee nobody switches on protects nothing. -With replication enabled, a file is placed on a subset of the ADAMANT nodes. The -peer pulls the DAG over libp2p and pins it before answering, so an -acknowledgement means the copy exists and is protected there, not that a request -was queued. +A file is placed on a subset of the ADAMANT nodes. The peer pulls the DAG over +libp2p and pins it before answering, so an acknowledgement means the copy exists +and is protected there, not that a request was queued. + +Turning it off with `replication.enabled: false` makes the node store content +**best effort** instead: one copy, on this node, pinned, with no promise that +any other node holds it. `GET /api/storage/policy` reports +`"mode": "best-effort"` so clients can see what they are getting. The node still +answers the protocol and still accepts copies from peers — a node that only took +part while it was also sending would have to be added to every other +configuration before it could be useful. ### How many copies, and where @@ -246,6 +267,15 @@ this is the same effect with a different trigger, bounded by the same disk reserve. What stays behind the configuration check is everything that makes a node responsible: pinning, registration, and being counted as a holder. +Open is not the same as unbounded. A copy taken this way is charged to the peer +that asked for it, against a per-peer and a node-wide budget over a rolling +hour, and a request that fails still costs a request — otherwise asking for +content that never arrives would be free, and asking is most of the cost. The +node-wide budget is the one that matters while peer identities are free to mint; +both sit far above ordinary traffic, where a node that just accepted an upload +asks two peers to cache it. Operator-facing transfer limits are separate work +(#29). + It is a stopgap, not the answer. A file spread this way survives the loss of the node it was uploaded to, but only until its peers need the space, and no repair job will notice if the copies go. Node discovery removes the need for it. @@ -407,6 +437,32 @@ Deletion also waits for pressure. Nothing is freed until the blockstore passes `storage.gc.highWatermarkBytes` or free space falls into `storage.diskReserveBytes`. +### The first start after the upgrade + +Startup walks the pinset once and records anything the registry does not know as +`confirmed`. It is idempotent, so a restart changes nothing, and a pin whose DAG +is not fully local is skipped rather than recorded — calling it confirmed would +claim more than the node can serve. + +It does not block the API. The walk is as long as the pinset, and holding reads +back for the whole migration would penalise exactly the nodes with the most to +serve. Reads, uploads and incoming copies do not need a complete registry; the +collector and the repair sweep are started once the walk finishes, so neither +acts on a half-built picture. + +Two consequences are worth planning for: + +- The original upload time is not recoverable, so a backfilled record is dated + at the backfill. Every legacy file therefore counts as **fresh**, which is the + widest tier: repair will place it on as many nodes as that tier asks for. On a + small node list that means historical content is eventually copied to + effectively every node. This is the durability guarantee arriving for content + that never had it, not a fault, but it is real disk and real transfer on every + node — plan capacity for the existing corpus, not just for new uploads. +- Repair works through a bounded, advancing batch per pass rather than the whole + registry at once, so the copying is gradual. It is still the largest transfer + the upgrade causes. + ### Before the first collection 1. Run `POST /api/storage/gc?dryRun=true` and review `releasedCids` and diff --git a/src/api/file.ts b/src/api/file.ts index be19498..cbb3704 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -7,6 +7,7 @@ import { multerStorage } from '../multer.js' import { pinFile, unpinFile } from '../storage/pinning.js' import { confirmFile, + effectiveQuorum, prepareFileRetrieval, releaseFile, replicateFile @@ -65,6 +66,24 @@ router.post( const knownBefore = await fileRegistry.get(cid) + // Compensation runs in reverse of the order it is pushed, so the + // registry step goes first to run last. Undoing the pin before the + // record means a failure at worst leaves a record for a file that is + // still pinned — visible, and reclaimable by the collector. The other + // order leaves a pinned file nothing accounts for. + // + // A CID this node already knew is restored rather than removed: + // registering it again may move an expired or released record to + // confirmed, and dropping it would lose a lifecycle this request did + // not create. + undo.push(async () => { + if (knownBefore) { + await fileRegistry.save(knownBefore) + } else { + await fileRegistry.remove(cid) + } + }) + // Only a pin this request created may be removed again; content that // was already durable keeps the pin it had. if (await pinFile(helia, file.cid)) { @@ -73,10 +92,6 @@ router.post( }) } - if (!knownBefore) { - undo.push(() => fileRegistry.remove(cid)) - } - records.push( await fileRegistry.register( { @@ -100,11 +115,17 @@ router.post( undo.length = 0 const durable = records.filter((record) => record.state === 'confirmed') - const replication = await Promise.all(durable.map((record) => replicateFile(record.cid))) + const replication = await Promise.all( + durable.map(async (record) => ({ + cid: record.cid, + report: await replicateFile(record.cid) + })) + ) + const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) if ( config.replication.requireQuorumOnUpload && - replication.some((report) => !report.satisfied) + replication.some((item) => !item.report.satisfied) ) { // The policy demands a quorum, so the upload is not durable. The files are // released instead of being kept as a copy nobody promised to hold. @@ -120,9 +141,14 @@ router.post( cid: record.cid, name: record.name, state: record.state, - expiresAt: record.expiresAt + expiresAt: record.expiresAt, + // Placement is decided per CID, so one file of a request can meet its + // quorum while another does not, on different peers. + replication: replicationByCid.get(record.cid) ?? null })), - replication: replication[0] ?? null + // The first file's report, kept for clients written against it. New + // ones should read the per-file field above. + replication: replication[0]?.report ?? null }) } catch (err) { // Undo first, then let the response listener reclaim the blocks: cleanup @@ -167,7 +193,11 @@ router.get('/:cid/status', readLimiter, async (req, res, next) => { // A file handed over to other nodes keeps its record but no longer // counts here, so the local copy is only added while it is still held. acknowledged: record.replicas.length + (record.heldLocally ? 1 : 0), - required: config.replication.enabled ? config.replication.ackQuorum : 1, + // What this file is actually held to, not what the configuration asks + // for in the abstract: a quorum larger than the network can answer is + // capped when the file is placed, and reporting the raw number would + // show every file as short of it forever. + required: effectiveQuorum(record.cid, record.createdAt), heldLocally: record.heldLocally } }) diff --git a/src/api/storage.ts b/src/api/storage.ts index 499512e..107c9b8 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -87,6 +87,7 @@ storageAdminRouter.post('/gc', async (req, res, next) => { removedBlocks: report.removedBlocks, removedCids: report.removedCids, repairedPins: report.repairedPins, + unprotected: report.unprotected, errors: report.errors }) } catch (err) { diff --git a/src/index.ts b/src/index.ts index 7dddc0c..802a5a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,44 +27,64 @@ diskUsageCron.start() peeringCron.start() await peerWithKnownNodes() -// Content pinned before this release has no registry entry, which would leave -// it out of the storage report, out of dry runs, and out of replication repair. -// Recording it is idempotent and runs before anything acts on the registry. -await backfillRegistryFromPins({ - node: helia, - unixfs: ifs, - registry: fileRegistry, - log: (message) => logger.info(message) -}).catch((err: Error) => logger.error(`Registry backfill failed: ${err.message}`)) - -// The collector frees blocks only when space is short: above the high watermark -// or once free space falls into the disk reserve. It never selects a confirmed -// file this node holds, so leaving it on costs nothing while there is room. -if (config.storage.gc.enabled) { - logger.info( - `Garbage collection enabled: frees blocks above ${config.storage.gc.highWatermarkBytes} ` + - `bytes of blockstore, or when free space falls into the ` + - `${config.storage.diskReserveBytes} byte reserve` - ) - garbageCollectionCron.start() -} else { - logger.info('Garbage collection is disabled. Storage grows until an operator collects it.') -} - // Answering the protocol is what lets peers place copies here, and it is // registered whether or not this node places copies of its own. A node that // only accepted copies when it was also sending them could not be added to a // network without every other node being reconfigured first. await registerReplicationProtocol(helia, createReplicationHandlers()) -if (config.replication.enabled) { - if (config.replication.repairEnabled) { - replicationRepairCron.start() +/** The scheduled work that reads the file registry and acts on what it finds. */ +function startLifecycleJobs(): void { + // The collector frees blocks only when space is short: above the high + // watermark or once free space falls into the disk reserve. It never selects + // a confirmed file this node holds, so leaving it on costs nothing while + // there is room. + if (config.storage.gc.enabled) { + logger.info( + `Garbage collection enabled: frees blocks above ${config.storage.gc.highWatermarkBytes} ` + + `bytes of blockstore, or when free space falls into the ` + + `${config.storage.diskReserveBytes} byte reserve` + ) + garbageCollectionCron.start() + } else { + logger.info('Garbage collection is disabled. Storage grows until an operator collects it.') + } + + if (config.replication.enabled) { + if (config.replication.repairEnabled) { + replicationRepairCron.start() + } + } else { + logger.info('Replication is disabled. Uploaded content is stored best effort on this node.') } -} else { - logger.info('Replication is disabled. Uploaded content is stored best effort on this node.') } +// Content pinned before this release has no registry entry, which would leave +// it out of the storage report, out of dry runs, and out of replication repair. +// Recording it is idempotent. +// +// It is deliberately not awaited. The walk is as long as the pinset, and +// blocking here would keep the whole API — reads included — unavailable for the +// entire migration on exactly the nodes that have the most to serve. Reads, +// uploads and incoming copies do not need the registry to be complete; the jobs +// that do are started once it is. +void backfillRegistryFromPins({ + node: helia, + unixfs: ifs, + registry: fileRegistry, + log: (message) => logger.info(message) +}) + .then((report) => { + for (const error of report.errors) { + logger.warn(`Registry backfill: ${error}`) + } + }) + .catch((err: Error) => logger.error(`Registry backfill failed: ${err.message}`)) + // Even a failed backfill must not leave the node without a collector: it + // reclaims nothing that is unregistered, so an incomplete registry makes it + // do less, never more. + .finally(startLifecycleJobs) + const PORT = config.serverPort const app = express() app.disable('x-powered-by') diff --git a/src/middleware/uploadGuards.ts b/src/middleware/uploadGuards.ts index a69e5bf..0cb95bf 100644 --- a/src/middleware/uploadGuards.ts +++ b/src/middleware/uploadGuards.ts @@ -56,6 +56,7 @@ export const admitUpload: RequestHandler = ( let released = false let claimed: { release(): void } | undefined + let session: UploadSession | undefined const release = (): void => { if (released) { return @@ -65,6 +66,31 @@ export const admitUpload: RequestHandler = ( uploadLimiter.release() } + // Attached before anything is awaited, and it covers every way a request can + // end without a durable result: a parser abort, a route failure, or a client + // that disconnected mid-upload. + // + // A client that hangs up while free space is being measured fires `close` + // inside that window. A listener added afterwards would never run, and the + // upload slot and the space claim would stay taken until the process + // restarts — which anyone could trigger, repeatedly, just by disconnecting. + res.on('close', () => { + release() + + if (session === undefined || session.isSettled) { + return + } + + session + .cleanup() + .then((removed) => { + if (removed > 0) { + logger.info(`Removed ${removed} blocks left by an unfinished upload`) + } + }) + .catch((err) => logger.error(`Upload cleanup failed: ${(err as Error).message}`)) + }) + const declaredBytes = parseContentLength(req.headers['content-length']) if (!checkRequestSize(declaredBytes, storage.maxRequestSizeBytes).allowed) { @@ -75,6 +101,12 @@ export const admitUpload: RequestHandler = ( availableStorageSize(blockstorePath) .then((available) => { + // The client is already gone and the slot went back with it. Claiming + // space now would promise it to a request that no longer exists. + if (released) { + return + } + // A chunked request declares no size, so the most it could write is what // its aggregate limit allows. Claiming that much keeps the reserve honest // for a request whose size is unknown, and claiming at all is what stops @@ -95,7 +127,7 @@ export const admitUpload: RequestHandler = ( return } - const session = new UploadSession({ + session = new UploadSession({ blockstore: helia.blockstore, isPinned: (cid) => helia.pins.isPinned(cid), deleteBlock: (cid) => blockstore.delete(cid), @@ -106,25 +138,6 @@ export const admitUpload: RequestHandler = ( claimed = claim ;(req as RequestWithSession)[SESSION_KEY] = session - // Covers every way a request can end without a durable result: a parser - // abort, a route failure, or a client that disconnected mid-upload. - res.on('close', () => { - release() - - if (session.isSettled) { - return - } - - session - .cleanup() - .then((removed) => { - if (removed > 0) { - logger.info(`Removed ${removed} blocks left by an unfinished upload`) - } - }) - .catch((err) => logger.error(`Upload cleanup failed: ${(err as Error).message}`)) - }) - next() }) .catch((err) => { diff --git a/src/storage/backfill.ts b/src/storage/backfill.ts index 30bc92a..00a0f6a 100644 --- a/src/storage/backfill.ts +++ b/src/storage/backfill.ts @@ -17,6 +17,15 @@ export interface BackfillReport { * claim more than the node can serve, so they are left alone. */ incomplete: number + /** + * Registry reads and writes that failed, with the CID they were for. + * + * A pin left unrecorded by one of these keeps its Helia pin and the + * protection that comes with it, exactly as before this release, but stays + * invisible to repair and to the storage report until a later pass records + * it. It is not the same condition as incomplete content and must not be + * reported as one. + */ errors: string[] } @@ -55,11 +64,26 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis report.known += 1 continue } + } catch (err) { + report.errors.push(`Registry read failed for ${cid}: ${(err as Error).message}`) + continue + } + + let stats + try { // Offline on purpose: a pin whose blocks are not all here must not send // the node looking for them on the network at startup. - const stats = await options.unixfs.stat(CID.parse(cid), { extended: true, offline: true }) + stats = await options.unixfs.stat(CID.parse(cid), { extended: true, offline: true }) + } catch { + // The only failure this catch may claim. A datastore that is unavailable + // says nothing about whether the DAG is complete, and reporting it as + // incomplete content would send an operator after the wrong problem. + report.incomplete += 1 + continue + } + try { await options.registry.save({ cid, // Nothing recorded the original name, and inventing one would be worse @@ -79,15 +103,15 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis }) report.registered += 1 - } catch { - report.incomplete += 1 + } catch (err) { + report.errors.push(`Registry write failed for ${cid}: ${(err as Error).message}`) } } - if (report.registered > 0 || report.incomplete > 0) { + if (report.registered > 0 || report.incomplete > 0 || report.errors.length > 0) { log( `Registry backfill: ${report.registered} pins recorded, ${report.known} already known, ` + - `${report.incomplete} skipped as incomplete` + `${report.incomplete} skipped as incomplete, ${report.errors.length} failed` ) } diff --git a/src/storage/gc.ts b/src/storage/gc.ts index deaa970..6518477 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -6,6 +6,15 @@ import { FileRegistry, isExpired, type FileRecord } from './registry.js' export type Watermarks = Pick +/** + * Free space restored beyond the reserve when a run is triggered by it. + * + * Stopping exactly at the reserve would leave the node one write away from + * collecting again, which is the same reason the watermarks keep a gap between + * them. + */ +export const RESERVE_RECOVERY_MARGIN = 0.25 + export interface GcPlanInput { /** Current blockstore size in bytes. */ blockstoreBytes: number @@ -82,13 +91,28 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { : 'none' const evicted: FileRecord[] = [] + // Bytes that have to come back for the reserve to be honoured again. The + // watermarks say nothing about free space: on a volume whose blockstore sits + // below the low watermark already, comparing against it selects nothing and a + // run triggered by a full disk would reclaim not a single byte. + const reserveTarget = + underReserve && input.availableBytes !== undefined && input.reserveBytes !== undefined + ? Math.ceil(input.reserveBytes * (1 + RESERVE_RECOVERY_MARGIN)) - input.availableBytes + : 0 + + // Expired files are released whatever the trigger, so what they free already + // counts towards it. + const stillShort = (): boolean => + (overWatermark && estimatedBytesAfter > lowWatermarkBytes) || + input.blockstoreBytes - estimatedBytesAfter < reserveTarget + if (shouldCollect) { // Oldest first: the least recently accepted upload is evicted before newer // content that a client may still be waiting to confirm. const byAge = [...alive].sort((a, b) => a.createdAt - b.createdAt) for (const record of byAge) { - if (estimatedBytesAfter <= lowWatermarkBytes) { + if (!stillShort()) { break } @@ -113,6 +137,12 @@ export interface GcReport { startedAt: number durationMs: number dryRun: boolean + /** + * True when the run deleted blocks and finished cleanly. + * + * False after a run that deleted some blocks but hit errors: what survived is + * unknown, so its records are kept and the next run retries them. + */ collected: boolean /** What made this run delete blocks, or why it did not. */ trigger: CollectionTrigger @@ -281,6 +311,8 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { if (event.type === 'helia:gc:deleted') { @@ -291,11 +323,26 @@ export async function runGarbageCollection(options: GcRunOptions): Promise 0) { + log( + `Collection incomplete: ${deletionErrors} blocks could not be deleted; ` + + `${releasedCleanly.length} records kept for the next run` + ) + report.durationMs = Date.now() - startedAt + return report + } + // Only files whose release succeeded lose their record. One that failed to // unpin still has blocks and a pin, and dropping its record would hide it // from the next run and from the storage report. diff --git a/src/storage/intakeBudget.ts b/src/storage/intakeBudget.ts new file mode 100644 index 0000000..636b536 --- /dev/null +++ b/src/storage/intakeBudget.ts @@ -0,0 +1,102 @@ +/** + * How much of this node's bandwidth and disk peers may spend on cached copies. + * + * The `cache` operation is open to any peer on purpose: a node its neighbours + * have not been configured with would otherwise keep every file uploaded to it + * in a single copy that dies with it. Open is not the same as unbounded, + * though. Without a budget one peer can ask for unique CIDs back to back and + * make this node fetch them for as long as it likes, which costs bandwidth and + * evicts the read cache of files people are actually asking for. + * + * There are two budgets because they answer different attacks. The per-peer one + * bounds a single identity. The node-wide one bounds their sum, which is what + * matters while identities are free to mint — as they are until node membership + * is established (#28). + * + * Both are far above ordinary traffic: a node that just accepted an upload asks + * two peers to cache it, and repair asks for a bounded batch per pass. They are + * constants rather than configuration because the operator-facing limit is + * monthly transfer accounting (#29), which supersedes them. + */ + +/** Rolling window the budgets are measured over. */ +export const INTAKE_WINDOW_MS = 60 * 60 * 1000 + +/** Bytes one peer may have this node pull in per window. */ +export const PER_PEER_INTAKE_BYTES = 4 * 1024 ** 3 + +/** Requests one peer may make per window, whatever they cost. */ +export const PER_PEER_INTAKE_REQUESTS = 500 + +/** Bytes every peer together may have this node pull in per window. */ +export const TOTAL_INTAKE_BYTES = 16 * 1024 ** 3 + +/** + * Requests every peer together may make per window. + * + * This also bounds how much the accounting itself can cost: a window can hold + * at most this many distinct peers, so minting identities cannot grow the map + * without also spending the shared budget. + */ +export const TOTAL_INTAKE_REQUESTS = 5000 + +interface Spend { + windowStartedAt: number + bytes: number + requests: number +} + +const perPeer = new Map() +let total: Spend = { windowStartedAt: 0, bytes: 0, requests: 0 } + +/** The spend for the window `now` falls in, starting a fresh one when it moved on. */ +function current(spend: Spend | undefined, now: number): Spend { + if (spend === undefined || now - spend.windowStartedAt >= INTAKE_WINDOW_MS) { + return { windowStartedAt: now, bytes: 0, requests: 0 } + } + + return spend +} + +/** Whether a peer may ask this node to pull in one more copy. */ +export function mayAcceptIntake(peerId: string, now: number = Date.now()): boolean { + const peer = current(perPeer.get(peerId), now) + const all = current(total, now) + + return ( + peer.bytes < PER_PEER_INTAKE_BYTES && + peer.requests < PER_PEER_INTAKE_REQUESTS && + all.bytes < TOTAL_INTAKE_BYTES && + all.requests < TOTAL_INTAKE_REQUESTS + ) +} + +/** + * Account for a request that was served. + * + * A transfer that failed is recorded as zero bytes and still counts as a + * request, so a peer cannot spend this node's bandwidth for free by asking for + * content that never arrives. + */ +export function recordIntake(peerId: string, bytes: number, now: number = Date.now()): void { + const peer = current(perPeer.get(peerId), now) + peer.bytes += bytes + peer.requests += 1 + perPeer.set(peerId, peer) + + total = current(total, now) + total.bytes += bytes + total.requests += 1 + + for (const [id, spend] of perPeer) { + if (now - spend.windowStartedAt >= INTAKE_WINDOW_MS) { + perPeer.delete(id) + } + } +} + +/** Forget every window. For tests, where no peer is really spending anything. */ +export function resetIntakeBudget(): void { + perPeer.clear() + total = { windowStartedAt: 0, bytes: 0, requests: 0 } +} diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index cc94dfb..6c7b40c 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -94,7 +94,7 @@ export interface ReplicationHandlers { * The node can serve the file from now on, and gives up nothing: the blocks * live in the same tier as read cache and go when space is needed. */ - cacheCopy(cid: string): Promise + cacheCopy(cid: string, peerId: string): Promise onError?(message: string): void /** Called when a peer asks for something it is not allowed to ask for. */ onRefused?(peerId: string, op: string): void @@ -195,22 +195,23 @@ async function respond( handlers: ReplicationHandlers ): Promise { const request = parseRequest(await readMessage(stream)) + const peerId = connection.remotePeer.toString() // Holding an extra copy is open to anyone, because it costs no more than a // read from the same peer would. Everything that makes this node responsible - // for content stays behind the authorization check. + // for content stays behind the authorization check. Open still means + // accountable: the handler charges the request to the peer that made it. if (request.op === 'cache') { if (!(await handlers.willAccept())) { sendMessage(stream, { ok: false, error: 'No room for another copy' }) return } - const cachedBytes = await handlers.cacheCopy(request.cid) + const cachedBytes = await handlers.cacheCopy(request.cid, peerId) sendMessage(stream, { ok: true, op: 'cache', cachedBytes }) return } - const peerId = connection.remotePeer.toString() if (!handlers.isAuthorized(peerId)) { handlers.onRefused?.(peerId, request.op) sendMessage(stream, { ok: false, error: 'Not authorized' }) diff --git a/src/storage/service.ts b/src/storage/service.ts index 4971271..24ee2a3 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -11,6 +11,7 @@ import type { FileRecord } from './registry.js' import { isUnderReplicated, replicate, + requiredAcks, type PlacementOutcome, type ReplicationPeer, type ReplicationReport @@ -24,7 +25,8 @@ import { type ReplicationCallOptions } from './replicationProtocol.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' -import { claimedBytes, claimSpace } from './reservation.js' +import { mayAcceptIntake, recordIntake } from './intakeBudget.js' +import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter } from './state.js' import { nextSweepBatch } from './sweep.js' @@ -204,6 +206,23 @@ export async function confirmFile( return fileRegistry.get(cid) } +/** + * Acknowledgements a file needs, given the network it actually lives in. + * + * The upload path caps the configured quorum by the copies that can exist, so + * reporting the raw configuration would show a file as permanently short of a + * quorum it was never asked to reach: `ackQuorum: 4` cannot be answered by more + * than three copies on a three-node network, and the upload that created the + * file already counted it as satisfied. + */ +export function effectiveQuorum(cid: string, createdAt: number): number { + if (!config.replication.enabled) { + return 1 + } + + return requiredAcks(config.replication, placementFor(cid, createdAt, getReplicationPeers())) +} + /** * Release a file so garbage collection may reclaim it. * Blocks stay on disk until the collector runs, which keeps the action @@ -216,6 +235,16 @@ export async function releaseFile(cid: string): Promise /** Store a copy requested by another ADAMANT node. */ export async function acceptReplica(cid: string): Promise { + // Pulling the DAG first is what bounds this. Pinning would fetch it too, but + // with no limit on how many transfers run at once, how much space they may + // take together, or how large the content may be. The `accept` probe that + // normally precedes a `store` is an optimisation, not a guarantee: nothing in + // the protocol requires it, and several peers can pass their own probe and + // then transfer at the same time. + await pullUnderIntakeLimits(cid) + + // Every block is local now, so pinning walks the blockstore rather than the + // network. return registerPinned(CID.parse(cid), cid) } @@ -259,38 +288,44 @@ export async function hasRoomForAnotherCopy(): Promise { } /** - * Pull a file into the blockstore without pinning or registering it. + * Pull a DAG into the blockstore under the limits a peer transfer carries: how + * many may run at once, how much space they may take together, and how large + * one may be. * - * Draining the content is what fetches every block of the DAG, and it leaves - * them exactly where a read would: unpinned, reclaimed when space is short. The - * node can serve the file from now on without promising to keep it. + * The slot is taken before free space is measured, so everything after it lives + * in the guarded region — a filesystem error while reading capacity used to + * leave the slot taken, and enough of them disabled intake until restart. + * + * Draining the content is what both fetches every block and bounds the + * transfer, so this expects UnixFS file content. Every CID this node registers + * is one: uploads add byte streams rather than files, which is what keeps a + * directory wrapper out of the registry in the first place. * * @returns Bytes pulled */ -export async function cacheFileLocally(cid: string): Promise { - // This is the one operation any peer may ask for, so it carries the limits an - // upload carries: how many may run at once, and how much space they may take - // together. The size is unknown before the transfer, so the aggregate request - // limit is claimed pessimistically and given back at the end. +async function pullUnderIntakeLimits(cid: string): Promise { if (!incomingCopyLimiter.tryAcquire()) { throw new Error('Too many copies are already being received') } - const claim = claimSpace({ - bytes: config.storage.maxRequestSizeBytes, - availableBytes: Number(await availableStorageSize(blockstorePath)), - reserveBytes: config.storage.diskReserveBytes - }) + let claim: Claim | undefined - if (claim === undefined) { - incomingCopyLimiter.release() - throw new Error('No room for another copy') - } + try { + // The size is unknown before the transfer, so the aggregate request limit + // is claimed pessimistically and given back at the end. + claim = claimSpace({ + bytes: config.storage.maxRequestSizeBytes, + availableBytes: Number(await availableStorageSize(blockstorePath)), + reserveBytes: config.storage.diskReserveBytes + }) + + if (claim === undefined) { + throw new Error('No room for another copy') + } - const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) - let bytes = 0 + const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) + let bytes = 0 - try { for await (const chunk of ifs.cat(CID.parse(cid), { signal })) { bytes += chunk.byteLength @@ -301,11 +336,40 @@ export async function cacheFileLocally(cid: string): Promise { return bytes } finally { - claim.release() + claim?.release() incomingCopyLimiter.release() } } +/** + * Hold a file for a peer without pinning or registering it. + * + * Draining the content is what fetches every block of the DAG, and it leaves + * them exactly where a read would: unpinned, reclaimed when space is short. The + * node can serve the file from now on without promising to keep it. + * + * This is the one operation any peer may ask for, so it also spends a budget + * the caller cannot renew at will. + * + * @returns Bytes pulled + */ +export async function cacheFileLocally(cid: string, peerId: string): Promise { + if (!mayAcceptIntake(peerId)) { + throw new Error('Cache budget for this peer is spent') + } + + let bytes = 0 + + try { + bytes = await pullUnderIntakeLimits(cid) + return bytes + } finally { + // Recorded even when the transfer failed. Otherwise content that never + // arrives is free to ask for, and asking is most of the cost. + recordIntake(peerId, bytes) + } +} + export interface RepairReport { checked: number underReplicated: number diff --git a/src/storage/state.ts b/src/storage/state.ts index d9e3bb9..298c7eb 100644 --- a/src/storage/state.ts +++ b/src/storage/state.ts @@ -24,6 +24,17 @@ export const uploadLimiter = new ConcurrencyLimiter(config.storage.maxConcurrent */ export const COPY_INTAKE_SHARE = 4 +/** + * Copies a node accepts at once however small its upload limit is. + * + * A share alone collapses on a conservatively configured node: a quarter of + * four is one, and then a single peer sending a two-file upload has its second + * copy refused. The floor is what a fan-out needs to land, and the space claim + * still refuses copies the disk cannot take, so raising it does not weaken the + * reserve. + */ +export const COPY_INTAKE_FLOOR = 4 + /** * Guards how many copies peers may push into the blockstore at once. * @@ -32,5 +43,5 @@ export const COPY_INTAKE_SHARE = 4 * own. */ export const incomingCopyLimiter = new ConcurrencyLimiter( - Math.max(1, Math.floor(config.storage.maxConcurrentUploads / COPY_INTAKE_SHARE)) + Math.max(COPY_INTAKE_FLOOR, Math.floor(config.storage.maxConcurrentUploads / COPY_INTAKE_SHARE)) ) diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts index 231dd1d..4d03cbc 100644 --- a/test/garbageCollectionPlan.test.ts +++ b/test/garbageCollectionPlan.test.ts @@ -83,6 +83,68 @@ describe('planGarbageCollection', () => { assert.equal(plan.estimatedBytesAfter, 600) }) + it('evicts for the reserve even when the blockstore is below the low watermark', () => { + // The watermarks say nothing about free space. A small blockstore on a full + // volume is under no watermark pressure at all, and comparing against one + // would have this run reclaim nothing while the disk stayed full. + const plan = planGarbageCollection({ + blockstoreBytes: 1000, + watermarks: { highWatermarkBytes: 4000, lowWatermarkBytes: 3000 }, + availableBytes: 400, + reserveBytes: 500, + records: [record({ cid: 'a', state: 'temporary', expiresAt: 9000, storedBytes: 300 })], + now: 1000 + }) + + assert.equal(plan.trigger, 'disk-reserve') + assert.deepEqual( + plan.evicted.map((item) => item.cid), + ['a'] + ) + }) + + it('keeps evicting until the reserve is honoured again, not until the first file', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 1000, + watermarks: { highWatermarkBytes: 4000, lowWatermarkBytes: 3000 }, + availableBytes: 400, + reserveBytes: 500, + records: [ + record({ cid: 'a', state: 'temporary', createdAt: 1, expiresAt: 9000, storedBytes: 100 }), + record({ cid: 'b', state: 'temporary', createdAt: 2, expiresAt: 9000, storedBytes: 100 }), + record({ cid: 'c', state: 'temporary', createdAt: 3, expiresAt: 9000, storedBytes: 100 }), + record({ cid: 'd', state: 'temporary', createdAt: 4, expiresAt: 9000, storedBytes: 100 }) + ], + now: 1000 + }) + + // 500 * 1.25 - 400 = 225 bytes short, so three files are needed + assert.deepEqual( + plan.evicted.map((item) => item.cid), + ['a', 'b', 'c'] + ) + }) + + it('counts what the expired files already free towards the reserve', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 1000, + watermarks: { highWatermarkBytes: 4000, lowWatermarkBytes: 3000 }, + availableBytes: 400, + reserveBytes: 500, + records: [ + record({ cid: 'gone', state: 'temporary', expiresAt: 500, storedBytes: 300 }), + record({ cid: 'alive', state: 'temporary', expiresAt: 9000, storedBytes: 300 }) + ], + now: 1000 + }) + + assert.deepEqual( + plan.expired.map((item) => item.cid), + ['gone'] + ) + assert.deepEqual(plan.evicted, []) + }) + it('stops evicting as soon as the estimate is below the low watermark', () => { const plan = planGarbageCollection({ blockstoreBytes: 750, diff --git a/test/intakeBudget.test.ts b/test/intakeBudget.test.ts new file mode 100644 index 0000000..8bdb736 --- /dev/null +++ b/test/intakeBudget.test.ts @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict' +import { beforeEach, describe, it } from 'node:test' +import { + INTAKE_WINDOW_MS, + mayAcceptIntake, + PER_PEER_INTAKE_BYTES, + PER_PEER_INTAKE_REQUESTS, + recordIntake, + resetIntakeBudget, + TOTAL_INTAKE_BYTES +} from '../src/storage/intakeBudget.js' + +const NOW = 1_000_000 + +beforeEach(() => resetIntakeBudget()) + +describe('cache intake budget', () => { + it('lets an idle peer through', () => { + assert.equal(mayAcceptIntake('peer-a', NOW), true) + }) + + it('stops a peer that spent its bytes', () => { + recordIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW) + + assert.equal(mayAcceptIntake('peer-a', NOW), false) + }) + + it('leaves other peers alone when one is spent', () => { + recordIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW) + + assert.equal(mayAcceptIntake('peer-b', NOW), true) + }) + + it('counts a failed transfer as a request, so asking is never free', () => { + for (let i = 0; i < PER_PEER_INTAKE_REQUESTS; i += 1) { + recordIntake('peer-a', 0, NOW) + } + + assert.equal(mayAcceptIntake('peer-a', NOW), false) + }) + + it('stops everyone once the node-wide budget is spent', () => { + // Minting identities is free until node membership exists, so the shared + // budget is the one that has to hold. + const peers = Math.ceil(TOTAL_INTAKE_BYTES / PER_PEER_INTAKE_BYTES) + + for (let i = 0; i < peers; i += 1) { + recordIntake(`peer-${i}`, PER_PEER_INTAKE_BYTES, NOW) + } + + assert.equal(mayAcceptIntake('fresh-peer', NOW), false) + }) + + it('opens the budget again in the next window', () => { + recordIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW) + + assert.equal(mayAcceptIntake('peer-a', NOW + INTAKE_WINDOW_MS), true) + }) + + it('forgets a peer whose window closed, so the accounting cannot grow forever', () => { + recordIntake('peer-a', 1, NOW) + recordIntake('peer-b', 1, NOW + INTAKE_WINDOW_MS) + + // peer-a is gone rather than merely out of date, so a spent identity is not + // remembered for longer than it can be charged. + assert.equal(mayAcceptIntake('peer-a', NOW + INTAKE_WINDOW_MS), true) + }) +}) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 3d78fd9..38c7f88 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -383,6 +383,40 @@ describe('garbage collection against fixture data', () => { await registry.remove(unreachable) }) + it('keeps every record when the collector could not delete everything', async () => { + const { registry, cids } = await createFixture() + + // Helia resolves even after reporting a block it could not delete. Which + // file the survivor belongs to is unknowable, so nothing may be + // deregistered on the strength of a run that did not finish. + const flaky = new Proxy(node, { + get: (target, property) => + property === 'gc' + ? (options?: { onProgress?: (event: unknown) => void }) => { + options?.onProgress?.({ + type: 'helia:gc:error', + detail: new Error('blockstore I/O error') + }) + return Promise.resolve() + } + : Reflect.get(target, property, target) + }) as IpfsNode + + const report = await runGarbageCollection({ + node: flaky, + registry, + watermarks: TIGHT, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.equal(report.collected, false) + assert.equal(report.errors.length, 1) + + // Released, so the next run retries it — but still there to be retried. + assert.equal((await registry.get(cids.abandoned))?.state, 'expired') + }) + it('restores a missing pin on confirmed content before deleting anything', async () => { const { registry, cids } = await createFixture() await unpinFile(node, CID.parse(cids.confirmed)) @@ -420,6 +454,26 @@ describe('registry backfill', () => { assert.ok(second.known > 0) }) + it('reports a registry failure as an error rather than as incomplete content', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'unwritable')) + await pinFile(node, cid) + + // A datastore that is unavailable says nothing about whether the DAG is + // local, and reporting it as incomplete sends an operator after the wrong + // problem entirely. + const failing = { + get: (value: string) => registry.get(value), + save: () => Promise.reject(new Error('datastore is unavailable')) + } as unknown as FileRegistry + + const report = await backfillRegistryFromPins({ node, unixfs: ifs, registry: failing }) + + assert.equal(report.incomplete, 0) + assert.ok(report.errors.some((message) => message.includes(cid.toString()))) + assert.equal(await registry.get(cid.toString()), undefined) + }) + it('leaves an existing record untouched', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'already-known')) From 28af1d9f8795bfb21aabd5901637fa18925a8db7 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Tue, 25 Aug 2026 09:54:20 +0300 Subject: [PATCH 114/167] feat(storage): enhance intake budget management and registry operations --- docs/storage-lifecycle.md | 34 ++++++-- src/gc.cron.ts | 8 +- src/storage/backfill.ts | 13 ++- src/storage/intakeBudget.ts | 169 ++++++++++++++++++++++++++++-------- src/storage/registry.ts | 120 +++++++++++++++++++------ src/storage/service.ts | 63 ++++++++++---- src/storage/sweep.ts | 19 +++- test/fileRegistry.test.ts | 59 +++++++++++++ test/intakeBudget.test.ts | 81 ++++++++++++----- test/sweepBatch.test.ts | 10 +++ 10 files changed, 459 insertions(+), 117 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 15197b1..1e6ffac 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -268,13 +268,19 @@ reserve. What stays behind the configuration check is everything that makes a node responsible: pinning, registration, and being counted as a holder. Open is not the same as unbounded. A copy taken this way is charged to the peer -that asked for it, against a per-peer and a node-wide budget over a rolling -hour, and a request that fails still costs a request — otherwise asking for -content that never arrives would be free, and asking is most of the cost. The -node-wide budget is the one that matters while peer identities are free to mint; -both sit far above ordinary traffic, where a node that just accepted an upload -asks two peers to cache it. Operator-facing transfer limits are separate work -(#29). +that asked for it, against a per-peer and a node-wide budget over the last hour. +The most a copy could cost is reserved before the first block is fetched and the +unused part is given back at the end: counting on completion instead would let +every concurrent request read the same figure and pass, and would charge nothing +at all to a peer that sends almost a whole copy and then aborts. The request +itself is charged and never refunded, because asking is most of the cost. + +The hour is measured in six slices that expire one at a time rather than as a +counter reset on the hour, which would let a peer spend its whole allowance just +before the boundary and again just after it. The node-wide budget is the one +that matters while peer identities are free to mint; both sit far above ordinary +traffic, where a node that just accepted an upload asks two peers to cache it. +Operator-facing transfer limits are separate work (#29). It is a stopgap, not the answer. A file spread this way survives the loss of the node it was uploaded to, but only until its peers need the space, and no repair @@ -444,6 +450,12 @@ Startup walks the pinset once and records anything the registry does not know as is not fully local is skipped rather than recorded — calling it confirmed would claim more than the node can serve. +Records are created only while the CID is absent, and every read-modify-write on +the registry is serialised per CID. Uploads are being served while this runs, so +without that an upload registering a CID mid-walk would be overwritten as +confirmed: an operator who required confirmation would find the file durable +anyway, under its CID instead of its name. + It does not block the API. The walk is as long as the pinset, and holding reads back for the whole migration would penalise exactly the nodes with the most to serve. Reads, uploads and incoming copies do not need a complete registry; the @@ -465,8 +477,12 @@ Two consequences are worth planning for: ### Before the first collection -1. Run `POST /api/storage/gc?dryRun=true` and review `releasedCids` and - `retainedCids`. Nothing is deleted by a dry run. +1. Run `POST /api/storage/gc?dryRun=true` and review `releasedCids`, + `retainedCids`, and `demoted`. Nothing is unpinned and nothing is deleted by + a dry run. It does ask peers whether they hold the files it would hand over, + because a handover unpins a local copy and a plan without them is not the + plan the real run follows. The sweep position is left where it was, so the + real run starts on the same files. 2. Confirm that every CID that must survive appears in `retainedCids`. If a CID is missing, it is not confirmed: pin it with `POST /api/helia/pin/:cid` or confirm it with `POST /api/file/:cid/confirm` first. diff --git a/src/gc.cron.ts b/src/gc.cron.ts index 8619ae4..e7038d8 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -43,7 +43,13 @@ export async function collectGarbage( try { // Hand copies over before collecting, so the blocks they free are reclaimed // by the same pass instead of waiting for the next one. - const demoted = options.dryRun === true ? [] : (await demoteReleasableCopies()).demoted + // + // A dry run evaluates them too. A handover unpins a local copy, so a plan + // that leaves handovers out is not the plan the real run follows — and the + // pre-upgrade dry run is read precisely to see which local pins go. It asks + // the same peers the real pass would, and stops short of acting on the + // answer. + const demoted = (await demoteReleasableCopies({ dryRun: options.dryRun })).demoted const metrics = await refreshStorageMetrics() diff --git a/src/storage/backfill.ts b/src/storage/backfill.ts index 00a0f6a..0fce9c3 100644 --- a/src/storage/backfill.ts +++ b/src/storage/backfill.ts @@ -84,7 +84,12 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis } try { - await options.registry.save({ + // Create-if-absent rather than a plain write: the API is serving while + // this runs, so an upload can register the same CID between the check + // above and this line. Overwriting it would replace a temporary record + // with a confirmed one that never expires, bypassing the confirmation an + // operator required and losing the file's name. + const created = await options.registry.createIfAbsent({ cid, // Nothing recorded the original name, and inventing one would be worse // than admitting it is unknown. @@ -102,7 +107,11 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis replicas: [] }) - report.registered += 1 + if (created === undefined) { + report.known += 1 + } else { + report.registered += 1 + } } catch (err) { report.errors.push(`Registry write failed for ${cid}: ${(err as Error).message}`) } diff --git a/src/storage/intakeBudget.ts b/src/storage/intakeBudget.ts index 636b536..7fdf329 100644 --- a/src/storage/intakeBudget.ts +++ b/src/storage/intakeBudget.ts @@ -19,9 +19,21 @@ * monthly transfer accounting (#29), which supersedes them. */ -/** Rolling window the budgets are measured over. */ +/** Window the budgets are measured over. */ export const INTAKE_WINDOW_MS = 60 * 60 * 1000 +/** + * Slices the window is measured in. + * + * A single counter reset on the hour is not a rolling window: a peer spends its + * whole allowance just before the boundary and the whole allowance again just + * after it, so the real limit is twice what it claims to be. Buckets that + * expire one at a time bound that overshoot to a sixth instead. + */ +export const INTAKE_BUCKETS = 6 + +const BUCKET_MS = INTAKE_WINDOW_MS / INTAKE_BUCKETS + /** Bytes one peer may have this node pull in per window. */ export const PER_PEER_INTAKE_BYTES = 4 * 1024 ** 3 @@ -34,63 +46,144 @@ export const TOTAL_INTAKE_BYTES = 16 * 1024 ** 3 /** * Requests every peer together may make per window. * - * This also bounds how much the accounting itself can cost: a window can hold - * at most this many distinct peers, so minting identities cannot grow the map + * This also bounds how much the accounting itself can cost: a window holds at + * most this many distinct peers, so minting identities cannot grow the map * without also spending the shared budget. */ export const TOTAL_INTAKE_REQUESTS = 5000 -interface Spend { - windowStartedAt: number +interface Bucket { + startedAt: number bytes: number requests: number } -const perPeer = new Map() -let total: Spend = { windowStartedAt: 0, bytes: 0, requests: 0 } +/** What one spender used over the buckets still inside the window. */ +class Spend { + private buckets: Bucket[] = [] + + /** Buckets the window still covers; the rest are dropped as they age out. */ + private live(now: number): Bucket[] { + const oldest = now - INTAKE_WINDOW_MS + this.buckets = this.buckets.filter((bucket) => bucket.startedAt > oldest) + return this.buckets + } -/** The spend for the window `now` falls in, starting a fresh one when it moved on. */ -function current(spend: Spend | undefined, now: number): Spend { - if (spend === undefined || now - spend.windowStartedAt >= INTAKE_WINDOW_MS) { - return { windowStartedAt: now, bytes: 0, requests: 0 } + totals(now: number): { bytes: number; requests: number } { + let bytes = 0 + let requests = 0 + + for (const bucket of this.live(now)) { + bytes += bucket.bytes + requests += bucket.requests + } + + return { bytes, requests } } - return spend + /** + * Charge the bucket `now` falls in. + * + * @returns The bucket charged, so a reservation can give part of it back + */ + charge(now: number, bytes: number, requests: number): Bucket { + const startedAt = Math.floor(now / BUCKET_MS) * BUCKET_MS + const live = this.live(now) + let bucket = live.find((item) => item.startedAt === startedAt) + + if (bucket === undefined) { + bucket = { startedAt, bytes: 0, requests: 0 } + live.push(bucket) + } + + bucket.bytes += bytes + bucket.requests += requests + + return bucket + } + + spent(now: number): boolean { + return this.live(now).length > 0 + } } -/** Whether a peer may ask this node to pull in one more copy. */ -export function mayAcceptIntake(peerId: string, now: number = Date.now()): boolean { - const peer = current(perPeer.get(peerId), now) - const all = current(total, now) - - return ( - peer.bytes < PER_PEER_INTAKE_BYTES && - peer.requests < PER_PEER_INTAKE_REQUESTS && - all.bytes < TOTAL_INTAKE_BYTES && - all.requests < TOTAL_INTAKE_REQUESTS - ) +const perPeer = new Map() +let total = new Spend() + +export interface IntakeReservation { + /** + * Correct the reservation to what the transfer really cost. + * + * Called with the bytes that actually crossed the network, whether or not the + * transfer finished: a peer that sends almost everything and then aborts has + * still spent the bandwidth. + */ + settle(bytes: number): void } /** - * Account for a request that was served. + * Reserve the most a copy could cost, before fetching a single block. * - * A transfer that failed is recorded as zero bytes and still counts as a - * request, so a peer cannot spend this node's bandwidth for free by asking for - * content that never arrives. + * Checking the counters and charging them afterwards is not a limit: every + * concurrent request reads the same figure and passes, and with eight intake + * slots that admits eight full transfers against a budget for one. The size is + * unknown before the transfer, so the maximum is charged up front and the + * unused part is given back at the end. + * + * @returns The reservation, or `undefined` when the budget cannot cover it */ -export function recordIntake(peerId: string, bytes: number, now: number = Date.now()): void { - const peer = current(perPeer.get(peerId), now) - peer.bytes += bytes - peer.requests += 1 +export function reserveIntake( + peerId: string, + maxBytes: number, + now: number = Date.now() +): IntakeReservation | undefined { + const peer = perPeer.get(peerId) ?? new Spend() + const peerTotals = peer.totals(now) + const allTotals = total.totals(now) + + if ( + peerTotals.bytes + maxBytes > PER_PEER_INTAKE_BYTES || + peerTotals.requests >= PER_PEER_INTAKE_REQUESTS || + allTotals.bytes + maxBytes > TOTAL_INTAKE_BYTES || + allTotals.requests >= TOTAL_INTAKE_REQUESTS + ) { + return undefined + } + perPeer.set(peerId, peer) - total = current(total, now) - total.bytes += bytes - total.requests += 1 + // The request is charged now and never given back: making one is most of the + // cost, so a transfer that fails must not be free to repeat. + const peerBucket = peer.charge(now, maxBytes, 1) + const totalBucket = total.charge(now, maxBytes, 1) + + if (perPeer.size > TOTAL_INTAKE_REQUESTS) { + for (const [id, spend] of perPeer) { + if (!spend.spent(now)) { + perPeer.delete(id) + } + } + } + + let settled = false + + return { + settle(bytes: number): void { + if (settled) { + return + } + + settled = true + + // Negative when the transfer cost less than was reserved, positive when + // it cost more — which happens when a request may be larger than the + // whole budget and the reservation had to be capped at it. + const difference = Math.max(0, bytes) - maxBytes - for (const [id, spend] of perPeer) { - if (now - spend.windowStartedAt >= INTAKE_WINDOW_MS) { - perPeer.delete(id) + // A bucket that aged out in the meantime is detached from the window + // already, so correcting it changes nothing. + peerBucket.bytes = Math.max(0, peerBucket.bytes + difference) + totalBucket.bytes = Math.max(0, totalBucket.bytes + difference) } } } @@ -98,5 +191,5 @@ export function recordIntake(peerId: string, bytes: number, now: number = Date.n /** Forget every window. For tests, where no peer is really spending anything. */ export function resetIntakeBudget(): void { perPeer.clear() - total = { windowStartedAt: 0, bytes: 0, requests: 0 } + total = new Spend() } diff --git a/src/storage/registry.ts b/src/storage/registry.ts index aafeb8e..8e4e33f 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -79,6 +79,59 @@ export class FileRegistry { return new Key(`${this.prefix}/${cid}`) } + /** + * Work already queued for a CID, so the next caller waits for it. + * + * Every compound operation here reads a record, decides from it, and writes + * the result. Two of those running at once for the same CID lose one of the + * decisions, and the datastore offers no compare-and-swap to prevent it. The + * registry is a single instance per process, so a queue per CID is enough. + * + * It stopped being theoretical when the startup backfill was moved off the + * critical path: it now runs while the API accepts uploads, and an upload + * registering a CID between the backfill's read and its write would be + * overwritten as confirmed — bypassing the confirmation an operator required + * and losing the uploaded file's name. + */ + private readonly queued = new Map>() + + /** Run `work` with nothing else touching this CID. */ + private async exclusive(cid: string, work: () => Promise): Promise { + const previous = this.queued.get(cid) ?? Promise.resolve() + // Runs whether or not the previous holder succeeded; its failure is its own. + const result = previous.then(work, work) + const settled = result.then( + () => undefined, + () => undefined + ) + + this.queued.set(cid, settled) + + try { + return await result + } finally { + // The map must not keep an entry for every CID the node ever touched. + if (this.queued.get(cid) === settled) { + this.queued.delete(cid) + } + } + } + + /** + * Record a file, but only while the registry does not know it. + * + * @returns The stored record, or `undefined` when one already existed + */ + async createIfAbsent(record: FileRecord): Promise { + return this.exclusive(record.cid, async () => { + if (await this.get(record.cid)) { + return undefined + } + + return this.save(record) + }) + } + async get(cid: string): Promise { try { return JSON.parse(decoder.decode(await this.datastore.get(this.key(cid)))) as FileRecord @@ -159,6 +212,13 @@ export class FileRegistry { async register( file: NewFileRecord, options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } + ): Promise { + return this.exclusive(file.cid, () => this.registerExclusively(file, options)) + } + + private async registerExclusively( + file: NewFileRecord, + options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } ): Promise { const now = options.now ?? Date.now() const existing = await this.get(file.cid) @@ -194,22 +254,24 @@ export class FileRegistry { /** Move a temporary file to the durable state. Confirming twice is a no-op. */ async confirm(cid: string, now: number = Date.now()): Promise { - const record = await this.get(cid) - if (!record) { - return undefined - } + return this.exclusive(cid, async () => { + const record = await this.get(cid) + if (!record) { + return undefined + } - if (record.state === 'confirmed') { - return record - } + if (record.state === 'confirmed') { + return record + } - return this.save({ - ...record, - state: 'confirmed', - expiresAt: null, - confirmedAt: now, - pinned: true, - heldLocally: true + return this.save({ + ...record, + state: 'confirmed', + expiresAt: null, + confirmedAt: now, + pinned: true, + heldLocally: true + }) }) } @@ -218,12 +280,14 @@ export class FileRegistry { * The blocks stay on disk until the collector runs. */ async release(cid: string): Promise { - const record = await this.get(cid) - if (!record) { - return undefined - } + return this.exclusive(cid, async () => { + const record = await this.get(cid) + if (!record) { + return undefined + } - return this.save({ ...record, state: 'expired', pinned: false, heldLocally: false }) + return this.save({ ...record, state: 'expired', pinned: false, heldLocally: false }) + }) } /** @@ -233,18 +297,24 @@ export class FileRegistry { * node simply stopped being one of its holders. */ async releaseLocalCopy(cid: string): Promise { - const record = await this.get(cid) - return record ? this.save({ ...record, pinned: false, heldLocally: false }) : undefined + return this.exclusive(cid, async () => { + const record = await this.get(cid) + return record ? this.save({ ...record, pinned: false, heldLocally: false }) : undefined + }) } async setPinned(cid: string, pinned: boolean): Promise { - const record = await this.get(cid) - return record ? this.save({ ...record, pinned }) : undefined + return this.exclusive(cid, async () => { + const record = await this.get(cid) + return record ? this.save({ ...record, pinned }) : undefined + }) } async setReplicas(cid: string, replicas: string[]): Promise { - const record = await this.get(cid) - return record ? this.save({ ...record, replicas }) : undefined + return this.exclusive(cid, async () => { + const record = await this.get(cid) + return record ? this.save({ ...record, replicas }) : undefined + }) } } diff --git a/src/storage/service.ts b/src/storage/service.ts index 24ee2a3..f80e487 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -25,7 +25,7 @@ import { type ReplicationCallOptions } from './replicationProtocol.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' -import { mayAcceptIntake, recordIntake } from './intakeBudget.js' +import { PER_PEER_INTAKE_BYTES, reserveIntake } from './intakeBudget.js' import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter } from './state.js' import { nextSweepBatch } from './sweep.js' @@ -287,6 +287,11 @@ export async function hasRoomForAnotherCopy(): Promise { } } +/** How much of a transfer has arrived, readable while it is still running. */ +export interface TransferProgress { + bytes: number +} + /** * Pull a DAG into the blockstore under the limits a peer transfer carries: how * many may run at once, how much space they may take together, and how large @@ -301,9 +306,15 @@ export async function hasRoomForAnotherCopy(): Promise { * is one: uploads add byte streams rather than files, which is what keeps a * directory wrapper out of the registry in the first place. * + * @param progress Updated as bytes arrive, so a caller can still see what a + * failed transfer cost. A peer that sends almost everything and then aborts + * has spent the bandwidth either way. * @returns Bytes pulled */ -async function pullUnderIntakeLimits(cid: string): Promise { +async function pullUnderIntakeLimits( + cid: string, + progress: TransferProgress = { bytes: 0 } +): Promise { if (!incomingCopyLimiter.tryAcquire()) { throw new Error('Too many copies are already being received') } @@ -324,17 +335,16 @@ async function pullUnderIntakeLimits(cid: string): Promise { } const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) - let bytes = 0 for await (const chunk of ifs.cat(CID.parse(cid), { signal })) { - bytes += chunk.byteLength + progress.bytes += chunk.byteLength - if (bytes > config.storage.maxRequestSizeBytes) { + if (progress.bytes > config.storage.maxRequestSizeBytes) { throw new Error('Copy is larger than this node accepts') } } - return bytes + return progress.bytes } finally { claim?.release() incomingCopyLimiter.release() @@ -354,19 +364,30 @@ async function pullUnderIntakeLimits(cid: string): Promise { * @returns Bytes pulled */ export async function cacheFileLocally(cid: string, peerId: string): Promise { - if (!mayAcceptIntake(peerId)) { + // Reserved before a block is fetched rather than counted afterwards. A check + // that only reads the counters lets every concurrent request see the same + // figure and pass, and a transfer charged on completion costs a peer nothing + // when it aborts after sending almost all of it. + // Capped at the budget itself: a node configured to accept requests larger + // than a peer's whole hourly allowance would otherwise refuse every copy + // outright instead of allowing one at a time. What the transfer really costs + // is settled afterwards either way. + const reservation = reserveIntake( + peerId, + Math.min(config.storage.maxRequestSizeBytes, PER_PEER_INTAKE_BYTES) + ) + + if (reservation === undefined) { throw new Error('Cache budget for this peer is spent') } - let bytes = 0 + const progress: TransferProgress = { bytes: 0 } try { - bytes = await pullUnderIntakeLimits(cid) - return bytes + return await pullUnderIntakeLimits(cid, progress) } finally { - // Recorded even when the transfer failed. Otherwise content that never - // arrives is free to ask for, and asking is most of the cost. - recordIntake(peerId, bytes) + // What actually crossed the network, finished or not. + reservation.settle(progress.bytes) } } @@ -544,7 +565,9 @@ export interface DemotionReport { * count: there is nowhere for the copy to go, and every node is expected to * hold it. */ -export async function demoteReleasableCopies(): Promise { +export async function demoteReleasableCopies( + options: { dryRun?: boolean } = {} +): Promise { const report: DemotionReport = { checked: 0, demoted: [], kept: [] } if (!config.replication.enabled) { @@ -559,7 +582,10 @@ export async function demoteReleasableCopies(): Promise { 'demote', (await fileRegistry.all()).filter( (record) => record.state === 'confirmed' && record.heldLocally - ) + ), + // A dry run must not take the batch: the real pass that follows would then + // resume past the very files the operator was shown. + { advance: options.dryRun !== true } ) report.checked = candidates.length @@ -595,9 +621,14 @@ export async function demoteReleasableCopies(): Promise { continue } + report.demoted.push(record.cid) + + if (options.dryRun === true) { + continue + } + await unpinFile(helia, CID.parse(record.cid)) await fileRegistry.releaseLocalCopy(record.cid) - report.demoted.push(record.cid) logger.info(`Released the local copy of ${record.cid}; ${holders.length} peers hold it`) } diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index 03bc19c..88ebdac 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -21,12 +21,22 @@ const cursors = new Map() * * @param sweep Which sweep is asking; each keeps its own position * @param records Every candidate, in a stable order + * @param options `advance: false` reports the next batch without taking it, for + * a dry run that must not move the position the real pass resumes from */ -export function nextSweepBatch(sweep: SweepName, records: T[]): T[] { +export function nextSweepBatch( + sweep: SweepName, + records: T[], + options: { advance?: boolean } = {} +): T[] { const size = SWEEP_BATCHES[sweep] + const advance = options.advance !== false if (records.length <= size) { - cursors.delete(sweep) + if (advance) { + cursors.delete(sweep) + } + return records } @@ -36,7 +46,10 @@ export function nextSweepBatch(sweep: SweepName, reco const start = resumeAt > 0 && resumeAt < records.length ? resumeAt : 0 const batch = [...records.slice(start), ...records.slice(0, start)].slice(0, size) - cursors.set(sweep, batch[batch.length - 1].cid) + + if (advance) { + cursors.set(sweep, batch[batch.length - 1].cid) + } return batch } diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index fe1845c..95bb3d0 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -135,6 +135,65 @@ describe('FileRegistry', () => { assert.equal(await registry.get(CID_A), undefined) }) + it('creates a record only while the registry does not know the CID', async () => { + const registry = createRegistry() + const legacy: FileRecord = { + cid: CID_A, + name: CID_A, + state: 'confirmed', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + fileSize: 10, + storedBytes: 10, + pinned: true, + heldLocally: true, + replicas: [] + } + + assert.notEqual(await registry.createIfAbsent(legacy), undefined) + assert.equal(await registry.createIfAbsent({ ...legacy, name: 'second' }), undefined) + assert.equal((await registry.get(CID_A))?.name, CID_A) + }) + + it('does not let a backfill overwrite an upload registered while it ran', async () => { + // Both read the registry, decide, and write. Startup backfill now runs + // while the API accepts uploads, and losing this race turns a temporary + // upload into a confirmed record that never expires. + const registry = createRegistry() + const legacy: FileRecord = { + cid: CID_B, + name: CID_B, + state: 'confirmed', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + fileSize: 10, + storedBytes: 10, + pinned: true, + heldLocally: true, + replicas: [] + } + + const [upload, backfilled] = await Promise.all([ + registry.register(newFile(CID_B), { confirmationRequired: true, temporaryTtlMs: TTL }), + registry.createIfAbsent(legacy) + ]) + + const stored = await registry.get(CID_B) + + if (backfilled === undefined) { + // The upload got there first, so its lifecycle is the one that survives + assert.equal(stored?.state, 'temporary') + assert.equal(stored?.name, upload.name) + } else { + // The backfill got there first, and the upload registered on top of it + assert.equal(stored?.name, upload.name) + } + + assert.notEqual(stored, undefined) + }) + it('reports an empty registry for a store that does not exist yet', async () => { const missing = join(tmpdir(), `ipfs-node-registry-missing-${process.pid}`) const registry = new FileRegistry(new FsDatastore(missing)) diff --git a/test/intakeBudget.test.ts b/test/intakeBudget.test.ts index 8bdb736..378d204 100644 --- a/test/intakeBudget.test.ts +++ b/test/intakeBudget.test.ts @@ -2,67 +2,102 @@ import assert from 'node:assert/strict' import { beforeEach, describe, it } from 'node:test' import { INTAKE_WINDOW_MS, - mayAcceptIntake, PER_PEER_INTAKE_BYTES, PER_PEER_INTAKE_REQUESTS, - recordIntake, + reserveIntake, resetIntakeBudget, TOTAL_INTAKE_BYTES } from '../src/storage/intakeBudget.js' -const NOW = 1_000_000 +/** Aligned to a bucket boundary, so the arithmetic in the tests is readable. */ +const NOW = 10 * INTAKE_WINDOW_MS +const COPY = 512 * 1024 ** 2 beforeEach(() => resetIntakeBudget()) describe('cache intake budget', () => { it('lets an idle peer through', () => { - assert.equal(mayAcceptIntake('peer-a', NOW), true) + assert.notEqual(reserveIntake('peer-a', COPY, NOW), undefined) }) it('stops a peer that spent its bytes', () => { - recordIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW) + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW)?.settle(PER_PEER_INTAKE_BYTES) - assert.equal(mayAcceptIntake('peer-a', NOW), false) + assert.equal(reserveIntake('peer-a', 1, NOW), undefined) }) it('leaves other peers alone when one is spent', () => { - recordIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW) + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW)?.settle(PER_PEER_INTAKE_BYTES) - assert.equal(mayAcceptIntake('peer-b', NOW), true) + assert.notEqual(reserveIntake('peer-b', COPY, NOW), undefined) }) - it('counts a failed transfer as a request, so asking is never free', () => { + it('charges the maximum up front, so concurrent pulls cannot all pass', () => { + // All but one byte of the budget is already gone + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES - 1, NOW)?.settle(PER_PEER_INTAKE_BYTES - 1) + + // Every intake slot asks at once; reading the counters alone would admit + // them all, because none of them has finished to be counted yet + const granted = Array.from({ length: 8 }, () => reserveIntake('peer-a', COPY, NOW)).filter( + (reservation) => reservation !== undefined + ) + + assert.equal(granted.length, 0) + }) + + it('gives back only what the transfer did not use', () => { + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW)?.settle(0) + + assert.notEqual(reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW), undefined) + }) + + it('charges an aborted transfer for the bytes it already moved', () => { + // A peer that sends almost a whole copy and then withholds the tail has + // spent the bandwidth, whether or not the pull resolved + reserveIntake('peer-a', COPY, NOW)?.settle(COPY - 1) + + assert.equal(reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW), undefined) + }) + + it('charges the excess when a transfer cost more than was reserved', () => { + reserveIntake('peer-a', COPY, NOW)?.settle(PER_PEER_INTAKE_BYTES) + + assert.equal(reserveIntake('peer-a', 1, NOW), undefined) + }) + + it('counts a request that moved nothing, so asking is never free', () => { for (let i = 0; i < PER_PEER_INTAKE_REQUESTS; i += 1) { - recordIntake('peer-a', 0, NOW) + reserveIntake('peer-a', 1, NOW)?.settle(0) } - assert.equal(mayAcceptIntake('peer-a', NOW), false) + assert.equal(reserveIntake('peer-a', 1, NOW), undefined) }) it('stops everyone once the node-wide budget is spent', () => { // Minting identities is free until node membership exists, so the shared - // budget is the one that has to hold. + // budget is the one that has to hold const peers = Math.ceil(TOTAL_INTAKE_BYTES / PER_PEER_INTAKE_BYTES) for (let i = 0; i < peers; i += 1) { - recordIntake(`peer-${i}`, PER_PEER_INTAKE_BYTES, NOW) + reserveIntake(`peer-${i}`, PER_PEER_INTAKE_BYTES, NOW)?.settle(PER_PEER_INTAKE_BYTES) } - assert.equal(mayAcceptIntake('fresh-peer', NOW), false) + assert.equal(reserveIntake('fresh-peer', COPY, NOW), undefined) }) - it('opens the budget again in the next window', () => { - recordIntake('peer-a', PER_PEER_INTAKE_BYTES, NOW) + it('does not hand a peer a second allowance across the hour boundary', () => { + // A counter reset on the hour would let this peer spend everything at 59 + // minutes and everything again at 61, which is twice the stated limit + const late = NOW + INTAKE_WINDOW_MS - 60_000 + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, late)?.settle(PER_PEER_INTAKE_BYTES) - assert.equal(mayAcceptIntake('peer-a', NOW + INTAKE_WINDOW_MS), true) + assert.equal(reserveIntake('peer-a', 1, late + 120_000), undefined) }) - it('forgets a peer whose window closed, so the accounting cannot grow forever', () => { - recordIntake('peer-a', 1, NOW) - recordIntake('peer-b', 1, NOW + INTAKE_WINDOW_MS) + it('opens the budget again once the spend is really an hour old', () => { + const late = NOW + INTAKE_WINDOW_MS - 60_000 + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, late)?.settle(PER_PEER_INTAKE_BYTES) - // peer-a is gone rather than merely out of date, so a spent identity is not - // remembered for longer than it can be charged. - assert.equal(mayAcceptIntake('peer-a', NOW + INTAKE_WINDOW_MS), true) + assert.notEqual(reserveIntake('peer-a', COPY, late + INTAKE_WINDOW_MS), undefined) }) }) diff --git a/test/sweepBatch.test.ts b/test/sweepBatch.test.ts index 76ecc1d..752d791 100644 --- a/test/sweepBatch.test.ts +++ b/test/sweepBatch.test.ts @@ -43,6 +43,16 @@ describe('nextSweepBatch', () => { assert.notDeepEqual(firstRepair[0], secondRepair[0]) }) + it('reports the next batch without taking it when asked to peek', () => { + const all = records(SWEEP_BATCHES.demote + 5) + + const peeked = nextSweepBatch('demote', all, { advance: false }) + const taken = nextSweepBatch('demote', all) + + // A dry run must not move the position the real pass resumes from + assert.deepEqual(peeked, taken) + }) + it('starts over when the record it stopped at is gone', () => { const all = records(SWEEP_BATCHES.rescue + 3) nextSweepBatch('rescue', all) From 64833265fb619cda06aa7a6629522d2898f71cb9 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Tue, 25 Aug 2026 11:02:21 +0300 Subject: [PATCH 115/167] feat(after review): implement block metering and enhance lifecycle management --- docs/storage-lifecycle.md | 33 ++++-- src/api/file.ts | 59 +++++++---- src/index.ts | 15 ++- src/storage/backfill.ts | 46 +++++++-- src/storage/config.ts | 10 +- src/storage/gc.ts | 29 +++++- src/storage/intakeBudget.ts | 13 ++- src/storage/meter.ts | 57 +++++++++++ src/storage/registry.ts | 70 ++++++++++++- src/storage/service.ts | 41 ++++---- test/fileRegistry.test.ts | 35 +++++++ test/intakeBudget.test.ts | 16 ++- test/integration/storageLifecycle.test.ts | 117 +++++++++++++++++++++- test/meter.test.ts | 63 ++++++++++++ 14 files changed, 527 insertions(+), 77 deletions(-) create mode 100644 src/storage/meter.ts create mode 100644 test/meter.test.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 1e6ffac..cc57716 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -40,7 +40,11 @@ admitted only when `freeSpace - alreadyClaimed - requestSize >= storage.diskReserveBytes`. Copies arriving from peers are uploads as far as the disk is concerned, so they -take the same claim and the same aggregate limit. Their concurrency is a quarter +take the same claim and the same aggregate limit. Their size is measured in +blocks rather than in file content: the structural nodes of a DAG are fetched +and written like any other block, and a limit counting only what a reader would +see reads zero for a DAG whose content is empty and whose structure is +megabytes. Their concurrency is a quarter of `storage.maxConcurrentUploads`, because a copy cannot declare its size and so claims the aggregate limit for its whole transfer, and because refusing one costs nothing a person can see: the sender still holds the file, and repair @@ -178,6 +182,12 @@ volume: on a disk whose blockstore is already below the low watermark, aiming at the watermark would select nothing and reclaim not a byte while the disk stayed full. +A run applies its plan only to records that still match it. The plan is made +from a snapshot, and an upload can re-register and confirm one of those CIDs +before the release reaches it; the record is re-read and the unpin performed with +nothing else touching the CID, so a file that gained a pin in the meantime keeps +it. `releasedCids` reports what was released rather than what was planned. + A collection that Helia could not complete keeps every record it released, and reports `collected: false` with the failures in `errors`. Which file a surviving block belongs to cannot be known — blocks are addressed by multihash and shared @@ -277,7 +287,10 @@ itself is charged and never refunded, because asking is most of the cost. The hour is measured in six slices that expire one at a time rather than as a counter reset on the hour, which would let a peer spend its whole allowance just -before the boundary and again just after it. The node-wide budget is the one +before the boundary and again just after it. A slice is kept until its end is an +hour old, so a charge counts for at least a full hour and at most seventy +minutes — over-counting rather than under-counting, which is the safe direction +for a limit. The node-wide budget is the one that matters while peer identities are free to mint; both sit far above ordinary traffic, where a node that just accepted an upload asks two peers to cache it. Operator-facing transfer limits are separate work (#29). @@ -450,11 +463,17 @@ Startup walks the pinset once and records anything the registry does not know as is not fully local is skipped rather than recorded — calling it confirmed would claim more than the node can serve. -Records are created only while the CID is absent, and every read-modify-write on -the registry is serialised per CID. Uploads are being served while this runs, so -without that an upload registering a CID mid-walk would be overwritten as -confirmed: an operator who required confirmation would find the file durable -anyway, under its CID instead of its name. +Which pins count as legacy is decided before the API accepts anything, by +listing the pinset once. Anything pinned after that belongs to a request with a +lifecycle of its own. Listing is cheap; reconciling each pin is not, which is +why only the first part is ordered against the listener. + +Records are then created only while the CID is absent, and every +read-modify-write on the registry is serialised per CID. Both are needed: +without the snapshot the backfill could record a file between an upload pinning +it and registering it, and without the serialisation two writers could lose one +another's decision. Either would leave an operator who required confirmation +with a file that is durable and named by its CID. It does not block the API. The walk is as long as the pinset, and holding reads back for the whole migration would penalise exactly the nodes with the most to diff --git a/src/api/file.ts b/src/api/file.ts index cbb3704..f58df2a 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -12,6 +12,7 @@ import { releaseFile, replicateFile } from '../storage/service.js' +import type { FileRecord } from '../storage/registry.js' import { fileRegistry } from '../storage/state.js' import { parseCid } from '../utils/cid.js' import { sendDownloadStream } from '../utils/downloadResponse.js' @@ -66,6 +67,13 @@ router.post( const knownBefore = await fileRegistry.get(cid) + // What this request wrote, so its compensation can tell that write + // apart from an identical one made by a concurrent upload of the same + // file. Undoing the other request's record would take away a lifecycle + // it committed to. It is filled in below, after the compensation that + // reads it has been registered. + const write: { record?: FileRecord } = {} + // Compensation runs in reverse of the order it is pushed, so the // registry step goes first to run last. Undoing the pin before the // record means a failure at worst leaves a record for a file that is @@ -77,35 +85,48 @@ router.post( // confirmed, and dropping it would lose a lifecycle this request did // not create. undo.push(async () => { - if (knownBefore) { - await fileRegistry.save(knownBefore) - } else { - await fileRegistry.remove(cid) - } + await fileRegistry.transition(cid, async (current) => { + if (write.record === undefined || current?.revision !== write.record.revision) { + return 'keep' + } + + return knownBefore ?? 'remove' + }) }) // Only a pin this request created may be removed again; content that // was already durable keeps the pin it had. if (await pinFile(helia, file.cid)) { undo.push(async () => { - await unpinFile(helia, file.cid) + // Under the same lock as the registry, and only while nothing + // claims the CID. A second upload of the same file finds it pinned + // already, creates no pin of its own, and would lose its content to + // this compensation. + await fileRegistry.transition(cid, async (current) => { + if (current !== undefined) { + return 'keep' + } + + await unpinFile(helia, file.cid) + return 'keep' + }) }) } - records.push( - await fileRegistry.register( - { - cid, - name: file.originalname, - fileSize: file.size, - storedBytes: file.storedBytes - }, - { - confirmationRequired: config.storage.confirmationRequired, - temporaryTtlMs: config.storage.temporaryTtlMs - } - ) + write.record = await fileRegistry.register( + { + cid, + name: file.originalname, + fileSize: file.size, + storedBytes: file.storedBytes + }, + { + confirmationRequired: config.storage.confirmationRequired, + temporaryTtlMs: config.storage.temporaryTtlMs + } ) + + records.push(write.record) } // Every file is stored and pinned: the blocks written by this request must diff --git a/src/index.ts b/src/index.ts index 802a5a9..ea2c2f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { config, CONFIG_FILE_NAME } from './config.js' import { diskUsageCron } from './disk-usage.cron.js' import { peeringCron, peerWithKnownNodes } from './peering.cron.js' import { helia, ifs } from './helia.js' -import { backfillRegistryFromPins } from './storage/backfill.js' +import { backfillRegistryFromPins, snapshotPins } from './storage/backfill.js' import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' import { fileRegistry } from './storage/state.js' @@ -63,13 +63,18 @@ function startLifecycleJobs(): void { // it out of the storage report, out of dry runs, and out of replication repair. // Recording it is idempotent. // -// It is deliberately not awaited. The walk is as long as the pinset, and -// blocking here would keep the whole API — reads included — unavailable for the -// entire migration on exactly the nodes that have the most to serve. Reads, +// Which pins are legacy is decided here, before the API can accept an upload: +// anything pinned after this line belongs to a request with a lifecycle of its +// own. Listing them is cheap; reconciling them is not. +const legacyPins = await snapshotPins(helia) + +// The reconciliation is deliberately not awaited. It is as long as the pinset, +// and blocking here would keep the whole API — reads included — unavailable for +// the entire migration on exactly the nodes that have the most to serve. Reads, // uploads and incoming copies do not need the registry to be complete; the jobs // that do are started once it is. void backfillRegistryFromPins({ - node: helia, + cids: legacyPins, unixfs: ifs, registry: fileRegistry, log: (message) => logger.info(message) diff --git a/src/storage/backfill.ts b/src/storage/backfill.ts index 0fce9c3..34a50e1 100644 --- a/src/storage/backfill.ts +++ b/src/storage/backfill.ts @@ -30,12 +30,37 @@ export interface BackfillReport { } export interface BackfillOptions { - node: IpfsNode + /** Pins to reconcile, from {@link snapshotPins}. */ + cids: string[] unixfs: UnixFS registry: FileRegistry log?: (message: string) => void } +/** + * List the pins that exist right now. + * + * This has to happen before the node accepts its first upload, and it is why + * enumeration is separate from the work that follows. A pin created after this + * point belongs to an upload with a lifecycle of its own; if the backfill saw + * it, it would record it as legacy content — `confirmed`, with no expiry, named + * by its CID — in the window between the upload pinning the file and + * registering it. An operator who required confirmation would find the file + * durable without ever confirming it. + * + * Listing pins is cheap next to what the backfill then does with them, so the + * part that must be ordered is also the part that costs almost nothing. + */ +export async function snapshotPins(node: IpfsNode): Promise { + const cids: string[] = [] + + for await (const pin of node.pins.ls()) { + cids.push(pin.cid.toString()) + } + + return cids +} + /** * Give existing pins a place in the lifecycle registry. * @@ -46,19 +71,22 @@ export interface BackfillOptions { * from a dry run's retained list, and from replication repair. In other words * the new guarantees would quietly apply to new uploads only. * - * This walks the pinset once and records what is missing, as `confirmed`, which - * is what a pinned file is. It is idempotent: a pin already in the registry is - * left exactly as it is, so running it again after a restart changes nothing. + * This records what is missing from a pin snapshot, as `confirmed`, which is + * what a pinned file is. It is idempotent: a pin already in the registry is left + * exactly as it is, so running it again after a restart changes nothing. */ export async function backfillRegistryFromPins(options: BackfillOptions): Promise { const log = options.log ?? ((): void => {}) - const report: BackfillReport = { pins: 0, registered: 0, known: 0, incomplete: 0, errors: [] } + const report: BackfillReport = { + pins: options.cids.length, + registered: 0, + known: 0, + incomplete: 0, + errors: [] + } const now = Date.now() - for await (const pin of options.node.pins.ls()) { - report.pins += 1 - const cid = pin.cid.toString() - + for (const cid of options.cids) { try { if (await options.registry.get(cid)) { report.known += 1 diff --git a/src/storage/config.ts b/src/storage/config.ts index e686ca2..53e6bcb 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -233,11 +233,6 @@ export function resolveStorageConfig(raw: unknown, uploadLimitSizeBytes: number) return storage } -/** - * Resolve the `replication` section. - * - * @param raw Value of the `replication` key from a parsed config file - */ /** * Validate the placement tiers. * @@ -276,6 +271,11 @@ function resolvePlacement(raw: unknown, defaults: PlacementTier[]): PlacementTie return tiers } +/** + * Resolve the `replication` section. + * + * @param raw Value of the `replication` key from a parsed config file + */ export function resolveReplicationConfig(raw: unknown): ReplicationConfig { const input = section(raw, 'replication') const defaults = DEFAULT_REPLICATION_CONFIG diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 6518477..493fee8 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -296,14 +296,37 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { + if (current === undefined || current.state === 'confirmed') { + return 'keep' + } + + await unpinFile(options.node, CID.parse(record.cid)) + acted = true + + return { ...current, state: 'expired', pinned: false, heldLocally: false } + }) + + if (acted) { + releasedCleanly.push(record) + } else { + log(`Skipped ${record.cid}: its lifecycle changed after the plan was made`) + } } catch (err) { errors.push(`Release failed for ${record.cid}: ${(err as Error).message}`) } } + // What the run actually released, rather than what it set out to release. + report.releasedCids = releasedCleanly.map((item) => item.cid) + if (!collect) { // Nothing is deleted while there is room. The released blocks stay on disk, // unprotected, and keep answering reads until the space is needed. diff --git a/src/storage/intakeBudget.ts b/src/storage/intakeBudget.ts index 7fdf329..0dd1ce3 100644 --- a/src/storage/intakeBudget.ts +++ b/src/storage/intakeBudget.ts @@ -62,10 +62,19 @@ interface Bucket { class Spend { private buckets: Bucket[] = [] - /** Buckets the window still covers; the rest are dropped as they age out. */ + /** + * Buckets the window still covers; the rest are dropped as they age out. + * + * A bucket is kept until its *end* is an hour old, not its start. Expiring by + * the start drops a charge made late in a bucket after roughly fifty minutes, + * which lets a peer spend about a sixth more than the stated limit in a real + * trailing hour. Keeping it counts every charge for a full hour and for at + * most seventy minutes — over-counting rather than under-counting, which is + * the safe direction for a limit. + */ private live(now: number): Bucket[] { const oldest = now - INTAKE_WINDOW_MS - this.buckets = this.buckets.filter((bucket) => bucket.startedAt > oldest) + this.buckets = this.buckets.filter((bucket) => bucket.startedAt + BUCKET_MS > oldest) return this.buckets } diff --git a/src/storage/meter.ts b/src/storage/meter.ts new file mode 100644 index 0000000..543be12 --- /dev/null +++ b/src/storage/meter.ts @@ -0,0 +1,57 @@ +import type { Blockstore } from 'interface-blockstore' + +/** How much of a transfer has arrived, readable while it is still running. */ +export interface TransferProgress { + bytes: number +} + +/** The part of a blockstore UnixFS needs to read content. */ +export type ReadableBlocks = Pick + +/** + * A blockstore view that charges every block read through it and stops at a + * limit. + * + * Counting what a UnixFS stream yields measures the file's logical content, not + * what crossed the network: DAG-PB nodes, links and other structural blocks are + * fetched and written to the blockstore without appearing in the stream at all. + * A DAG can be built whose logical content is empty while its structure is + * megabytes, and a cap counting only the stream would read zero for it. + * + * Metering therefore sits under UnixFS rather than over it. The limit is + * enforced per block as the walk proceeds, so an oversized DAG is abandoned + * partway instead of after all of it has arrived. + * + * Blocks already on disk are counted too. That charges a little more than the + * transfer really cost, which is the safe direction for a limit. + * + * @param progress Updated as blocks are consumed; survives a failed transfer, + * because a peer that sends almost everything and then aborts has spent the + * bandwidth either way + * @param limitBytes Most this view may hand out before it refuses to continue + */ +export function meteredBlocks( + source: ReadableBlocks, + progress: TransferProgress, + limitBytes: number +): ReadableBlocks { + return { + get: (cid, options) => { + const block = source.get(cid, options) + + return (async function* () { + for await (const chunk of block) { + progress.bytes += chunk.byteLength + + if (progress.bytes > limitBytes) { + throw new Error('Copy is larger than this node accepts') + } + + yield chunk + } + })() + }, + put: (cid, block, options) => source.put(cid, block, options), + has: (cid, options) => source.has(cid, options) + } +} diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 8e4e33f..1ee5286 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -10,6 +10,15 @@ export const REGISTRY_PREFIX = '/adm/files' const encoder = new TextEncoder() const decoder = new TextDecoder() +/** + * Stamps every stored record, so two writes are never mistaken for one. + * + * A counter carried on the record itself would reset whenever a writer builds a + * fresh record rather than editing the stored one, which is exactly what + * registering an already-known CID does. + */ +let writes = 0 + /** Lifecycle state of a file known to this node. */ export type FileState = 'temporary' | 'confirmed' | 'expired' @@ -38,8 +47,25 @@ export interface FileRecord { heldLocally: boolean /** Names of peer nodes that acknowledged holding a copy. */ replicas: string[] + /** + * Opaque stamp of the write that stored this record. + * + * It exists so a caller can tell its own write apart from an identical one + * made by somebody else. Two concurrent uploads of the same file produce + * records that match field for field, so without this the compensation of a + * failed upload cannot see that another request has since adopted the CID — + * and would undo work that succeeded. + * + * It is unique within a process run and means nothing across restarts, which + * is all it is compared over: a caller only ever checks a record against a + * stamp it was handed by its own write. + */ + revision?: number } +/** What {@link FileRegistry.transition} should do with a record. */ +export type Transition = 'keep' | 'remove' + export interface NewFileRecord { cid: string name: string @@ -132,6 +158,40 @@ export class FileRegistry { }) } + /** + * Decide a record's next state from what it is now, with nothing else + * touching the CID meanwhile. + * + * A decision taken from a record read earlier is stale by the time it is + * applied: the collector plans a release and an upload may re-register the + * same CID before the unpin runs, and a failed upload may try to undo a + * lifecycle another request has since adopted. `work` is handed the record as + * it is at that moment, may perform whatever must not be interleaved — an + * unpin belongs here — and returns the record to store, `'remove'`, or + * `'keep'` to leave it alone. + * + * @returns The stored record, or `undefined` when nothing was stored + */ + async transition( + cid: string, + work: (current: FileRecord | undefined) => Promise + ): Promise { + return this.exclusive(cid, async () => { + const outcome = await work(await this.get(cid)) + + if (outcome === 'keep') { + return undefined + } + + if (outcome === 'remove') { + await this.removeRecord(cid) + return undefined + } + + return this.save(outcome) + }) + } + async get(cid: string): Promise { try { return JSON.parse(decoder.decode(await this.datastore.get(this.key(cid)))) as FileRecord @@ -144,11 +204,17 @@ export class FileRegistry { } async save(record: FileRecord): Promise { - await this.datastore.put(this.key(record.cid), encoder.encode(JSON.stringify(record))) - return record + writes += 1 + const stored: FileRecord = { ...record, revision: writes } + await this.datastore.put(this.key(stored.cid), encoder.encode(JSON.stringify(stored))) + return stored } async remove(cid: string): Promise { + return this.exclusive(cid, () => this.removeRecord(cid)) + } + + private async removeRecord(cid: string): Promise { try { await this.datastore.delete(this.key(cid)) } catch (err) { diff --git a/src/storage/service.ts b/src/storage/service.ts index f80e487..880ec1c 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -1,3 +1,4 @@ +import { unixfs } from '@helia/unixfs' import { CID } from 'multiformats/cid' import { config } from '../config.js' import { helia, ifs } from '../helia.js' @@ -26,6 +27,7 @@ import { } from './replicationProtocol.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { PER_PEER_INTAKE_BYTES, reserveIntake } from './intakeBudget.js' +import { meteredBlocks, type TransferProgress } from './meter.js' import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter } from './state.js' import { nextSweepBatch } from './sweep.js' @@ -287,11 +289,6 @@ export async function hasRoomForAnotherCopy(): Promise { } } -/** How much of a transfer has arrived, readable while it is still running. */ -export interface TransferProgress { - bytes: number -} - /** * Pull a DAG into the blockstore under the limits a peer transfer carries: how * many may run at once, how much space they may take together, and how large @@ -309,11 +306,15 @@ export interface TransferProgress { * @param progress Updated as bytes arrive, so a caller can still see what a * failed transfer cost. A peer that sends almost everything and then aborts * has spent the bandwidth either way. + * @param limitBytes Most this transfer may bring in. Defaults to the aggregate + * request limit; a caller working against a budget passes what it reserved, + * so a transfer cannot outgrow the allowance that admitted it. * @returns Bytes pulled */ async function pullUnderIntakeLimits( cid: string, - progress: TransferProgress = { bytes: 0 } + progress: TransferProgress = { bytes: 0 }, + limitBytes: number = config.storage.maxRequestSizeBytes ): Promise { if (!incomingCopyLimiter.tryAcquire()) { throw new Error('Too many copies are already being received') @@ -336,12 +337,15 @@ async function pullUnderIntakeLimits( const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) - for await (const chunk of ifs.cat(CID.parse(cid), { signal })) { - progress.bytes += chunk.byteLength + // Draining the stream is what walks the DAG; the meter under UnixFS is what + // counts and bounds the blocks that walk pulls in, including the structural + // ones the stream never yields. + const metered = unixfs({ + blockstore: meteredBlocks(helia.blockstore, progress, limitBytes) + }) - if (progress.bytes > config.storage.maxRequestSizeBytes) { - throw new Error('Copy is larger than this node accepts') - } + for await (const chunk of metered.cat(CID.parse(cid), { signal })) { + void chunk } return progress.bytes @@ -370,12 +374,9 @@ export async function cacheFileLocally(cid: string, peerId: string): Promise { assert.equal(await registry.get(CID_A), undefined) }) + it('undoes a write only while it is still the last one', async () => { + // Two concurrent uploads of the same file write records that match field + // for field. The first must not roll back the lifecycle the second owns. + const registry = createRegistry() + const first = await registry.register(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) + const second = await registry.register(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) + + await registry.transition(CID_A, async (current) => + current?.revision === first.revision ? 'remove' : 'keep' + ) + + assert.notEqual(first.revision, second.revision) + assert.notEqual(await registry.get(CID_A), undefined) + }) + + it('removes a record when the undo is still the last write', async () => { + const registry = createRegistry() + const written = await registry.register(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) + + await registry.transition(CID_A, async (current) => + current?.revision === written.revision ? 'remove' : 'keep' + ) + + assert.equal(await registry.get(CID_A), undefined) + }) + it('creates a record only while the registry does not know the CID', async () => { const registry = createRegistry() const legacy: FileRecord = { diff --git a/test/intakeBudget.test.ts b/test/intakeBudget.test.ts index 378d204..328e2a6 100644 --- a/test/intakeBudget.test.ts +++ b/test/intakeBudget.test.ts @@ -1,6 +1,7 @@ import assert from 'node:assert/strict' import { beforeEach, describe, it } from 'node:test' import { + INTAKE_BUCKETS, INTAKE_WINDOW_MS, PER_PEER_INTAKE_BYTES, PER_PEER_INTAKE_REQUESTS, @@ -94,10 +95,21 @@ describe('cache intake budget', () => { assert.equal(reserveIntake('peer-a', 1, late + 120_000), undefined) }) - it('opens the budget again once the spend is really an hour old', () => { + it('still counts a spend a full hour after it happened', () => { + // Expiring a bucket by its start would drop a charge made late in it after + // roughly fifty minutes, and the real trailing-hour limit would be a sixth + // higher than the stated one const late = NOW + INTAKE_WINDOW_MS - 60_000 reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, late)?.settle(PER_PEER_INTAKE_BYTES) - assert.notEqual(reserveIntake('peer-a', COPY, late + INTAKE_WINDOW_MS), undefined) + assert.equal(reserveIntake('peer-a', 1, late + INTAKE_WINDOW_MS), undefined) + }) + + it('opens the budget again once the whole bucket is behind the window', () => { + const bucketMs = INTAKE_WINDOW_MS / INTAKE_BUCKETS + const late = NOW + INTAKE_WINDOW_MS - 60_000 + reserveIntake('peer-a', PER_PEER_INTAKE_BYTES, late)?.settle(PER_PEER_INTAKE_BYTES) + + assert.notEqual(reserveIntake('peer-a', COPY, late + INTAKE_WINDOW_MS + bucketMs), undefined) }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 38c7f88..0cd45f3 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -8,8 +8,9 @@ import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' -import { backfillRegistryFromPins } from '../../src/storage/backfill.js' +import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' import { runGarbageCollection } from '../../src/storage/gc.js' +import { meteredBlocks } from '../../src/storage/meter.js' import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' import { UploadSession } from '../../src/storage/uploadSession.js' @@ -417,6 +418,35 @@ describe('garbage collection against fixture data', () => { assert.equal((await registry.get(cids.abandoned))?.state, 'expired') }) + it('does not apply a plan the registry has moved past', async () => { + const { registry, cids } = await createFixture() + + // The plan is made from a snapshot; an upload can re-register and confirm + // the same CID before the run reaches the unpin + const stale = await registry.all() + await registry.confirm(cids.abandoned) + + const planned = new Proxy(registry, { + get: (target, property) => + property === 'all' + ? async (): Promise => stale + : Reflect.get(target, property, target) + }) as FileRegistry + + const report = await runGarbageCollection({ + node, + registry: planned, + watermarks: TIGHT, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.deepEqual(report.releasedCids, []) + assert.equal((await registry.get(cids.abandoned))?.state, 'confirmed') + assert.equal(await isProtected(node, CID.parse(cids.abandoned)), true) + assert.equal((await storedDigests()).has(digestOf(cids.abandoned)), true) + }) + it('restores a missing pin on confirmed content before deleting anything', async () => { const { registry, cids } = await createFixture() await unpinFile(node, CID.parse(cids.confirmed)) @@ -435,13 +465,64 @@ describe('garbage collection against fixture data', () => { }) }) +describe('block metering', () => { + it('charges the structural blocks a UnixFS stream never yields', async () => { + // Counting the stream measures the file; counting blocks measures what + // crossed the network. A DAG-PB root and its links are real bytes that a + // reader of the content alone never sees. Large enough to be chunked, so + // the DAG has a root above its leaves at all. + const payload = deterministicBytes(4 * 1024 * 1024, 'metered') + const cid = await ifs.addBytes(payload) + + const progress = { bytes: 0 } + const metered = unixfs({ + blockstore: meteredBlocks(node.blockstore, progress, 16 * 1024 * 1024) + }) + + let streamed = 0 + for await (const chunk of metered.cat(cid)) { + streamed += chunk.byteLength + } + + assert.equal(streamed, payload.byteLength) + assert.ok( + progress.bytes > streamed, + `metered ${progress.bytes} bytes, which must exceed the ${streamed} bytes of content` + ) + }) + + it('stops a walk that outgrows the limit instead of finishing it', async () => { + const payload = deterministicBytes(4 * 1024 * 1024, 'metered-limit') + const cid = await ifs.addBytes(payload) + const progress = { bytes: 0 } + const metered = unixfs({ + blockstore: meteredBlocks(node.blockstore, progress, 2 * 1024 * 1024) + }) + + await assert.rejects(async () => { + for await (const chunk of metered.cat(cid)) { + void chunk + } + }, /larger than this node accepts/) + + assert.ok( + progress.bytes < payload.byteLength, + `metered ${progress.bytes} bytes, so the walk ran further than the limit allowed` + ) + }) +}) + describe('registry backfill', () => { it('records a pin that predates the registry, and does so only once', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'legacy-pin')) await pinFile(node, cid) - const first = await backfillRegistryFromPins({ node, unixfs: ifs, registry }) + const first = await backfillRegistryFromPins({ + cids: await snapshotPins(node), + unixfs: ifs, + registry + }) const recorded = await registry.get(cid.toString()) assert.ok(first.registered > 0) @@ -449,11 +530,33 @@ describe('registry backfill', () => { assert.equal(recorded?.heldLocally, true) assert.ok((recorded?.storedBytes ?? 0) > 0) - const second = await backfillRegistryFromPins({ node, unixfs: ifs, registry }) + const second = await backfillRegistryFromPins({ + cids: await snapshotPins(node), + unixfs: ifs, + registry + }) assert.equal(second.registered, 0) assert.ok(second.known > 0) }) + it('ignores a pin created after the snapshot was taken', async () => { + // A pin made once the API is serving belongs to a request with a lifecycle + // of its own, and must not be recorded as legacy content + const registry = createRegistry() + const before = await ifs.addBytes(deterministicBytes(2048, 'snapshot-before')) + await pinFile(node, before) + + const cids = await snapshotPins(node) + + const after = await ifs.addBytes(deterministicBytes(2048, 'snapshot-after')) + await pinFile(node, after) + + await backfillRegistryFromPins({ cids, unixfs: ifs, registry }) + + assert.notEqual(await registry.get(before.toString()), undefined) + assert.equal(await registry.get(after.toString()), undefined) + }) + it('reports a registry failure as an error rather than as incomplete content', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'unwritable')) @@ -467,7 +570,11 @@ describe('registry backfill', () => { save: () => Promise.reject(new Error('datastore is unavailable')) } as unknown as FileRegistry - const report = await backfillRegistryFromPins({ node, unixfs: ifs, registry: failing }) + const report = await backfillRegistryFromPins({ + cids: await snapshotPins(node), + unixfs: ifs, + registry: failing + }) assert.equal(report.incomplete, 0) assert.ok(report.errors.some((message) => message.includes(cid.toString()))) @@ -480,7 +587,7 @@ describe('registry backfill', () => { await pinFile(node, cid) await registry.save(record({ cid: cid.toString(), state: 'confirmed', name: 'mine.bin' })) - await backfillRegistryFromPins({ node, unixfs: ifs, registry }) + await backfillRegistryFromPins({ cids: await snapshotPins(node), unixfs: ifs, registry }) assert.equal((await registry.get(cid.toString()))?.name, 'mine.bin') }) diff --git a/test/meter.test.ts b/test/meter.test.ts new file mode 100644 index 0000000..71ab49a --- /dev/null +++ b/test/meter.test.ts @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { CID } from 'multiformats/cid' +import { meteredBlocks, type ReadableBlocks } from '../src/storage/meter.js' + +const CID_A = CID.parse('bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku') + +/** A blockstore that hands out fixed-size blocks in two chunks. */ +function blocks(size: number): ReadableBlocks { + return { + get: () => + (async function* () { + yield new Uint8Array(Math.ceil(size / 2)) + yield new Uint8Array(Math.floor(size / 2)) + })(), + put: async () => CID_A, + has: async () => true + } as unknown as ReadableBlocks +} + +async function drain(source: ReadableBlocks): Promise { + for await (const chunk of source.get(CID_A)) { + void chunk + } +} + +describe('meteredBlocks', () => { + it('charges every byte of a block, not what a stream would yield', async () => { + const progress = { bytes: 0 } + + await drain(meteredBlocks(blocks(1000), progress, 10_000)) + + assert.equal(progress.bytes, 1000) + }) + + it('keeps counting across blocks', async () => { + const progress = { bytes: 0 } + const metered = meteredBlocks(blocks(400), progress, 10_000) + + await drain(metered) + await drain(metered) + + assert.equal(progress.bytes, 800) + }) + + it('abandons the walk once the limit is passed', async () => { + const progress = { bytes: 0 } + const metered = meteredBlocks(blocks(600), progress, 1000) + + await drain(metered) + await assert.rejects(() => drain(metered), /larger than this node accepts/) + }) + + it('keeps what a failed transfer already cost', async () => { + const progress = { bytes: 0 } + const metered = meteredBlocks(blocks(2000), progress, 1500) + + await assert.rejects(() => drain(metered)) + + // A peer that sends almost everything and then aborts has spent it + assert.ok(progress.bytes > 0, 'the bytes that arrived must still be charged') + }) +}) From 7c9925a4d9adb68d81a058caddb559f9ace1fa22 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Tue, 25 Aug 2026 11:51:59 +0300 Subject: [PATCH 116/167] fix(review): implement rollback functionality for failed uploads and enhance lifecycle management --- docs/storage-lifecycle.md | 20 +++- src/api/file.ts | 73 +++++------- src/storage/gc.ts | 21 ++-- src/storage/meter.ts | 45 ++++++++ src/storage/registry.ts | 26 ++++- src/storage/rollback.ts | 56 +++++++++ src/storage/service.ts | 52 +++++++-- test/fileRegistry.test.ts | 17 +++ test/integration/storageLifecycle.test.ts | 59 ++++++++-- test/meter.test.ts | 15 ++- test/rollback.test.ts | 132 ++++++++++++++++++++++ 11 files changed, 444 insertions(+), 72 deletions(-) create mode 100644 src/storage/rollback.ts create mode 100644 test/rollback.test.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index cc57716..d1a416b 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -44,7 +44,21 @@ take the same claim and the same aggregate limit. Their size is measured in blocks rather than in file content: the structural nodes of a DAG are fetched and written like any other block, and a limit counting only what a reader would see reads zero for a DAG whose content is empty and whose structure is -megabytes. Their concurrency is a quarter +megabytes. + +Counting is not enough on its own. Left to itself the UnixFS exporter requests +every block of a DAG before any of them completes — a 64 MiB file issues 65 +reads up front — so by the time the first byte can be counted the whole DAG has +arrived, whatever the limit said. Intake therefore reads a bounded number of +blocks at a time, which is what makes the limit enforceable: measured against a +real node, an unbounded read pulls a 64 MiB file in full under a 4 MiB limit, +and a bounded one stops at 4 MiB. + +What is left is the reads already in flight when the limit is crossed. They +still complete, so a transfer can exceed its limit by that many blocks; the disk +claim and the intake reservation both include exactly that much headroom, and +the whole block is charged rather than the part that was read. The overshoot is +covered, not merely small. Their concurrency is a quarter of `storage.maxConcurrentUploads`, because a copy cannot declare its size and so claims the aggregate limit for its whole transfer, and because refusing one costs nothing a person can see: the sender still holds the file, and repair @@ -188,6 +202,10 @@ before the release reaches it; the record is re-read and the unpin performed wit nothing else touching the CID, so a file that gained a pin in the meantime keeps it. `releasedCids` reports what was released rather than what was planned. +The same applies at the end of a run. Deleting blocks takes time, and a +re-upload during it can pin and register one of the released CIDs again; a +record is deregistered only while it is still the one this run released. + A collection that Helia could not complete keeps every record it released, and reports `collected: false` with the failures in `errors`. Which file a surviving block belongs to cannot be known — blocks are addressed by multihash and shared diff --git a/src/api/file.ts b/src/api/file.ts index f58df2a..3463893 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -13,6 +13,7 @@ import { replicateFile } from '../storage/service.js' import type { FileRecord } from '../storage/registry.js' +import { rollbackUpload } from '../storage/rollback.js' import { fileRegistry } from '../storage/state.js' import { parseCid } from '../utils/cid.js' import { sendDownloadStream } from '../utils/downloadResponse.js' @@ -65,55 +66,36 @@ router.post( const cid = file.cid.toString() logger.info(`Successfully added file ${cid}`) - const knownBefore = await fileRegistry.get(cid) - - // What this request wrote, so its compensation can tell that write - // apart from an identical one made by a concurrent upload of the same - // file. Undoing the other request's record would take away a lifecycle - // it committed to. It is filled in below, after the compensation that - // reads it has been registered. - const write: { record?: FileRecord } = {} - - // Compensation runs in reverse of the order it is pushed, so the - // registry step goes first to run last. Undoing the pin before the - // record means a failure at worst leaves a record for a file that is - // still pinned — visible, and reclaimable by the collector. The other - // order leaves a pinned file nothing accounts for. - // - // A CID this node already knew is restored rather than removed: - // registering it again may move an expired or released record to - // confirmed, and dropping it would lose a lifecycle this request did - // not create. - undo.push(async () => { - await fileRegistry.transition(cid, async (current) => { - if (write.record === undefined || current?.revision !== write.record.revision) { - return 'keep' - } - - return knownBefore ?? 'remove' - }) - }) - // Only a pin this request created may be removed again; content that // was already durable keeps the pin it had. - if (await pinFile(helia, file.cid)) { - undo.push(async () => { - // Under the same lock as the registry, and only while nothing - // claims the CID. A second upload of the same file finds it pinned - // already, creates no pin of its own, and would lose its content to - // this compensation. - await fileRegistry.transition(cid, async (current) => { - if (current !== undefined) { - return 'keep' - } + const createdPin = await pinFile(helia, file.cid) + // What this request wrote, and what it replaced. Both are filled in + // below, after the compensation that reads them is registered. + // + // The baseline cannot be read before the write: another upload of the + // same file can adopt the CID in between, and restoring what this + // request saw earlier would erase the lifecycle that upload owns. The + // registration reports what it actually replaced, from inside the same + // serialised section. + const write: { record?: FileRecord; previous?: FileRecord } = {} + + // One compensation rather than two: the pin and the record have to be + // undone under the same lock and in that order. + undo.push(() => + rollbackUpload({ + registry: fileRegistry, + cid, + written: write.record, + previous: write.previous, + createdPin, + unpin: async () => { await unpinFile(helia, file.cid) - return 'keep' - }) + } }) - } + ) - write.record = await fileRegistry.register( + const registration = await fileRegistry.registerReplacing( { cid, name: file.originalname, @@ -126,7 +108,10 @@ router.post( } ) - records.push(write.record) + write.record = registration.record + write.previous = registration.previous + + records.push(registration.record) } // Every file is stored and pinned: the blocks written by this request must diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 493fee8..9db4fd7 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -292,30 +292,30 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { + const releasedRecord = await options.registry.transition(record.cid, async (current) => { if (current === undefined || current.state === 'confirmed') { return 'keep' } await unpinFile(options.node, CID.parse(record.cid)) - acted = true return { ...current, state: 'expired', pinned: false, heldLocally: false } }) - if (acted) { - releasedCleanly.push(record) + if (releasedRecord) { + releasedCleanly.push(releasedRecord) } else { log(`Skipped ${record.cid}: its lifecycle changed after the plan was made`) } @@ -369,9 +369,16 @@ export async function runGarbageCollection(options: GcRunOptions): Promise + current?.revision === record.revision ? 'remove' : 'keep' + ) } catch (err) { errors.push(`Registry cleanup failed for ${record.cid}: ${(err as Error).message}`) } diff --git a/src/storage/meter.ts b/src/storage/meter.ts index 543be12..b0c1c6b 100644 --- a/src/storage/meter.ts +++ b/src/storage/meter.ts @@ -8,6 +8,38 @@ export interface TransferProgress { /** The part of a blockstore UnixFS needs to read content. */ export type ReadableBlocks = Pick +/** + * Largest block a transfer is assumed to bring in at once. + * + * A block is fetched and written whole before any of its bytes can be counted, + * so a limit can only be noticed once it has already been passed. + */ +export const MAX_BLOCK_BYTES = 2 * 1024 ** 2 + +/** + * Blocks an intake reads at a time. + * + * This is what makes the limit enforceable at all. Left to itself, the UnixFS + * exporter requests every block of a DAG before any of them completes — a + * 64 MiB file issues 65 reads up front — so by the time the first byte can be + * counted the whole DAG has already arrived, whatever the limit said. Measured + * against a real node: with the reads unbounded a 64 MiB file lands in full + * under a 4 MiB limit; at four at a time it stops at 4 MiB. + * + * It is not 1 because this is also the path an upload's copies take, and + * serialising every block would put a round trip between each of them. + */ +export const INTAKE_READ_CONCURRENCY = 4 + +/** + * Most a transfer can exceed its limit by. + * + * The reads already in flight when the limit is crossed still complete, so the + * overshoot is bounded by them. Callers reserve this much headroom on top of + * what they allow, so the excess is covered rather than merely small. + */ +export const INTAKE_OVERSHOOT_BYTES = INTAKE_READ_CONCURRENCY * MAX_BLOCK_BYTES + /** * A blockstore view that charges every block read through it and stops at a * limit. @@ -25,6 +57,11 @@ export type ReadableBlocks = Pick * Blocks already on disk are counted too. That charges a little more than the * transfer really cost, which is the safe direction for a limit. * + * The limit is noticed late, because a block is stored before it can be counted + * and several are read at a time. What arrives is bounded by `limitBytes` plus + * {@link INTAKE_OVERSHOOT_BYTES}, which callers reserve as headroom — and only + * while the reader honours {@link INTAKE_READ_CONCURRENCY}. + * * @param progress Updated as blocks are consumed; survives a failed transfer, * because a peer that sends almost everything and then aborts has spent the * bandwidth either way @@ -44,6 +81,14 @@ export function meteredBlocks( progress.bytes += chunk.byteLength if (progress.bytes > limitBytes) { + // The block was fetched and stored whole before any of it could be + // counted, so the rest of it is accounted for before giving up. + // Charging only the part that was read would leave the budget + // below what the peer actually spent. + for await (const rest of block) { + progress.bytes += rest.byteLength + } + throw new Error('Copy is larger than this node accepts') } diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 1ee5286..df04ad2 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -279,31 +279,49 @@ export class FileRegistry { file: NewFileRecord, options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } ): Promise { + return (await this.registerReplacing(file, options)).record + } + + /** + * Register an upload and report the record it replaced. + * + * A record read before the write is already a guess by the time the write + * happens: another upload of the same file can adopt the CID in between. A + * rollback restoring what the caller saw earlier would erase the lifecycle + * that upload owns, so the replaced record is captured inside the same + * serialised section as the write that replaced it. + */ + async registerReplacing( + file: NewFileRecord, + options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } + ): Promise<{ record: FileRecord; previous: FileRecord | undefined }> { return this.exclusive(file.cid, () => this.registerExclusively(file, options)) } private async registerExclusively( file: NewFileRecord, options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } - ): Promise { + ): Promise<{ record: FileRecord; previous: FileRecord | undefined }> { const now = options.now ?? Date.now() const existing = await this.get(file.cid) if (existing?.state === 'confirmed') { // Re-uploading already durable content must not weaken its state, and it // puts the blocks back on this node whether or not they were released. - return this.save({ + const record = await this.save({ ...existing, storedBytes: Math.max(existing.storedBytes, file.storedBytes), fileSize: file.fileSize > 0 ? file.fileSize : existing.fileSize, pinned: true, heldLocally: true }) + + return { record, previous: existing } } const confirmed = !options.confirmationRequired - return this.save({ + const record = await this.save({ cid: file.cid, name: file.name, state: confirmed ? 'confirmed' : 'temporary', @@ -316,6 +334,8 @@ export class FileRegistry { heldLocally: true, replicas: existing?.replicas ?? [] }) + + return { record, previous: existing } } /** Move a temporary file to the durable state. Confirming twice is a no-op. */ diff --git a/src/storage/rollback.ts b/src/storage/rollback.ts new file mode 100644 index 0000000..135f5e5 --- /dev/null +++ b/src/storage/rollback.ts @@ -0,0 +1,56 @@ +import type { FileRecord, FileRegistry } from './registry.js' + +export interface UploadRollback { + registry: FileRegistry + cid: string + /** The record this request stored, when it got that far. */ + written?: FileRecord + /** What that write replaced, as reported by the write itself. */ + previous?: FileRecord + /** Whether the pin on this CID was created by this request. */ + createdPin: boolean + /** Removes that pin. */ + unpin: () => Promise +} + +/** + * Undo what one file of a failed upload left behind. + * + * The pin and the registry record have to be undone together, under the same + * per-CID lock, and in that order. Undoing them as two steps cannot work in + * either order: + * + * - unpin first, and the check that protects a concurrent upload's content + * ("is anyone claiming this CID?") finds this request's own record and keeps + * the pin, which the next step then removes — leaving content pinned with + * nothing accounting for it, permanently, because session cleanup skips + * pinned blocks; + * - remove the record first, and a failure to unpin afterwards leaves the same + * orphan. + * + * Nothing is undone once another request has written to the CID: it owns the + * lifecycle and the content from that point, whether or not it created the pin + * itself. A second upload of the same file finds it pinned already and creates + * no pin of its own, so the pin it depends on is this one. + */ +export async function rollbackUpload(options: UploadRollback): Promise { + await options.registry.transition(options.cid, async (current) => { + if (options.written === undefined || current?.revision !== options.written.revision) { + return 'keep' + } + + // A CID this node already knew keeps its earlier lifecycle: registering it + // again may move an expired or released record to confirmed, and dropping + // it would lose a lifecycle this request did not create. Its pin stays with + // it. + if (options.previous) { + return options.previous + } + + if (options.createdPin) { + await options.unpin() + } + + return 'remove' + }) +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 880ec1c..c10acb9 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -27,7 +27,12 @@ import { } from './replicationProtocol.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { PER_PEER_INTAKE_BYTES, reserveIntake } from './intakeBudget.js' -import { meteredBlocks, type TransferProgress } from './meter.js' +import { + INTAKE_OVERSHOOT_BYTES, + INTAKE_READ_CONCURRENCY, + meteredBlocks, + type TransferProgress +} from './meter.js' import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter } from './state.js' import { nextSweepBatch } from './sweep.js' @@ -326,7 +331,9 @@ async function pullUnderIntakeLimits( // The size is unknown before the transfer, so the aggregate request limit // is claimed pessimistically and given back at the end. claim = claimSpace({ - bytes: config.storage.maxRequestSizeBytes, + // Plus what the limit can be overshot by, so the disk reserve covers what + // can actually arrive rather than what was allowed. + bytes: limitBytes + INTAKE_OVERSHOOT_BYTES, availableBytes: Number(await availableStorageSize(blockstorePath)), reserveBytes: config.storage.diskReserveBytes }) @@ -344,7 +351,14 @@ async function pullUnderIntakeLimits( blockstore: meteredBlocks(helia.blockstore, progress, limitBytes) }) - for await (const chunk of metered.cat(CID.parse(cid), { signal })) { + // `blockReadConcurrency` is what the exporter honours; `CatOptions` + // re-exports a narrower type that leaves it out, so the options are built + // as a value rather than inline. Without it the exporter requests the whole + // DAG before the meter can count a single byte, and the limit bounds + // nothing at all. + const readOptions = { signal, blockReadConcurrency: INTAKE_READ_CONCURRENCY } + + for await (const chunk of metered.cat(CID.parse(cid), readOptions)) { void chunk } @@ -388,8 +402,13 @@ export async function cacheFileLocally(cid: string, peerId: string): Promise { + if (current === undefined) { + return 'keep' + } + + await pinFile(helia, cid) + + return { ...current, pinned: true, heldLocally: true } + }) + + if (kept === undefined) { + continue + } + rescued.push(record.cid) logger.warn(`No node was holding ${record.cid}; kept the local copy instead`) } catch { diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 6f93e8a..3fad42d 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -135,6 +135,23 @@ describe('FileRegistry', () => { assert.equal(await registry.get(CID_A), undefined) }) + it('reports the record a registration replaced', async () => { + // Read before the write, the baseline is a guess: another upload can adopt + // the CID in between, and a rollback restoring the guess would erase it + const registry = createRegistry() + const first = await registry.registerReplacing(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) + const second = await registry.registerReplacing(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) + + assert.equal(first.previous, undefined) + assert.equal(second.previous?.revision, first.record.revision) + }) + it('undoes a write only while it is still the last one', async () => { // Two concurrent uploads of the same file write records that match field // for field. The first must not roll back the lifecycle the second owns. diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 0cd45f3..a533046 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -10,7 +10,11 @@ import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' import { runGarbageCollection } from '../../src/storage/gc.js' -import { meteredBlocks } from '../../src/storage/meter.js' +import { + INTAKE_OVERSHOOT_BYTES, + INTAKE_READ_CONCURRENCY, + meteredBlocks +} from '../../src/storage/meter.js' import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' import { UploadSession } from '../../src/storage/uploadSession.js' @@ -447,6 +451,38 @@ describe('garbage collection against fixture data', () => { assert.equal((await storedDigests()).has(digestOf(cids.abandoned)), true) }) + it('does not remove a record re-created while blocks were being deleted', async () => { + const { registry, cids } = await createFixture() + + // Deleting blocks takes time, and a re-upload can pin and register the same + // CID while it runs + const racing = new Proxy(node, { + get: (target, property) => + property === 'gc' + ? async (): Promise => { + await pinFile(node, CID.parse(cids.abandoned)) + await registry.register( + { cid: cids.abandoned, name: 'again.bin', fileSize: 1024, storedBytes: 1024 }, + { confirmationRequired: false, temporaryTtlMs: 1000 } + ) + } + : Reflect.get(target, property, target) + }) as IpfsNode + + await runGarbageCollection({ + node: racing, + registry, + watermarks: TIGHT, + blockstoreBytes: 4096, + now: 1000 + }) + + const record = await registry.get(cids.abandoned) + assert.equal(record?.state, 'confirmed') + assert.equal(record?.name, 'again.bin') + assert.equal(await isProtected(node, CID.parse(cids.abandoned)), true) + }) + it('restores a missing pin on confirmed content before deleting anything', async () => { const { registry, cids } = await createFixture() await unpinFile(node, CID.parse(cids.confirmed)) @@ -492,22 +528,29 @@ describe('block metering', () => { }) it('stops a walk that outgrows the limit instead of finishing it', async () => { - const payload = deterministicBytes(4 * 1024 * 1024, 'metered-limit') + const limit = 2 * 1024 * 1024 + const payload = deterministicBytes(16 * 1024 * 1024, 'metered-limit') const cid = await ifs.addBytes(payload) const progress = { bytes: 0 } - const metered = unixfs({ - blockstore: meteredBlocks(node.blockstore, progress, 2 * 1024 * 1024) - }) + const metered = unixfs({ blockstore: meteredBlocks(node.blockstore, progress, limit) }) + + // Without a bound on how many blocks are read at a time the exporter + // requests the whole DAG before a single byte can be counted, and the limit + // bounds nothing + const readOptions = { + signal: AbortSignal.timeout(30_000), + blockReadConcurrency: INTAKE_READ_CONCURRENCY + } await assert.rejects(async () => { - for await (const chunk of metered.cat(cid)) { + for await (const chunk of metered.cat(cid, readOptions)) { void chunk } }, /larger than this node accepts/) assert.ok( - progress.bytes < payload.byteLength, - `metered ${progress.bytes} bytes, so the walk ran further than the limit allowed` + progress.bytes <= limit + INTAKE_OVERSHOOT_BYTES, + `metered ${progress.bytes} bytes, past the ${limit + INTAKE_OVERSHOOT_BYTES} the reservation covers` ) }) }) diff --git a/test/meter.test.ts b/test/meter.test.ts index 71ab49a..1f9e235 100644 --- a/test/meter.test.ts +++ b/test/meter.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { CID } from 'multiformats/cid' -import { meteredBlocks, type ReadableBlocks } from '../src/storage/meter.js' +import { MAX_BLOCK_BYTES, meteredBlocks, type ReadableBlocks } from '../src/storage/meter.js' const CID_A = CID.parse('bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku') @@ -51,6 +51,19 @@ describe('meteredBlocks', () => { await assert.rejects(() => drain(metered), /larger than this node accepts/) }) + it('charges the whole block it stopped on, not the part it had read', async () => { + const progress = { bytes: 0 } + const metered = meteredBlocks(blocks(600), progress, 1000) + + await drain(metered) + await assert.rejects(() => drain(metered)) + + // A block is fetched and stored whole, so the overshoot is one block and + // callers reserve MAX_BLOCK_BYTES of headroom for it + assert.equal(progress.bytes, 1200) + assert.ok(progress.bytes <= 1000 + MAX_BLOCK_BYTES) + }) + it('keeps what a failed transfer already cost', async () => { const progress = { bytes: 0 } const metered = meteredBlocks(blocks(2000), progress, 1500) diff --git a/test/rollback.test.ts b/test/rollback.test.ts new file mode 100644 index 0000000..8dd72c0 --- /dev/null +++ b/test/rollback.test.ts @@ -0,0 +1,132 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { FsDatastore } from 'datastore-fs' +import { FileRegistry } from '../src/storage/registry.js' +import { rollbackUpload } from '../src/storage/rollback.js' + +const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' +const TTL = 60_000 +const file = { cid: CID_A, name: 'photo.jpg', fileSize: 100, storedBytes: 120 } + +let storeDir: string +let datastore: FsDatastore +let counter = 0 + +function createRegistry(): FileRegistry { + counter += 1 + return new FileRegistry(datastore, `/adm/rollback-${counter}`) +} + +before(async () => { + storeDir = await mkdtemp(join(tmpdir(), 'ipfs-node-rollback-')) + datastore = new FsDatastore(join(storeDir, 'datastore')) + await datastore.open() +}) + +after(async () => { + await datastore.close() + await rm(storeDir, { recursive: true, force: true }) +}) + +describe('rollbackUpload', () => { + it('removes the record and the pin this request created', async () => { + const registry = createRegistry() + const unpinned: string[] = [] + const written = await registry.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + + await rollbackUpload({ + registry, + cid: CID_A, + written: written.record, + previous: written.previous, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) + + // Session cleanup skips pinned blocks, so a pin left behind here would keep + // the failed upload on disk with nothing accounting for it + assert.equal(await registry.get(CID_A), undefined) + assert.deepEqual(unpinned, [CID_A]) + }) + + it('keeps the pin when a record survives the rollback', async () => { + const registry = createRegistry() + const unpinned: string[] = [] + await registry.register(file, { confirmationRequired: false, temporaryTtlMs: TTL }) + + const written = await registry.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + + await rollbackUpload({ + registry, + cid: CID_A, + written: written.record, + previous: written.previous, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) + + assert.notEqual(await registry.get(CID_A), undefined) + assert.deepEqual(unpinned, []) + }) + + it('leaves a concurrent upload of the same file alone', async () => { + const registry = createRegistry() + const unpinned: string[] = [] + + // This request writes first and fails later; another one adopts the CID in + // between and is the reason both the record and the pin must stay + const mine = await registry.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + const theirs = await registry.register( + { ...file, name: 'theirs.jpg' }, + { confirmationRequired: false, temporaryTtlMs: TTL } + ) + + await rollbackUpload({ + registry, + cid: CID_A, + written: mine.record, + previous: mine.previous, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) + + assert.equal((await registry.get(CID_A))?.revision, theirs.revision) + assert.deepEqual(unpinned, []) + }) + + it('does nothing when the request never wrote a record', async () => { + const registry = createRegistry() + const unpinned: string[] = [] + + await rollbackUpload({ + registry, + cid: CID_A, + written: undefined, + previous: undefined, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) + + assert.deepEqual(unpinned, []) + }) +}) From 784e8e34d3a94800b59c32fbe571b8e001dcf59f Mon Sep 17 00:00:00 2001 From: metalisk Date: Tue, 25 Aug 2026 15:25:34 +0300 Subject: [PATCH 117/167] fix(after review): implement exclusive CID locking for concurrent uploads and enhance rollback functionality --- docs/storage-lifecycle.md | 8 + src/api/file.ts | 148 +++++++++--------- src/storage/gc.ts | 81 +++++++--- src/storage/meter.ts | 8 +- src/storage/registry.ts | 57 ++++++- src/storage/rollback.ts | 58 +++---- src/storage/service.ts | 176 +++++++++++++++++----- test/fileRegistry.test.ts | 48 ++++++ test/integration/storageLifecycle.test.ts | 61 ++++++++ test/meter.test.ts | 13 +- test/rollback.test.ts | 159 +++++++++++-------- 11 files changed, 579 insertions(+), 238 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index d1a416b..b46d096 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -78,6 +78,14 @@ pinned, and registered. Any other outcome removes the recorded blocks: - the route failed after some files were already imported - the client disconnected mid-upload +Pinning and lifecycle registration are one transaction for concurrency +purposes. Before changing either, a request locks every CID it contains in +lexical order and holds those locks through commit or rollback. Another upload, +confirmation, release, collector or handover therefore sees either the state +before the request or the state after it, never a pin from one side and a +registry record from the other. Ordering the locks also keeps two overlapping +multi-file requests from deadlocking. + A recorded block is deleted only when no other in-flight upload references it and no pin protects it. Anything that survives cleanup is unpinned and therefore reclaimable by the next collection. diff --git a/src/api/file.ts b/src/api/file.ts index 3463893..1dd82e6 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -12,7 +12,6 @@ import { releaseFile, replicateFile } from '../storage/service.js' -import type { FileRecord } from '../storage/registry.js' import { rollbackUpload } from '../storage/rollback.js' import { fileRegistry } from '../storage/state.js' import { parseCid } from '../utils/cid.js' @@ -52,73 +51,89 @@ router.post( const session = getUploadSession(req) - // A pin protects blocks from the session cleanup, and a registry entry - // claims disk in the storage report. Both are created before the request is - // known to succeed, so both have to be undone if it does not. - const undo: Array<() => Promise> = [] - try { const files = flatFiles(req.files as UnixFsMulterFile[]) logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) - const records = [] - for (const file of files) { - const cid = file.cid.toString() - logger.info(`Successfully added file ${cid}`) - - // Only a pin this request created may be removed again; content that - // was already durable keeps the pin it had. - const createdPin = await pinFile(helia, file.cid) - - // What this request wrote, and what it replaced. Both are filled in - // below, after the compensation that reads them is registered. - // - // The baseline cannot be read before the write: another upload of the - // same file can adopt the CID in between, and restoring what this - // request saw earlier would erase the lifecycle that upload owns. The - // registration reports what it actually replaced, from inside the same - // serialised section. - const write: { record?: FileRecord; previous?: FileRecord } = {} - - // One compensation rather than two: the pin and the record have to be - // undone under the same lock and in that order. - undo.push(() => - rollbackUpload({ - registry: fileRegistry, - cid, - written: write.record, - previous: write.previous, - createdPin, - unpin: async () => { - await unpinFile(helia, file.cid) + // Hold all involved CIDs until this request either commits or restores + // every pin and lifecycle record it changed. Sorting happens inside the + // registry, so overlapping multi-file requests cannot deadlock. + const records = await fileRegistry.withExclusiveCids( + files.map((file) => file.cid.toString()), + async (registry) => { + const undo: Array<() => Promise> = [] + + try { + const stored = [] + + for (const file of files) { + const cid = file.cid.toString() + logger.info(`Successfully added file ${cid}`) + + const previous = await registry.get(cid) + const createdPin = await pinFile(helia, file.cid) + + let registration + try { + registration = await registry.registerReplacing( + { + cid, + name: file.originalname, + fileSize: file.size, + storedBytes: file.storedBytes + }, + { + confirmationRequired: config.storage.confirmationRequired, + temporaryTtlMs: config.storage.temporaryTtlMs + } + ) + } catch (err) { + // A datastore failure can happen before or after it touches the + // key. Restore both sides from the baseline captured under the + // same CID lock, so neither outcome leaks a pin or a record. + await rollbackUpload({ + registry, + cid, + previous, + createdPin, + unpin: async () => { + await unpinFile(helia, file.cid) + } + }) + throw err + } + + undo.push(() => + rollbackUpload({ + registry, + cid, + previous: registration.previous, + createdPin, + unpin: async () => { + await unpinFile(helia, file.cid) + } + }) + ) + + stored.push(registration.record) } - }) - ) - const registration = await fileRegistry.registerReplacing( - { - cid, - name: file.originalname, - fileSize: file.size, - storedBytes: file.storedBytes - }, - { - confirmationRequired: config.storage.confirmationRequired, - temporaryTtlMs: config.storage.temporaryTtlMs + // Every file is stored and pinned. Ownership passes from the + // request session to the registry before any CID lock is released. + session.commit() + return stored + } catch (err) { + for (const step of undo.reverse()) { + try { + await step() + } catch (undoError) { + logger.error(`Could not undo a failed upload: ${(undoError as Error).message}`) + } + } + throw err } - ) - - write.record = registration.record - write.previous = registration.previous - - records.push(registration.record) - } - - // Every file is stored and pinned: the blocks written by this request must - // now survive, so ownership passes from the session to the registry and - // there is nothing left to undo. - session.commit() - undo.length = 0 + } + ) const durable = records.filter((record) => record.state === 'confirmed') const replication = await Promise.all( @@ -157,17 +172,6 @@ router.post( replication: replication[0]?.report ?? null }) } catch (err) { - // Undo first, then let the response listener reclaim the blocks: cleanup - // deliberately skips anything pinned, so a pin left behind here would - // keep the failed upload on disk forever. - for (const step of undo.reverse()) { - try { - await step() - } catch (undoError) { - logger.error(`Could not undo a failed upload: ${(undoError as Error).message}`) - } - } - next(err) } } diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 9db4fd7..eb5b10a 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -250,27 +250,47 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { + const current = await registry.get(record.cid) - if (options.dryRun === true) { - report.repairedPins.push(record.cid) - continue - } + if (current === undefined || current.state !== 'confirmed' || !current.heldLocally) { + return false + } - const signal = - options.pinTimeoutMs === undefined ? undefined : AbortSignal.timeout(options.pinTimeoutMs) + const cid = CID.parse(record.cid) + if (await isProtected(options.node, cid)) { + return false + } - await pinFile(options.node, cid, signal) - await options.registry.setPinned(record.cid, true) + log(`Restoring the missing pin of the confirmed file ${record.cid}`) + const signal = + options.pinTimeoutMs === undefined ? undefined : AbortSignal.timeout(options.pinTimeoutMs) + const createdPin = await pinFile(options.node, cid, signal) + + try { + await registry.save({ ...current, pinned: true }) + return true + } catch (err) { + if (createdPin && current.pinned !== true) { + await unpinFile(options.node, cid) + } + throw err + } + }) // Recorded only once the pin is actually back, so the field cannot claim // a repair that did not happen. - report.repairedPins.push(record.cid) + if (repaired) { + report.repairedPins.push(record.cid) + } } catch (err) { report.unprotected.push(record.cid) errors.push(`Pin check failed for ${record.cid}: ${(err as Error).message}`) @@ -304,15 +324,36 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { - if (current === undefined || current.state === 'confirmed') { - return 'keep' + const releasedRecord = await options.registry.withExclusiveCids( + [record.cid], + async (registry) => { + const current = await registry.get(record.cid) + if ( + current === undefined || + current.revision !== record.revision || + current.state === 'confirmed' + ) { + return undefined + } + + const cid = CID.parse(record.cid) + const removedPin = await unpinFile(options.node, cid) + + try { + return await registry.save({ + ...current, + state: 'expired', + pinned: false, + heldLocally: false + }) + } catch (err) { + if (removedPin && current.pinned) { + await pinFile(options.node, cid) + } + throw err + } } - - await unpinFile(options.node, CID.parse(record.cid)) - - return { ...current, state: 'expired', pinned: false, heldLocally: false } - }) + ) if (releasedRecord) { releasedCleanly.push(releasedRecord) diff --git a/src/storage/meter.ts b/src/storage/meter.ts index b0c1c6b..44c9f5f 100644 --- a/src/storage/meter.ts +++ b/src/storage/meter.ts @@ -9,12 +9,14 @@ export interface TransferProgress { export type ReadableBlocks = Pick /** - * Largest block a transfer is assumed to bring in at once. + * Largest block the configured Bitswap transport can bring in at once. * * A block is fetched and written whole before any of its bytes can be counted, - * so a limit can only be noticed once it has already been passed. + * so a limit can only be noticed once it has already been passed. Bitswap's + * encoded-message overhead leaves its actual block maximum just under 4 MiB; + * reserving the full 4 MiB keeps the bound conservative. */ -export const MAX_BLOCK_BYTES = 2 * 1024 ** 2 +export const MAX_BLOCK_BYTES = 4 * 1024 ** 2 /** * Blocks an intake reads at a time. diff --git a/src/storage/registry.ts b/src/storage/registry.ts index df04ad2..3491606 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -50,11 +50,10 @@ export interface FileRecord { /** * Opaque stamp of the write that stored this record. * - * It exists so a caller can tell its own write apart from an identical one - * made by somebody else. Two concurrent uploads of the same file produce - * records that match field for field, so without this the compensation of a - * failed upload cannot see that another request has since adopted the CID — - * and would undo work that succeeded. + * It exists so a caller can tell a planned lifecycle action apart from a + * newer write with identical fields. Garbage collection, handover and rescue + * all do network or blockstore work after selecting a record; without this + * stamp they could apply that stale decision to a re-uploaded CID. * * It is unique within a process run and means nothing across restarts, which * is all it is compared over: a caller only ever checks a record against a @@ -73,6 +72,24 @@ export interface NewFileRecord { storedBytes: number } +/** + * Registry operations that are safe to compose while their CID locks are held. + * + * Calling the regular public mutators from inside {@link FileRegistry.withExclusiveCids} + * would queue behind the lock the caller already owns. This view deliberately + * bypasses that second acquisition while keeping the datastore operations and + * revision stamping in one place. + */ +export interface LockedFileRegistry { + get(cid: string): Promise + save(record: FileRecord): Promise + remove(cid: string): Promise + registerReplacing( + file: NewFileRecord, + options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } + ): Promise<{ record: FileRecord; previous: FileRecord | undefined }> +} + function isNotFound(err: unknown): boolean { return (err as { name?: string })?.name === 'NotFoundError' } @@ -143,6 +160,36 @@ export class FileRegistry { } } + /** + * Hold every requested CID lock until `work` finishes. + * + * Locks are acquired in lexical order, so two multi-file uploads cannot take + * the same locks in opposite orders and deadlock. The callback receives + * mutators that operate under those already-held locks. + * + * @param cids CIDs whose lifecycle and pin state must not be interleaved + * @param work Compound operation to run while all locks are held + */ + async withExclusiveCids( + cids: Iterable, + work: (registry: LockedFileRegistry) => Promise + ): Promise { + const ordered = [...new Set(cids)].sort() + const locked: LockedFileRegistry = { + get: (cid) => this.get(cid), + save: (record) => this.save(record), + remove: (cid) => this.removeRecord(cid), + registerReplacing: (file, options) => this.registerExclusively(file, options) + } + + const acquire = (index: number): Promise => { + const cid = ordered[index] + return cid === undefined ? work(locked) : this.exclusive(cid, () => acquire(index + 1)) + } + + return acquire(0) + } + /** * Record a file, but only while the registry does not know it. * diff --git a/src/storage/rollback.ts b/src/storage/rollback.ts index 135f5e5..75c2c2a 100644 --- a/src/storage/rollback.ts +++ b/src/storage/rollback.ts @@ -1,10 +1,9 @@ -import type { FileRecord, FileRegistry } from './registry.js' +import type { FileRecord, LockedFileRegistry } from './registry.js' export interface UploadRollback { - registry: FileRegistry + /** Registry view whose CID lock the caller already holds. */ + registry: LockedFileRegistry cid: string - /** The record this request stored, when it got that far. */ - written?: FileRecord /** What that write replaced, as reported by the write itself. */ previous?: FileRecord /** Whether the pin on this CID was created by this request. */ @@ -16,41 +15,24 @@ export interface UploadRollback { /** * Undo what one file of a failed upload left behind. * - * The pin and the registry record have to be undone together, under the same - * per-CID lock, and in that order. Undoing them as two steps cannot work in - * either order: - * - * - unpin first, and the check that protects a concurrent upload's content - * ("is anyone claiming this CID?") finds this request's own record and keeps - * the pin, which the next step then removes — leaving content pinned with - * nothing accounting for it, permanently, because session cleanup skips - * pinned blocks; - * - remove the record first, and a failure to unpin afterwards leaves the same - * orphan. - * - * Nothing is undone once another request has written to the CID: it owns the - * lifecycle and the content from that point, whether or not it created the pin - * itself. A second upload of the same file finds it pinned already and creates - * no pin of its own, so the pin it depends on is this one. + * The caller holds the CID lock from registration through request commit or + * rollback. No other request can adopt the pin between these steps, so this + * first restores the real pin state and only then restores or removes the + * registry entry. If unpinning fails, the current entry deliberately remains: + * a visible record for protected content is recoverable, while an orphan pin + * is not. */ export async function rollbackUpload(options: UploadRollback): Promise { - await options.registry.transition(options.cid, async (current) => { - if (options.written === undefined || current?.revision !== options.written.revision) { - return 'keep' - } - - // A CID this node already knew keeps its earlier lifecycle: registering it - // again may move an expired or released record to confirmed, and dropping - // it would lose a lifecycle this request did not create. Its pin stays with - // it. - if (options.previous) { - return options.previous - } - - if (options.createdPin) { - await options.unpin() - } + // `pinFile` returns true only when there was no direct pin before this + // request. Keep a newly repaired pin only when the earlier record expected + // one; otherwise restore the real state as well as its metadata. + if (options.createdPin && options.previous?.pinned !== true) { + await options.unpin() + } - return 'remove' - }) + if (options.previous) { + await options.registry.save(options.previous) + } else { + await options.registry.remove(options.cid) + } } diff --git a/src/storage/service.ts b/src/storage/service.ts index c10acb9..5d9088f 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -8,7 +8,7 @@ import { getNodesList } from '../utils/utils.js' import { blockstorePath } from '../store.js' import { mayDemote, placeFile, storageTargets, type Placement } from './placement.js' import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' -import type { FileRecord } from './registry.js' +import type { FileRecord, LockedFileRegistry } from './registry.js' import { isUnderReplicated, replicate, @@ -164,23 +164,44 @@ async function placeCopy(peer: ReplicationPeer, cid: string): Promise { +async function registerPinnedLocked( + registry: LockedFileRegistry, + cid: CID, + name: string, + previous: FileRecord | undefined +): Promise { const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) + const createdPin = await pinFile(helia, cid, signal) - await pinFile(helia, cid, signal) - - // Everything is local after pinning, so the deduplicated DAG size is what - // this node actually holds on disk for the file. - const stats = await ifs.stat(cid, { extended: true, offline: true, signal }) + try { + // Everything is local after pinning, so the deduplicated DAG size is what + // this node actually holds on disk for the file. + const stats = await ifs.stat(cid, { extended: true, offline: true, signal }) + + return ( + await registry.registerReplacing( + { + cid: cid.toString(), + name, + fileSize: Number(stats.size), + storedBytes: Number(stats.deduplicatedDagSize) + }, + { confirmationRequired: false, temporaryTtlMs: config.storage.temporaryTtlMs } + ) + ).record + } catch (err) { + // A failed stat or datastore write must not leave a pin no lifecycle owns. + if (createdPin && previous?.pinned !== true) { + await unpinFile(helia, cid) + } + throw err + } +} - return fileRegistry.register( - { - cid: cid.toString(), - name, - fileSize: Number(stats.size), - storedBytes: Number(stats.deduplicatedDagSize) - }, - { confirmationRequired: false, temporaryTtlMs: config.storage.temporaryTtlMs } +async function registerPinned(cid: CID, name: string): Promise { + const key = cid.toString() + return fileRegistry.withExclusiveCids([key], async (registry) => + registerPinnedLocked(registry, cid, name, await registry.get(key)) ) } @@ -196,17 +217,36 @@ export async function confirmFile( options: { registerUnknown?: boolean } = {} ): Promise { const parsed = CID.parse(cid) - const known = await fileRegistry.get(cid) + const confirmed = await fileRegistry.withExclusiveCids([cid], async (registry) => { + const current = await registry.get(cid) - if (!known && options.registerUnknown !== true) { - return undefined - } + if (!current) { + return options.registerUnknown === true + ? registerPinnedLocked(registry, parsed, cid, current) + : undefined + } - if (known) { - await pinFile(helia, parsed) - await fileRegistry.confirm(cid) - } else { - await registerPinned(parsed, cid) + const createdPin = await pinFile(helia, parsed) + + try { + return await registry.save({ + ...current, + state: 'confirmed', + expiresAt: null, + confirmedAt: current.confirmedAt ?? Date.now(), + pinned: true, + heldLocally: true + }) + } catch (err) { + if (createdPin && current.pinned !== true) { + await unpinFile(helia, parsed) + } + throw err + } + }) + + if (!confirmed) { + return undefined } await replicateFile(cid) @@ -236,8 +276,32 @@ export function effectiveQuorum(cid: string, createdAt: number): number { * reversible until then. */ export async function releaseFile(cid: string): Promise { - await unpinFile(helia, CID.parse(cid)) - return fileRegistry.release(cid) + const parsed = CID.parse(cid) + + return fileRegistry.withExclusiveCids([cid], async (registry) => { + const current = await registry.get(cid) + const removedPin = await unpinFile(helia, parsed) + + if (!current) { + return undefined + } + + try { + return await registry.save({ + ...current, + state: 'expired', + pinned: false, + heldLocally: false + }) + } catch (err) { + // The lifecycle still promises protection when the datastore write did + // not land, so restore the pin before exposing the failure. + if (removedPin && current.pinned) { + await pinFile(helia, parsed) + } + throw err + } + }) } /** Store a copy requested by another ADAMANT node. */ @@ -484,14 +548,28 @@ async function rescueOrphanedFiles( // the file meanwhile — a re-upload, a release, an updated replica list — // so the pin and the state change happen together, against the record as // it is now. - const kept = await fileRegistry.transition(record.cid, async (current) => { - if (current === undefined) { - return 'keep' + const kept = await fileRegistry.withExclusiveCids([record.cid], async (registry) => { + const current = await registry.get(record.cid) + + if ( + current === undefined || + current.revision !== record.revision || + current.state !== 'confirmed' || + current.heldLocally + ) { + return undefined } - await pinFile(helia, cid) + const createdPin = await pinFile(helia, cid) - return { ...current, pinned: true, heldLocally: true } + try { + return await registry.save({ ...current, pinned: true, heldLocally: true }) + } catch (err) { + if (createdPin && current.pinned !== true) { + await unpinFile(helia, cid) + } + throw err + } }) if (kept === undefined) { @@ -662,15 +740,41 @@ export async function demoteReleasableCopies( continue } - report.demoted.push(record.cid) - if (options.dryRun === true) { + report.demoted.push(record.cid) continue } - await unpinFile(helia, CID.parse(record.cid)) - await fileRegistry.releaseLocalCopy(record.cid) - logger.info(`Released the local copy of ${record.cid}; ${holders.length} peers hold it`) + const demoted = await fileRegistry.withExclusiveCids([record.cid], async (registry) => { + const current = await registry.get(record.cid) + + if ( + current === undefined || + current.revision !== record.revision || + current.state !== 'confirmed' || + !current.heldLocally + ) { + return false + } + + const parsed = CID.parse(record.cid) + const removedPin = await unpinFile(helia, parsed) + + try { + await registry.save({ ...current, pinned: false, heldLocally: false }) + return true + } catch (err) { + if (removedPin && current.pinned) { + await pinFile(helia, parsed) + } + throw err + } + }) + + if (demoted) { + report.demoted.push(record.cid) + logger.info(`Released the local copy of ${record.cid}; ${holders.length} peers hold it`) + } } return report diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 3fad42d..e0245e4 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -252,6 +252,54 @@ describe('FileRegistry', () => { assert.deepEqual(await registry.all(), []) }) + + it('serializes compound work with regular CID mutations', async () => { + const registry = createRegistry() + let releaseFirst!: () => void + const firstMayFinish = new Promise((resolve) => { + releaseFirst = resolve + }) + let secondFinished = false + + const first = registry.withExclusiveCids([CID_A], async (locked) => { + await locked.registerReplacing(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) + await firstMayFinish + await locked.remove(CID_A) + }) + const second = registry + .register( + { ...newFile(), name: 'second.jpg' }, + { confirmationRequired: false, temporaryTtlMs: TTL } + ) + .then(() => { + secondFinished = true + }) + + await new Promise((resolve) => setImmediate(resolve)) + assert.equal(secondFinished, false) + + releaseFirst() + await Promise.all([first, second]) + + assert.equal((await registry.get(CID_A))?.name, 'second.jpg') + }) + + it('takes overlapping multi-CID locks in one order', async () => { + const registry = createRegistry() + + await Promise.race([ + Promise.all([ + registry.withExclusiveCids([CID_A, CID_B], async () => undefined), + registry.withExclusiveCids([CID_B, CID_A], async () => undefined) + ]), + new Promise((_, reject) => + setTimeout(() => reject(new Error('opposite CID order deadlocked')), 1000) + ) + ]) + }) }) describe('expiry rules', () => { diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index a533046..cb7bc92 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -6,6 +6,7 @@ import { after, before, describe, it } from 'node:test' import { unixfs, type UnixFS } from '@helia/unixfs' import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' +import { fixedSize } from 'ipfs-unixfs-importer/chunker' import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' @@ -451,6 +452,41 @@ describe('garbage collection against fixture data', () => { assert.equal((await storedDigests()).has(digestOf(cids.abandoned)), true) }) + it('does not apply an expired plan to a new temporary upload', async () => { + const { registry, cids } = await createFixture() + const stale = await registry.all() + const fresh = await registry.register( + { + cid: cids.abandoned, + name: 'new-upload.bin', + fileSize: 1024, + storedBytes: 1024 + }, + { confirmationRequired: true, temporaryTtlMs: 60_000, now: 2000 } + ) + + const planned = new Proxy(registry, { + get: (target, property) => + property === 'all' + ? async (): Promise => stale + : Reflect.get(target, property, target) + }) as FileRegistry + + const report = await runGarbageCollection({ + node, + registry: planned, + watermarks: TIGHT, + blockstoreBytes: 4096, + now: 2000 + }) + + const current = await registry.get(cids.abandoned) + assert.deepEqual(report.releasedCids, []) + assert.equal(current?.revision, fresh.revision) + assert.equal(current?.state, 'temporary') + assert.equal(await isProtected(node, CID.parse(cids.abandoned)), true) + }) + it('does not remove a record re-created while blocks were being deleted', async () => { const { registry, cids } = await createFixture() @@ -553,6 +589,31 @@ describe('block metering', () => { `metered ${progress.bytes} bytes, past the ${limit + INTAKE_OVERSHOOT_BYTES} the reservation covers` ) }) + + it('covers large in-flight blocks with the advertised overshoot', async () => { + const limit = 1024 * 1024 + const payload = deterministicBytes(15 * 1024 * 1024, 'large-metered-blocks') + const cid = await ifs.addBytes(payload, { + chunker: fixedSize({ chunkSize: 3 * 1024 * 1024 }) + }) + const progress = { bytes: 0 } + const metered = unixfs({ blockstore: meteredBlocks(node.blockstore, progress, limit) }) + const readOptions = { + signal: AbortSignal.timeout(30_000), + blockReadConcurrency: INTAKE_READ_CONCURRENCY + } + + await assert.rejects(async () => { + for await (const chunk of metered.cat(cid, readOptions)) { + void chunk + } + }, /larger than this node accepts/) + + assert.ok( + progress.bytes <= limit + INTAKE_OVERSHOOT_BYTES, + `metered ${progress.bytes} bytes, past the reserved ${limit + INTAKE_OVERSHOOT_BYTES}` + ) + }) }) describe('registry backfill', () => { diff --git a/test/meter.test.ts b/test/meter.test.ts index 1f9e235..dbb89db 100644 --- a/test/meter.test.ts +++ b/test/meter.test.ts @@ -1,7 +1,13 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { CID } from 'multiformats/cid' -import { MAX_BLOCK_BYTES, meteredBlocks, type ReadableBlocks } from '../src/storage/meter.js' +import { + INTAKE_OVERSHOOT_BYTES, + INTAKE_READ_CONCURRENCY, + MAX_BLOCK_BYTES, + meteredBlocks, + type ReadableBlocks +} from '../src/storage/meter.js' const CID_A = CID.parse('bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku') @@ -73,4 +79,9 @@ describe('meteredBlocks', () => { // A peer that sends almost everything and then aborts has spent it assert.ok(progress.bytes > 0, 'the bytes that arrived must still be charged') }) + + it('reserves one transport-sized block for every in-flight read', () => { + assert.equal(MAX_BLOCK_BYTES, 4 * 1024 ** 2) + assert.equal(INTAKE_OVERSHOOT_BYTES, INTAKE_READ_CONCURRENCY * MAX_BLOCK_BYTES) + }) }) diff --git a/test/rollback.test.ts b/test/rollback.test.ts index 8dd72c0..ebf592c 100644 --- a/test/rollback.test.ts +++ b/test/rollback.test.ts @@ -35,20 +35,21 @@ describe('rollbackUpload', () => { it('removes the record and the pin this request created', async () => { const registry = createRegistry() const unpinned: string[] = [] - const written = await registry.registerReplacing(file, { - confirmationRequired: false, - temporaryTtlMs: TTL - }) - - await rollbackUpload({ - registry, - cid: CID_A, - written: written.record, - previous: written.previous, - createdPin: true, - unpin: async () => { - unpinned.push(CID_A) - } + await registry.withExclusiveCids([CID_A], async (locked) => { + const written = await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + + await rollbackUpload({ + registry: locked, + cid: CID_A, + previous: written.previous, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) }) // Session cleanup skips pinned blocks, so a pin left behind here would keep @@ -62,71 +63,103 @@ describe('rollbackUpload', () => { const unpinned: string[] = [] await registry.register(file, { confirmationRequired: false, temporaryTtlMs: TTL }) - const written = await registry.registerReplacing(file, { - confirmationRequired: false, - temporaryTtlMs: TTL - }) - - await rollbackUpload({ - registry, - cid: CID_A, - written: written.record, - previous: written.previous, - createdPin: true, - unpin: async () => { - unpinned.push(CID_A) - } + await registry.withExclusiveCids([CID_A], async (locked) => { + const written = await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + + await rollbackUpload({ + registry: locked, + cid: CID_A, + previous: written.previous, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) }) assert.notEqual(await registry.get(CID_A), undefined) assert.deepEqual(unpinned, []) }) - it('leaves a concurrent upload of the same file alone', async () => { + it('restores an unpinned previous record and its real pin state', async () => { const registry = createRegistry() const unpinned: string[] = [] - - // This request writes first and fails later; another one adopts the CID in - // between and is the reason both the record and the pin must stay - const mine = await registry.registerReplacing(file, { - confirmationRequired: false, - temporaryTtlMs: TTL - }) - const theirs = await registry.register( - { ...file, name: 'theirs.jpg' }, - { confirmationRequired: false, temporaryTtlMs: TTL } - ) - - await rollbackUpload({ - registry, - cid: CID_A, - written: mine.record, - previous: mine.previous, - createdPin: true, - unpin: async () => { - unpinned.push(CID_A) - } + await registry.register(file, { confirmationRequired: false, temporaryTtlMs: TTL }) + await registry.release(CID_A) + + await registry.withExclusiveCids([CID_A], async (locked) => { + const written = await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + + await rollbackUpload({ + registry: locked, + cid: CID_A, + previous: written.previous, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) }) - assert.equal((await registry.get(CID_A))?.revision, theirs.revision) - assert.deepEqual(unpinned, []) + assert.equal((await registry.get(CID_A))?.state, 'expired') + assert.equal((await registry.get(CID_A))?.pinned, false) + assert.deepEqual(unpinned, [CID_A]) }) - it('does nothing when the request never wrote a record', async () => { + it('removes a created pin when registration never wrote a record', async () => { const registry = createRegistry() const unpinned: string[] = [] - await rollbackUpload({ - registry, - cid: CID_A, - written: undefined, - previous: undefined, - createdPin: true, - unpin: async () => { - unpinned.push(CID_A) - } + await registry.withExclusiveCids([CID_A], async (locked) => { + await rollbackUpload({ + registry: locked, + cid: CID_A, + previous: undefined, + createdPin: true, + unpin: async () => { + unpinned.push(CID_A) + } + }) }) - assert.deepEqual(unpinned, []) + assert.equal(await registry.get(CID_A), undefined) + assert.deepEqual(unpinned, [CID_A]) + }) + + it('serializes two failed uploads so neither lifecycle survives', async () => { + const registry = createRegistry() + let pinned = false + + const failUpload = (name: string): Promise => + registry.withExclusiveCids([CID_A], async (locked) => { + const previous = await locked.get(CID_A) + const createdPin = !pinned + pinned = true + await locked.registerReplacing( + { ...file, name }, + { confirmationRequired: false, temporaryTtlMs: TTL } + ) + + await rollbackUpload({ + registry: locked, + cid: CID_A, + previous, + createdPin, + unpin: async () => { + pinned = false + } + }) + }) + + await Promise.all([failUpload('first.jpg'), failUpload('second.jpg')]) + + assert.equal(await registry.get(CID_A), undefined) + assert.equal(pinned, false) }) }) From 24f9a67d20095281eff0727a35151a20a829fb99 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 25 Aug 2026 15:53:33 +0300 Subject: [PATCH 118/167] fix(after review): implement exclusive CID locking and enhance error handling in file operations --- package-lock.json | 5 +++-- package.json | 1 + src/api/file.ts | 7 ++++++- src/storage/registry.ts | 37 +++++++++++++++++++++++++++++++- src/storage/service.ts | 44 ++++++++++++++++++++++++--------------- test/fileRegistry.test.ts | 37 ++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index b30e071..8e4db13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ipfs-node", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ipfs-node", - "version": "0.0.1", + "version": "0.1.0", "license": "GPLv3", "dependencies": { "@chainsafe/libp2p-noise": "^17.0.0", @@ -49,6 +49,7 @@ "eslint-config-prettier": "^10.1.8", "globals": "^17.11.0", "husky": "^9.1.7", + "ipfs-unixfs-importer": "^17.0.1", "lint-staged": "^17.3.0", "nodemon": "^3.1.14", "prettier": "^3.9.6", diff --git a/package.json b/package.json index 1d70e0c..445d4ae 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "eslint-config-prettier": "^10.1.8", "globals": "^17.11.0", "husky": "^9.1.7", + "ipfs-unixfs-importer": "^17.0.1", "lint-staged": "^17.3.0", "nodemon": "^3.1.14", "prettier": "^3.9.6", diff --git a/src/api/file.ts b/src/api/file.ts index 1dd82e6..004e6dd 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -265,9 +265,14 @@ fileAdminRouter.post('/:cid/unpin', async (req, res, next) => { const cid = parseCid(req.params.cid) const record = await releaseFile(cid.toString()) + // Nothing was released, so saying so would be a lie an operator acts on. + if (!record) { + return res.status(404).send({ error: 'Unknown CID' }) + } + res.send({ cid: cid.toString(), - state: record?.state ?? 'expired', + state: record.state, pinned: false, reclaimable: true }) diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 3491606..58dd768 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -1,3 +1,4 @@ +import { AsyncLocalStorage } from 'node:async_hooks' import { Key } from 'interface-datastore' import type { Datastore, Pair } from 'interface-datastore' @@ -138,8 +139,32 @@ export class FileRegistry { */ private readonly queued = new Map>() + /** + * CIDs whose lock is held by the call currently running. + * + * The class exposes two ways to change a record: the public mutators, which + * take the lock themselves, and the {@link LockedFileRegistry} view handed to + * {@link withExclusiveCids}, which does not. Mixing them is a deadlock: the + * inner call queues behind a lock its own caller is holding, and neither ever + * finishes. Nothing observable happens — no error, no timeout, just a request + * that hangs until the client gives up. + * + * Tracking the held locks per asynchronous context turns that into a thrown + * error at the call that made the mistake. + */ + private readonly held = new AsyncLocalStorage>() + /** Run `work` with nothing else touching this CID. */ private async exclusive(cid: string, work: () => Promise): Promise { + const holding = this.held.getStore() + + if (holding?.has(cid)) { + throw new Error( + `Deadlock avoided: ${cid} is already locked by this operation. ` + + 'Use the registry passed to withExclusiveCids instead of the public mutators.' + ) + } + const previous = this.queued.get(cid) ?? Promise.resolve() // Runs whether or not the previous holder succeeded; its failure is its own. const result = previous.then(work, work) @@ -182,9 +207,19 @@ export class FileRegistry { registerReplacing: (file, options) => this.registerExclusively(file, options) } + const holding = new Set(this.held.getStore() ?? []) + const acquire = (index: number): Promise => { const cid = ordered[index] - return cid === undefined ? work(locked) : this.exclusive(cid, () => acquire(index + 1)) + + if (cid === undefined) { + return this.held.run(holding, () => work(locked)) + } + + return this.exclusive(cid, () => { + holding.add(cid) + return acquire(index + 1) + }) } return acquire(0) diff --git a/src/storage/service.ts b/src/storage/service.ts index 5d9088f..c036e62 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -280,12 +280,19 @@ export async function releaseFile(cid: string): Promise return fileRegistry.withExclusiveCids([cid], async (registry) => { const current = await registry.get(cid) - const removedPin = await unpinFile(helia, parsed) + // Nothing is unpinned for a CID the registry does not know. Startup records + // every pin this node holds, so an unknown CID is either not pinned at all + // or has not been reconciled yet — and reconciliation is exactly the window + // in which removing a pin would drop protection from content that predates + // the registry. The caller is told it was unknown rather than that it was + // released. if (!current) { return undefined } + const removedPin = await unpinFile(helia, parsed) + try { return await registry.save({ ...current, @@ -446,15 +453,27 @@ async function pullUnderIntakeLimits( * @returns Bytes pulled */ export async function cacheFileLocally(cid: string, peerId: string): Promise { + // A copy is held to the same size an upload is, so a node accepts as a copy + // what it would accept as a file. Taking the headroom off this instead would + // make the two disagree, and quietly: at a 20 MiB request limit a copy would + // stop at 4 MiB, and at 16 MiB or less no copy could be held at all — while + // uploads of that size kept working and nothing said why. + // + // The budget is what carries the headroom. The allowance leaves room for it + // so that reserving both still fits, which also keeps a node configured to + // accept requests larger than a peer's whole hourly budget from refusing + // every copy outright instead of allowing one at a time. + const allowance = Math.min( + config.storage.maxRequestSizeBytes, + PER_PEER_INTAKE_BYTES - INTAKE_OVERSHOOT_BYTES + ) + // Reserved before a block is fetched rather than counted afterwards. A check // that only reads the counters lets every concurrent request see the same // figure and pass, and a transfer charged on completion costs a peer nothing - // when it aborts after sending almost all of it. - // Capped at the budget itself: a node configured to accept requests larger - // than a peer's whole hourly allowance would otherwise refuse every copy - // outright instead of allowing one at a time. - const allowance = Math.min(config.storage.maxRequestSizeBytes, PER_PEER_INTAKE_BYTES) - const reservation = reserveIntake(peerId, allowance) + // when it aborts after sending almost all of it. What is reserved covers the + // overshoot, because the limit is noticed late. + const reservation = reserveIntake(peerId, allowance + INTAKE_OVERSHOOT_BYTES) if (reservation === undefined) { throw new Error('Cache budget for this peer is spent') @@ -463,16 +482,7 @@ export async function cacheFileLocally(cid: string, peerId: string): Promise { assert.equal((await registry.get(CID_A))?.name, 'second.jpg') }) + it('refuses a public mutator called from inside its own lock', async () => { + // The two ways to change a record — the locking mutators and the view + // handed to withExclusiveCids — cannot be mixed: the inner call queues + // behind a lock its own caller holds. That used to hang with no error and + // no timeout, so the request simply never answered. + const registry = createRegistry() + await registry.register(newFile(), { confirmationRequired: false, temporaryTtlMs: TTL }) + + await assert.rejects( + () => registry.withExclusiveCids([CID_A], async () => registry.setPinned(CID_A, false)), + /Deadlock avoided/ + ) + }) + + it('refuses a nested lock on a CID the caller already holds', async () => { + const registry = createRegistry() + + await assert.rejects( + () => + registry.withExclusiveCids([CID_A], async () => + registry.withExclusiveCids([CID_A], async () => undefined) + ), + /Deadlock avoided/ + ) + }) + + it('allows a public mutator for a CID the caller does not hold', async () => { + const registry = createRegistry() + await registry.register(newFile(CID_B), { confirmationRequired: false, temporaryTtlMs: TTL }) + + const pinned = await registry.withExclusiveCids([CID_A], async () => + registry.setPinned(CID_B, false) + ) + + assert.equal(pinned?.pinned, false) + }) + it('takes overlapping multi-CID locks in one order', async () => { const registry = createRegistry() From 5baed1030e01c7bb18897b596d21b7ec4c17f0a2 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 25 Aug 2026 16:16:24 +0300 Subject: [PATCH 119/167] fix(lifecycle): implement lifecycle management for file registration, confirmation, and release --- src/storage/lifecycle.ts | 192 ++++++++++++++++++++++ src/storage/registry.ts | 92 ++--------- src/storage/service.ts | 133 +++------------ test/fileRegistry.test.ts | 119 ++++++++------ test/integration/storageLifecycle.test.ts | 169 ++++++++++++++++++- test/rollback.test.ts | 15 +- 6 files changed, 467 insertions(+), 253 deletions(-) create mode 100644 src/storage/lifecycle.ts diff --git a/src/storage/lifecycle.ts b/src/storage/lifecycle.ts new file mode 100644 index 0000000..21b9d0b --- /dev/null +++ b/src/storage/lifecycle.ts @@ -0,0 +1,192 @@ +import type { UnixFS } from '@helia/unixfs' +import type { CID } from 'multiformats/cid' +import type { IpfsNode } from '../ipfs-node.js' +import { pinFile, unpinFile } from './pinning.js' +import type { FileRecord, FileRegistry, LockedFileRegistry } from './registry.js' + +/** + * The lifecycle transitions that change a pin and a record together. + * + * They take what they work on rather than reaching for the node and the + * registry the process happens to have started with — the same shape garbage + * collection already uses. That is what makes them exercisable against a + * throwaway node in a test, which matters more here than elsewhere: each of + * them writes `pinned` and `heldLocally`, so a mistake produces a record that + * claims a protection the blockstore does not have. + */ +export interface LifecycleTarget { + node: IpfsNode + registry: FileRegistry +} + +export interface RegisterPinnedOptions extends LifecycleTarget { + unixfs: UnixFS + cid: CID + /** Display name recorded for the file; defaults to its CID. */ + name?: string + /** Lifetime an unconfirmed upload would get; unused here, since this path is durable. */ + temporaryTtlMs: number + /** Bounds the DAG walk when the content still has to be fetched. */ + pinTimeoutMs?: number +} + +/** + * Pin content and record it as durable, with the CID locked throughout. + * + * The DAG is pulled over libp2p if it is missing locally, bounded by + * `pinTimeoutMs` so an unreachable CID cannot hang the caller. + */ +export async function registerPinnedFile(options: RegisterPinnedOptions): Promise { + const key = options.cid.toString() + + return options.registry.withExclusiveCids([key], async (registry) => + registerPinnedUnderLock(registry, options, await registry.get(key)) + ) +} + +/** + * The body of {@link registerPinnedFile}, for a caller that already holds the + * CID lock and has read the record. + */ +async function registerPinnedUnderLock( + registry: LockedFileRegistry, + options: RegisterPinnedOptions, + previous: FileRecord | undefined +): Promise { + const signal = + options.pinTimeoutMs === undefined ? undefined : AbortSignal.timeout(options.pinTimeoutMs) + const createdPin = await pinFile(options.node, options.cid, signal) + + try { + // Everything is local after pinning, so the deduplicated DAG size is what + // this node actually holds on disk for the file. + const stats = await options.unixfs.stat(options.cid, { + extended: true, + offline: true, + signal + }) + + return ( + await registry.registerReplacing( + { + cid: options.cid.toString(), + name: options.name ?? options.cid.toString(), + fileSize: Number(stats.size), + storedBytes: Number(stats.deduplicatedDagSize) + }, + { confirmationRequired: false, temporaryTtlMs: options.temporaryTtlMs } + ) + ).record + } catch (err) { + // A failed stat or datastore write must not leave a pin no lifecycle owns. + if (createdPin && previous?.pinned !== true) { + await unpinFile(options.node, options.cid) + } + + throw err + } +} + +export interface ConfirmOptions extends RegisterPinnedOptions { + /** + * What to do with a CID the node never accepted through an upload. + * + * The confirmation endpoint reports it as unknown; an explicit pin request + * stores and registers it. + */ + registerUnknown?: boolean + now?: number +} + +/** + * Make a known file durable, or adopt an unknown one. + * + * The final state check, the pin and the registry write happen under one CID + * lock. Split apart, a concurrent release could remove the pin between the two, + * and this would then publish a `confirmed`, `pinned` record for content that is + * no longer protected. + * + * @returns The stored record, or `undefined` for an unknown CID that this call + * was not asked to adopt + */ +export async function confirmStoredFile(options: ConfirmOptions): Promise { + const key = options.cid.toString() + + return options.registry.withExclusiveCids([key], async (registry) => { + const current = await registry.get(key) + + if (!current) { + return options.registerUnknown === true + ? registerPinnedUnderLock(registry, options, current) + : undefined + } + + const createdPin = await pinFile(options.node, options.cid) + + try { + return await registry.save({ + ...current, + state: 'confirmed', + expiresAt: null, + confirmedAt: current.confirmedAt ?? options.now ?? Date.now(), + pinned: true, + heldLocally: true + }) + } catch (err) { + if (createdPin && current.pinned !== true) { + await unpinFile(options.node, options.cid) + } + + throw err + } + }) +} + +export interface ReleaseOptions extends LifecycleTarget { + cid: CID +} + +/** + * Release a file so garbage collection may reclaim it. + * + * Blocks stay on disk until the collector runs, which keeps the action + * reversible until then. + * + * @returns The released record, or `undefined` when the registry does not know + * the CID — in which case nothing was unpinned either + */ +export async function releaseStoredFile(options: ReleaseOptions): Promise { + const key = options.cid.toString() + + return options.registry.withExclusiveCids([key], async (registry) => { + const current = await registry.get(key) + + // Nothing is unpinned for a CID the registry does not know. Startup records + // every pin this node holds, so an unknown CID is either not pinned at all + // or has not been reconciled yet — and reconciliation is exactly the window + // in which removing a pin would drop protection from content that predates + // the registry. + if (!current) { + return undefined + } + + const removedPin = await unpinFile(options.node, options.cid) + + try { + return await registry.save({ + ...current, + state: 'expired', + pinned: false, + heldLocally: false + }) + } catch (err) { + // The lifecycle still promises protection when the datastore write did + // not land, so restore the pin before exposing the failure. + if (removedPin && current.pinned) { + await pinFile(options.node, options.cid) + } + + throw err + } + }) +} diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 58dd768..b8e8b70 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -350,36 +350,22 @@ export class FileRegistry { } /** - * Register a freshly uploaded file. + * Register a freshly uploaded file, and report the record it replaced. + * + * Reachable only through {@link withExclusiveCids}, because registering + * writes `pinned` and `heldLocally`: it describes protection it does not + * create. Whoever writes that has to hold the CID lock and pin the content in + * the same breath, or the record and the blockstore start disagreeing. + * + * A record read before the write is a guess by the time the write happens — + * another upload of the same file can adopt the CID in between — so what was + * replaced is captured here rather than by the caller. * * @param file Identity and size of the imported content * @param options `confirmationRequired` decides whether the upload becomes * durable right away, which keeps the current client protocol working, or * stays temporary until an authorized confirmation arrives */ - async register( - file: NewFileRecord, - options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } - ): Promise { - return (await this.registerReplacing(file, options)).record - } - - /** - * Register an upload and report the record it replaced. - * - * A record read before the write is already a guess by the time the write - * happens: another upload of the same file can adopt the CID in between. A - * rollback restoring what the caller saw earlier would erase the lifecycle - * that upload owns, so the replaced record is captured inside the same - * serialised section as the write that replaced it. - */ - async registerReplacing( - file: NewFileRecord, - options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } - ): Promise<{ record: FileRecord; previous: FileRecord | undefined }> { - return this.exclusive(file.cid, () => this.registerExclusively(file, options)) - } - private async registerExclusively( file: NewFileRecord, options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } @@ -420,64 +406,6 @@ export class FileRegistry { return { record, previous: existing } } - /** Move a temporary file to the durable state. Confirming twice is a no-op. */ - async confirm(cid: string, now: number = Date.now()): Promise { - return this.exclusive(cid, async () => { - const record = await this.get(cid) - if (!record) { - return undefined - } - - if (record.state === 'confirmed') { - return record - } - - return this.save({ - ...record, - state: 'confirmed', - expiresAt: null, - confirmedAt: now, - pinned: true, - heldLocally: true - }) - }) - } - - /** - * Release a file so that garbage collection may reclaim it. - * The blocks stay on disk until the collector runs. - */ - async release(cid: string): Promise { - return this.exclusive(cid, async () => { - const record = await this.get(cid) - if (!record) { - return undefined - } - - return this.save({ ...record, state: 'expired', pinned: false, heldLocally: false }) - }) - } - - /** - * Record that the file now lives on other nodes only. - * - * The file stays `confirmed` because it is still durable in the network; this - * node simply stopped being one of its holders. - */ - async releaseLocalCopy(cid: string): Promise { - return this.exclusive(cid, async () => { - const record = await this.get(cid) - return record ? this.save({ ...record, pinned: false, heldLocally: false }) : undefined - }) - } - - async setPinned(cid: string, pinned: boolean): Promise { - return this.exclusive(cid, async () => { - const record = await this.get(cid) - return record ? this.save({ ...record, pinned }) : undefined - }) - } - async setReplicas(cid: string, replicas: string[]): Promise { return this.exclusive(cid, async () => { const record = await this.get(cid) diff --git a/src/storage/service.ts b/src/storage/service.ts index c036e62..9103af5 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -8,7 +8,13 @@ import { getNodesList } from '../utils/utils.js' import { blockstorePath } from '../store.js' import { mayDemote, placeFile, storageTargets, type Placement } from './placement.js' import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' -import type { FileRecord, LockedFileRegistry } from './registry.js' +import { + confirmStoredFile, + registerPinnedFile, + releaseStoredFile, + type LifecycleTarget +} from './lifecycle.js' +import type { FileRecord } from './registry.js' import { isUnderReplicated, replicate, @@ -156,53 +162,19 @@ async function placeCopy(peer: ReplicationPeer, cid: string): Promise { - const signal = AbortSignal.timeout(config.replication.requestTimeoutMs) - const createdPin = await pinFile(helia, cid, signal) - - try { - // Everything is local after pinning, so the deduplicated DAG size is what - // this node actually holds on disk for the file. - const stats = await ifs.stat(cid, { extended: true, offline: true, signal }) - - return ( - await registry.registerReplacing( - { - cid: cid.toString(), - name, - fileSize: Number(stats.size), - storedBytes: Number(stats.deduplicatedDagSize) - }, - { confirmationRequired: false, temporaryTtlMs: config.storage.temporaryTtlMs } - ) - ).record - } catch (err) { - // A failed stat or datastore write must not leave a pin no lifecycle owns. - if (createdPin && previous?.pinned !== true) { - await unpinFile(helia, cid) - } - throw err - } -} +/** The node and registry every lifecycle transition on this node works against. */ +const lifecycleTarget = (): LifecycleTarget => ({ node: helia, registry: fileRegistry }) +/** Pin content that is not in the registry yet and record it as durable. */ async function registerPinned(cid: CID, name: string): Promise { - const key = cid.toString() - return fileRegistry.withExclusiveCids([key], async (registry) => - registerPinnedLocked(registry, cid, name, await registry.get(key)) - ) + return registerPinnedFile({ + ...lifecycleTarget(), + unixfs: ifs, + cid, + name, + temporaryTtlMs: config.storage.temporaryTtlMs, + pinTimeoutMs: config.replication.requestTimeoutMs + }) } /** @@ -216,33 +188,14 @@ export async function confirmFile( cid: string, options: { registerUnknown?: boolean } = {} ): Promise { - const parsed = CID.parse(cid) - const confirmed = await fileRegistry.withExclusiveCids([cid], async (registry) => { - const current = await registry.get(cid) - - if (!current) { - return options.registerUnknown === true - ? registerPinnedLocked(registry, parsed, cid, current) - : undefined - } - - const createdPin = await pinFile(helia, parsed) - - try { - return await registry.save({ - ...current, - state: 'confirmed', - expiresAt: null, - confirmedAt: current.confirmedAt ?? Date.now(), - pinned: true, - heldLocally: true - }) - } catch (err) { - if (createdPin && current.pinned !== true) { - await unpinFile(helia, parsed) - } - throw err - } + const confirmed = await confirmStoredFile({ + ...lifecycleTarget(), + unixfs: ifs, + cid: CID.parse(cid), + name: cid, + registerUnknown: options.registerUnknown, + temporaryTtlMs: config.storage.temporaryTtlMs, + pinTimeoutMs: config.replication.requestTimeoutMs }) if (!confirmed) { @@ -276,39 +229,7 @@ export function effectiveQuorum(cid: string, createdAt: number): number { * reversible until then. */ export async function releaseFile(cid: string): Promise { - const parsed = CID.parse(cid) - - return fileRegistry.withExclusiveCids([cid], async (registry) => { - const current = await registry.get(cid) - - // Nothing is unpinned for a CID the registry does not know. Startup records - // every pin this node holds, so an unknown CID is either not pinned at all - // or has not been reconciled yet — and reconciliation is exactly the window - // in which removing a pin would drop protection from content that predates - // the registry. The caller is told it was unknown rather than that it was - // released. - if (!current) { - return undefined - } - - const removedPin = await unpinFile(helia, parsed) - - try { - return await registry.save({ - ...current, - state: 'expired', - pinned: false, - heldLocally: false - }) - } catch (err) { - // The lifecycle still promises protection when the datastore write did - // not land, so restore the pin before exposing the failure. - if (removedPin && current.pinned) { - await pinFile(helia, parsed) - } - throw err - } - }) + return releaseStoredFile({ ...lifecycleTarget(), cid: CID.parse(cid) }) } /** Store a copy requested by another ADAMANT node. */ diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 855e2dc..dca0ec9 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -22,6 +22,25 @@ function createRegistry(): FileRegistry { const newFile = (cid = CID_A) => ({ cid, name: 'photo.jpg', fileSize: 100, storedBytes: 120 }) +/** + * Register a file the way production does. + * + * Registering writes `pinned` and `heldLocally`, so the registry only offers it + * under a held CID lock. Going through `withExclusiveCids` here is not + * ceremony: it is the path the upload route, replica intake and confirmation + * all take. + */ +function registerFile( + registry: FileRegistry, + file = newFile(), + options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } = { + confirmationRequired: false, + temporaryTtlMs: TTL + } +): Promise<{ record: FileRecord; previous: FileRecord | undefined }> { + return registry.withExclusiveCids([file.cid], (locked) => locked.registerReplacing(file, options)) +} + before(async () => { storeDir = await mkdtemp(join(tmpdir(), 'ipfs-node-registry-')) datastore = new FsDatastore(join(storeDir, 'datastore')) @@ -37,7 +56,7 @@ describe('FileRegistry', () => { it('registers an upload as confirmed when confirmation is not required', async () => { const registry = createRegistry() - const record = await registry.register(newFile(), { + const { record } = await registerFile(registry, newFile(), { confirmationRequired: false, temporaryTtlMs: TTL, now: 1000 @@ -52,7 +71,7 @@ describe('FileRegistry', () => { it('registers an upload as temporary with a TTL when confirmation is required', async () => { const registry = createRegistry() - const record = await registry.register(newFile(), { + const { record } = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL, now: 1000 @@ -65,13 +84,13 @@ describe('FileRegistry', () => { it('never downgrades content that is already confirmed', async () => { const registry = createRegistry() - await registry.register(newFile(), { + await registerFile(registry, newFile(), { confirmationRequired: false, temporaryTtlMs: TTL, now: 1000 }) - const reuploaded = await registry.register(newFile(), { + const { record: reuploaded } = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL, now: 2000 @@ -81,44 +100,37 @@ describe('FileRegistry', () => { assert.equal(reuploaded.expiresAt, null) }) - it('confirms a temporary file and clears its expiry', async () => { + it('re-registering a temporary file confirms it and clears its expiry', async () => { + // This is what an upload of already-known content does. Confirming through + // the admin endpoint and releasing a file both changed the pin as well as + // the record, so they live in the storage service now, behind the same CID + // lock; the registry only decides what registration itself writes. const registry = createRegistry() - await registry.register(newFile(), { + await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL, now: 1000 }) - const confirmed = await registry.confirm(CID_A, 5000) - - assert.equal(confirmed?.state, 'confirmed') - assert.equal(confirmed?.expiresAt, null) - assert.equal(confirmed?.confirmedAt, 5000) - }) - - it('reports an unknown CID instead of creating one on confirmation', async () => { - assert.equal(await createRegistry().confirm(CID_B), undefined) - }) - - it('releases a confirmed file so it becomes reclaimable', async () => { - const registry = createRegistry() - await registry.register(newFile(), { + const { record } = await registerFile(registry, newFile(), { confirmationRequired: false, temporaryTtlMs: TTL, - now: 1000 + now: 5000 }) - const released = await registry.release(CID_A) - - assert.equal(released?.state, 'expired') - assert.equal(released?.pinned, false) - assert.equal(isReclaimable(released as FileRecord, 1000), true) + assert.equal(record.state, 'confirmed') + assert.equal(record.expiresAt, null) + assert.equal(record.confirmedAt, 5000) + assert.equal(record.pinned, true) }) it('lists every registered file and survives a reopen', async () => { const registry = createRegistry() - await registry.register(newFile(CID_A), { confirmationRequired: false, temporaryTtlMs: TTL }) - await registry.register(newFile(CID_B), { confirmationRequired: true, temporaryTtlMs: TTL }) + await registerFile(registry, newFile(CID_A)) + await registerFile(registry, newFile(CID_B), { + confirmationRequired: true, + temporaryTtlMs: TTL + }) const cids = (await registry.all()).map((record) => record.cid) @@ -127,7 +139,7 @@ describe('FileRegistry', () => { it('forgets a removed file and tolerates removing it twice', async () => { const registry = createRegistry() - await registry.register(newFile(), { confirmationRequired: false, temporaryTtlMs: TTL }) + await registerFile(registry) await registry.remove(CID_A) await assert.doesNotReject(() => registry.remove(CID_A)) @@ -139,11 +151,11 @@ describe('FileRegistry', () => { // Read before the write, the baseline is a guess: another upload can adopt // the CID in between, and a rollback restoring the guess would erase it const registry = createRegistry() - const first = await registry.registerReplacing(newFile(), { + const first = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL }) - const second = await registry.registerReplacing(newFile(), { + const second = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL }) @@ -156,26 +168,26 @@ describe('FileRegistry', () => { // Two concurrent uploads of the same file write records that match field // for field. The first must not roll back the lifecycle the second owns. const registry = createRegistry() - const first = await registry.register(newFile(), { + const first = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL }) - const second = await registry.register(newFile(), { + const second = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL }) await registry.transition(CID_A, async (current) => - current?.revision === first.revision ? 'remove' : 'keep' + current?.revision === first.record.revision ? 'remove' : 'keep' ) - assert.notEqual(first.revision, second.revision) + assert.notEqual(first.record.revision, second.record.revision) assert.notEqual(await registry.get(CID_A), undefined) }) it('removes a record when the undo is still the last write', async () => { const registry = createRegistry() - const written = await registry.register(newFile(), { + const { record: written } = await registerFile(registry, newFile(), { confirmationRequired: true, temporaryTtlMs: TTL }) @@ -228,7 +240,10 @@ describe('FileRegistry', () => { } const [upload, backfilled] = await Promise.all([ - registry.register(newFile(CID_B), { confirmationRequired: true, temporaryTtlMs: TTL }), + registerFile(registry, newFile(CID_B), { + confirmationRequired: true, + temporaryTtlMs: TTL + }).then(({ record }) => record), registry.createIfAbsent(legacy) ]) @@ -269,22 +284,20 @@ describe('FileRegistry', () => { await firstMayFinish await locked.remove(CID_A) }) - const second = registry - .register( - { ...newFile(), name: 'second.jpg' }, - { confirmationRequired: false, temporaryTtlMs: TTL } - ) - .then(() => { - secondFinished = true - }) + const second = registry.setReplicas(CID_A, ['n2']).then((record) => { + secondFinished = true + return record + }) await new Promise((resolve) => setImmediate(resolve)) assert.equal(secondFinished, false) releaseFirst() - await Promise.all([first, second]) + const [, updated] = await Promise.all([first, second]) - assert.equal((await registry.get(CID_A))?.name, 'second.jpg') + // It ran after the compound work removed the record, not in the middle of it + assert.equal(updated, undefined) + assert.equal(await registry.get(CID_A), undefined) }) it('refuses a public mutator called from inside its own lock', async () => { @@ -293,10 +306,10 @@ describe('FileRegistry', () => { // behind a lock its own caller holds. That used to hang with no error and // no timeout, so the request simply never answered. const registry = createRegistry() - await registry.register(newFile(), { confirmationRequired: false, temporaryTtlMs: TTL }) + await registerFile(registry) await assert.rejects( - () => registry.withExclusiveCids([CID_A], async () => registry.setPinned(CID_A, false)), + () => registry.withExclusiveCids([CID_A], async () => registry.setReplicas(CID_A, ['n2'])), /Deadlock avoided/ ) }) @@ -315,13 +328,13 @@ describe('FileRegistry', () => { it('allows a public mutator for a CID the caller does not hold', async () => { const registry = createRegistry() - await registry.register(newFile(CID_B), { confirmationRequired: false, temporaryTtlMs: TTL }) + await registerFile(registry, newFile(CID_B)) - const pinned = await registry.withExclusiveCids([CID_A], async () => - registry.setPinned(CID_B, false) + const updated = await registry.withExclusiveCids([CID_A], async () => + registry.setReplicas(CID_B, ['n3']) ) - assert.equal(pinned?.pinned, false) + assert.deepEqual(updated?.replicas, ['n3']) }) it('takes overlapping multi-CID locks in one order', async () => { diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index cb7bc92..04aa874 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -11,6 +11,11 @@ import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' import { runGarbageCollection } from '../../src/storage/gc.js' +import { + confirmStoredFile, + registerPinnedFile, + releaseStoredFile +} from '../../src/storage/lifecycle.js' import { INTAKE_OVERSHOOT_BYTES, INTAKE_READ_CONCURRENCY, @@ -53,6 +58,18 @@ function createSession(maxRequestSizeBytes = 64 * 1024 * 1024): UploadSession { }) } +/** Register a file the way an upload does: under the CID lock. */ +function registerFile( + registry: FileRegistry, + file: { cid: string; name: string; fileSize: number; storedBytes: number }, + options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } +): Promise { + return registry.withExclusiveCids( + [file.cid], + async (locked) => (await locked.registerReplacing(file, options)).record + ) +} + function record(overrides: Pick & Partial): FileRecord { return { name: 'file', @@ -429,7 +446,11 @@ describe('garbage collection against fixture data', () => { // The plan is made from a snapshot; an upload can re-register and confirm // the same CID before the run reaches the unpin const stale = await registry.all() - await registry.confirm(cids.abandoned) + await registerFile( + registry, + { cid: cids.abandoned, name: 'again.bin', fileSize: 1024, storedBytes: 1024 }, + { confirmationRequired: false, temporaryTtlMs: 60_000 } + ) const planned = new Proxy(registry, { get: (target, property) => @@ -455,13 +476,9 @@ describe('garbage collection against fixture data', () => { it('does not apply an expired plan to a new temporary upload', async () => { const { registry, cids } = await createFixture() const stale = await registry.all() - const fresh = await registry.register( - { - cid: cids.abandoned, - name: 'new-upload.bin', - fileSize: 1024, - storedBytes: 1024 - }, + const fresh = await registerFile( + registry, + { cid: cids.abandoned, name: 'new-upload.bin', fileSize: 1024, storedBytes: 1024 }, { confirmationRequired: true, temporaryTtlMs: 60_000, now: 2000 } ) @@ -497,7 +514,8 @@ describe('garbage collection against fixture data', () => { property === 'gc' ? async (): Promise => { await pinFile(node, CID.parse(cids.abandoned)) - await registry.register( + await registerFile( + registry, { cid: cids.abandoned, name: 'again.bin', fileSize: 1024, storedBytes: 1024 }, { confirmationRequired: false, temporaryTtlMs: 1000 } ) @@ -616,6 +634,139 @@ describe('block metering', () => { }) }) +describe('lifecycle transitions', () => { + const TTL = 60_000 + + /** A registry whose datastore write always fails, to exercise compensation. */ + function unwritable(registry: FileRegistry): FileRegistry { + return new Proxy(registry, { + get: (target, property) => + property === 'save' + ? async (): Promise => { + throw new Error('datastore is unavailable') + } + : Reflect.get(target, property, target) + }) as FileRegistry + } + + it('adopts an unknown CID only when asked to', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-unknown')) + + const ignored = await confirmStoredFile({ + node, + registry, + unixfs: ifs, + cid, + temporaryTtlMs: TTL + }) + + assert.equal(ignored, undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + + const adopted = await confirmStoredFile({ + node, + registry, + unixfs: ifs, + cid, + registerUnknown: true, + temporaryTtlMs: TTL + }) + + assert.equal(adopted?.state, 'confirmed') + assert.equal(adopted?.pinned, true) + assert.equal(await isDirectlyPinned(node, cid), true) + }) + + it('confirms a temporary file, clears its expiry and pins it', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-temporary')) + await registry.save( + record({ cid: cid.toString(), state: 'temporary', expiresAt: 9000, pinned: false }) + ) + + const confirmed = await confirmStoredFile({ + node, + registry, + unixfs: ifs, + cid, + temporaryTtlMs: TTL, + now: 5000 + }) + + assert.equal(confirmed?.state, 'confirmed') + assert.equal(confirmed?.expiresAt, null) + assert.equal(confirmed?.confirmedAt, 5000) + assert.equal(confirmed?.pinned, true) + assert.equal(await isDirectlyPinned(node, cid), true) + }) + + it('removes the pin it created when confirmation cannot be recorded', async () => { + // A record claiming durability for content the blockstore does not protect + // is the one outcome this must never leave behind + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-fails')) + await registry.save( + record({ cid: cid.toString(), state: 'temporary', expiresAt: 9000, pinned: false }) + ) + + await assert.rejects( + () => + confirmStoredFile({ + node, + registry: unwritable(registry), + unixfs: ifs, + cid, + temporaryTtlMs: TTL + }), + /datastore is unavailable/ + ) + + assert.equal(await isDirectlyPinned(node, cid), false) + assert.equal((await registry.get(cid.toString()))?.state, 'temporary') + }) + + it('releases a registered file and removes its pin', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'release-known')) + await registerPinnedFile({ node, registry, unixfs: ifs, cid, temporaryTtlMs: TTL }) + + const released = await releaseStoredFile({ node, registry, cid }) + + assert.equal(released?.state, 'expired') + assert.equal(released?.pinned, false) + assert.equal(released?.heldLocally, false) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + + it('leaves a CID the registry does not know alone', async () => { + // Startup records every pin, so an unknown CID is either unpinned or not + // reconciled yet — and that window is exactly when unpinning would drop + // protection from content older than the registry + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'release-unknown')) + await pinFile(node, cid) + + assert.equal(await releaseStoredFile({ node, registry, cid }), undefined) + assert.equal(await isDirectlyPinned(node, cid), true) + }) + + it('restores the pin when the release cannot be recorded', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'release-fails')) + await registerPinnedFile({ node, registry, unixfs: ifs, cid, temporaryTtlMs: TTL }) + + await assert.rejects( + () => releaseStoredFile({ node, registry: unwritable(registry), cid }), + /datastore is unavailable/ + ) + + // The record still promises protection, so the protection has to be there + assert.equal((await registry.get(cid.toString()))?.pinned, true) + assert.equal(await isDirectlyPinned(node, cid), true) + }) +}) + describe('registry backfill', () => { it('records a pin that predates the registry, and does so only once', async () => { const registry = createRegistry() diff --git a/test/rollback.test.ts b/test/rollback.test.ts index ebf592c..0b1fed4 100644 --- a/test/rollback.test.ts +++ b/test/rollback.test.ts @@ -61,7 +61,9 @@ describe('rollbackUpload', () => { it('keeps the pin when a record survives the rollback', async () => { const registry = createRegistry() const unpinned: string[] = [] - await registry.register(file, { confirmationRequired: false, temporaryTtlMs: TTL }) + await registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(file, { confirmationRequired: false, temporaryTtlMs: TTL }) + ) await registry.withExclusiveCids([CID_A], async (locked) => { const written = await locked.registerReplacing(file, { @@ -87,8 +89,15 @@ describe('rollbackUpload', () => { it('restores an unpinned previous record and its real pin state', async () => { const registry = createRegistry() const unpinned: string[] = [] - await registry.register(file, { confirmationRequired: false, temporaryTtlMs: TTL }) - await registry.release(CID_A) + // What the storage service leaves behind when a file is released: the + // record survives, unpinned and reclaimable + await registry.withExclusiveCids([CID_A], async (locked) => { + const { record } = await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + await locked.save({ ...record, state: 'expired', pinned: false, heldLocally: false }) + }) await registry.withExclusiveCids([CID_A], async (locked) => { const written = await locked.registerReplacing(file, { From 00530679b21aaf599476debc02f4188c2674f8f2 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Tue, 25 Aug 2026 19:22:41 +0300 Subject: [PATCH 120/167] fix(storage): implement storage operation locking for concurrent uploads and garbage collection --- docs/storage-lifecycle.md | 8 +- src/gc.cron.ts | 60 ++++---- src/middleware/uploadGuards.ts | 100 +++++++------ src/storage/lifecycle.ts | 110 +++++++++++--- src/storage/operationLock.ts | 112 ++++++++++++++ src/storage/service.ts | 110 ++++++++------ src/storage/state.ts | 4 + src/storage/uploadSession.ts | 46 +++--- test/integration/storageLifecycle.test.ts | 172 +++++++++++++++++++++- test/storageOperationLock.test.ts | 64 ++++++++ 10 files changed, 621 insertions(+), 165 deletions(-) create mode 100644 src/storage/operationLock.ts create mode 100644 test/storageOperationLock.test.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index b46d096..3180986 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -191,8 +191,12 @@ blockstore addresses content by multihash and rebuilds a CID with its own defaul codec while listing, so an entry can read differently from the CID a file was uploaded under. -Collection takes the Helia blockstore write lock, so uploads wait while it runs. -Schedule it accordingly. +Intake and pinning hold a process-level shared storage-operation lease from the +first block write or copy through registry commit or cleanup. Collection holds +the exclusive lease for its entire demotion and deletion pass, so it cannot +remove an upload's earlier blocks while the DAG is still unpinned. Once a +collector is queued, later intake waits behind it to prevent collection +starvation. Schedule long forced collections accordingly. ### What a run triggered by free space reclaims diff --git a/src/gc.cron.ts b/src/gc.cron.ts index e7038d8..673e577 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -4,7 +4,7 @@ import { helia } from './helia.js' import { runGarbageCollection, type GcReport } from './storage/gc.js' import { refreshStorageMetrics } from './storage/metrics.js' import { demoteReleasableCopies } from './storage/service.js' -import { fileRegistry } from './storage/state.js' +import { fileRegistry, storageOperationLock } from './storage/state.js' import { logger } from './utils/logger.js' /** A collection pass, plus the copies it handed over to other nodes first. */ @@ -41,39 +41,41 @@ export async function collectGarbage( running = true try { - // Hand copies over before collecting, so the blocks they free are reclaimed - // by the same pass instead of waiting for the next one. - // - // A dry run evaluates them too. A handover unpins a local copy, so a plan - // that leaves handovers out is not the plan the real run follows — and the - // pre-upgrade dry run is read precisely to see which local pins go. It asks - // the same peers the real pass would, and stops short of acting on the - // answer. - const demoted = (await demoteReleasableCopies({ dryRun: options.dryRun })).demoted + return await storageOperationLock.withExclusive(async () => { + // Hand copies over before collecting, so the blocks they free are reclaimed + // by the same pass instead of waiting for the next one. + // + // A dry run evaluates them too. A handover unpins a local copy, so a plan + // that leaves handovers out is not the plan the real run follows — and the + // pre-upgrade dry run is read precisely to see which local pins go. It asks + // the same peers the real pass would, and stops short of acting on the + // answer. + const demoted = (await demoteReleasableCopies({ dryRun: options.dryRun })).demoted - const metrics = await refreshStorageMetrics() + const metrics = await refreshStorageMetrics() - const report = await runGarbageCollection({ - node: helia, - registry: fileRegistry, - watermarks: config.storage.gc, - blockstoreBytes: metrics.blockstoreBytes, - availableBytes: metrics.availableBytes, - reserveBytes: config.storage.diskReserveBytes, - dryRun: options.dryRun, - force: options.force, - pinTimeoutMs: config.replication.requestTimeoutMs, - log: (message) => logger.info(message) - }) + const report = await runGarbageCollection({ + node: helia, + registry: fileRegistry, + watermarks: config.storage.gc, + blockstoreBytes: metrics.blockstoreBytes, + availableBytes: metrics.availableBytes, + reserveBytes: config.storage.diskReserveBytes, + dryRun: options.dryRun, + force: options.force, + pinTimeoutMs: config.replication.requestTimeoutMs, + log: (message) => logger.info(message) + }) - const collection: CollectionReport = { ...report, demoted } + const collection: CollectionReport = { ...report, demoted } - if (!collection.dryRun) { - lastReport = collection - await refreshStorageMetrics() - } + if (!collection.dryRun) { + lastReport = collection + await refreshStorageMetrics() + } - return collection + return collection + }) } finally { running = false } diff --git a/src/middleware/uploadGuards.ts b/src/middleware/uploadGuards.ts index 0cb95bf..6c35921 100644 --- a/src/middleware/uploadGuards.ts +++ b/src/middleware/uploadGuards.ts @@ -3,8 +3,9 @@ import { config } from '../config.js' import { helia } from '../helia.js' import { blockstore, blockstorePath } from '../store.js' import { checkRequestSize, parseContentLength } from '../storage/limits.js' +import type { StorageOperationLease } from '../storage/operationLock.js' import { claimSpace } from '../storage/reservation.js' -import { uploadLimiter } from '../storage/state.js' +import { storageOperationLock, uploadLimiter } from '../storage/state.js' import { UploadSession } from '../storage/uploadSession.js' import { parseCid } from '../utils/cid.js' import { logger } from '../utils/logger.js' @@ -57,6 +58,7 @@ export const admitUpload: RequestHandler = ( let released = false let claimed: { release(): void } | undefined let session: UploadSession | undefined + let operationLease: StorageOperationLease | undefined const release = (): void => { if (released) { return @@ -64,6 +66,13 @@ export const admitUpload: RequestHandler = ( released = true claimed?.release() uploadLimiter.release() + + // An admitted session owns this lease until it has committed or finished + // cleanup. Releasing it on `close` before cleanup would reopen the exact + // window in which GC can delete the session's unpinned blocks. + if (session === undefined || session.isSettled) { + operationLease?.release() + } } // Attached before anything is awaited, and it covers every way a request can @@ -99,49 +108,58 @@ export const admitUpload: RequestHandler = ( return } - availableStorageSize(blockstorePath) - .then((available) => { - // The client is already gone and the slot went back with it. Claiming - // space now would promise it to a request that no longer exists. - if (released) { - return - } - - // A chunked request declares no size, so the most it could write is what - // its aggregate limit allows. Claiming that much keeps the reserve honest - // for a request whose size is unknown, and claiming at all is what stops - // several uploads from each seeing the same free space and passing. - const claim = claimSpace({ - bytes: declaredBytes ?? storage.maxRequestSizeBytes, - availableBytes: Number(available), - reserveBytes: storage.diskReserveBytes - }) + void (async () => { + operationLease = await storageOperationLock.acquireShared() - if (claim === undefined) { - logger.warn( - `Upload rejected: free space ${String(available)} bytes would fall into the ` + - `${storage.diskReserveBytes} byte reserve` - ) - release() - res.status(507).send({ error: 'Insufficient storage' }) - return - } - - session = new UploadSession({ - blockstore: helia.blockstore, - isPinned: (cid) => helia.pins.isPinned(cid), - deleteBlock: (cid) => blockstore.delete(cid), - maxRequestSizeBytes: storage.maxRequestSizeBytes, - parseCid, - onCleanupError: (err) => logger.error(`Upload cleanup error: ${err.message}`) - }) - claimed = claim - ;(req as RequestWithSession)[SESSION_KEY] = session + if (released) { + operationLease.release() + return + } + + const available = await availableStorageSize(blockstorePath) + + // The client is already gone and the slot went back with it. Claiming + // space now would promise it to a request that no longer exists. + if (released) { + operationLease.release() + return + } - next() + // A chunked request declares no size, so the most it could write is what + // its aggregate limit allows. Claiming that much keeps the reserve honest + // for a request whose size is unknown, and claiming at all is what stops + // several uploads from each seeing the same free space and passing. + const claim = claimSpace({ + bytes: declaredBytes ?? storage.maxRequestSizeBytes, + availableBytes: Number(available), + reserveBytes: storage.diskReserveBytes }) - .catch((err) => { + + if (claim === undefined) { + logger.warn( + `Upload rejected: free space ${String(available)} bytes would fall into the ` + + `${storage.diskReserveBytes} byte reserve` + ) release() - next(err) + res.status(507).send({ error: 'Insufficient storage' }) + return + } + + session = new UploadSession({ + blockstore: helia.blockstore, + isPinned: (cid) => helia.pins.isPinned(cid), + deleteBlock: (cid) => blockstore.delete(cid), + maxRequestSizeBytes: storage.maxRequestSizeBytes, + parseCid, + onCleanupError: (err) => logger.error(`Upload cleanup error: ${err.message}`), + onSettle: () => operationLease?.release() }) + claimed = claim + ;(req as RequestWithSession)[SESSION_KEY] = session + + next() + })().catch((err) => { + release() + next(err) + }) } diff --git a/src/storage/lifecycle.ts b/src/storage/lifecycle.ts index 21b9d0b..83066e5 100644 --- a/src/storage/lifecycle.ts +++ b/src/storage/lifecycle.ts @@ -1,7 +1,7 @@ import type { UnixFS } from '@helia/unixfs' import type { CID } from 'multiformats/cid' import type { IpfsNode } from '../ipfs-node.js' -import { pinFile, unpinFile } from './pinning.js' +import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' import type { FileRecord, FileRegistry, LockedFileRegistry } from './registry.js' /** @@ -30,6 +30,61 @@ export interface RegisterPinnedOptions extends LifecycleTarget { pinTimeoutMs?: number } +/** Build the optional deadline shared by one pin and its offline stat. */ +function pinSignal(timeoutMs: number | undefined): AbortSignal | undefined { + return timeoutMs === undefined ? undefined : AbortSignal.timeout(timeoutMs) +} + +/** + * Put both sides of a lifecycle transition back to their observed baseline. + * + * Datastore implementations may reject after persisting a write. Merely + * undoing the pin would then leave the newly written record claiming the + * opposite state. Restore the pin first and the record second while the CID + * lock is still held, and preserve every failure when rollback is incomplete. + */ +async function restoreLifecycleBaseline( + registry: LockedFileRegistry, + node: IpfsNode, + cid: CID, + previous: FileRecord | undefined, + wasPinned: boolean, + originalError: unknown, + signal?: AbortSignal +): Promise { + const rollbackErrors: unknown[] = [] + + try { + if (wasPinned) { + await pinFile(node, cid, signal) + } else { + await unpinFile(node, cid) + } + } catch (err) { + rollbackErrors.push(err) + } + + try { + if (previous === undefined) { + await registry.remove(cid.toString()) + } else { + await registry.save(previous) + } + } catch (err) { + rollbackErrors.push(err) + } + + if (rollbackErrors.length > 0) { + const message = originalError instanceof Error ? originalError.message : String(originalError) + throw new AggregateError( + [originalError, ...rollbackErrors], + `Lifecycle operation failed and rollback was incomplete: ${message}` + ) + } + + throw originalError +} + /** * Pin content and record it as durable, with the CID locked throughout. * @@ -53,11 +108,12 @@ async function registerPinnedUnderLock( options: RegisterPinnedOptions, previous: FileRecord | undefined ): Promise { - const signal = - options.pinTimeoutMs === undefined ? undefined : AbortSignal.timeout(options.pinTimeoutMs) - const createdPin = await pinFile(options.node, options.cid, signal) + const signal = pinSignal(options.pinTimeoutMs) + const wasPinned = previous?.pinned === true || (await isDirectlyPinned(options.node, options.cid)) try { + await pinFile(options.node, options.cid, signal) + // Everything is local after pinning, so the deduplicated DAG size is what // this node actually holds on disk for the file. const stats = await options.unixfs.stat(options.cid, { @@ -78,12 +134,15 @@ async function registerPinnedUnderLock( ) ).record } catch (err) { - // A failed stat or datastore write must not leave a pin no lifecycle owns. - if (createdPin && previous?.pinned !== true) { - await unpinFile(options.node, options.cid) - } - - throw err + return restoreLifecycleBaseline( + registry, + options.node, + options.cid, + previous, + wasPinned, + err, + signal + ) } } @@ -121,9 +180,12 @@ export async function confirmStoredFile(options: ConfirmOptions): Promise void +} + +/** + * Coordinates operations that build or protect DAGs with garbage collection. + * + * Helia serializes GC with one blockstore read or write at a time. A file import + * spans many such writes, though, and its earlier blocks stay unpinned until the + * whole DAG is imported. This process-level reader/writer lock extends the + * critical section from intake through pin, registry commit, or cleanup. + */ +export class StorageOperationLock { + private readers = 0 + private writer = false + private readonly waiters: Waiter[] = [] + + /** Take a shared lease for an upload, copy, or pin operation. */ + acquireShared(): Promise { + if (!this.writer && this.waiters.length === 0) { + this.readers += 1 + return Promise.resolve(this.lease('shared')) + } + + return new Promise((resolve) => this.waiters.push({ mode: 'shared', resolve })) + } + + /** Take the exclusive lease used by one complete collection pass. */ + acquireExclusive(): Promise { + if (!this.writer && this.readers === 0 && this.waiters.length === 0) { + this.writer = true + return Promise.resolve(this.lease('exclusive')) + } + + return new Promise((resolve) => this.waiters.push({ mode: 'exclusive', resolve })) + } + + /** Run one intake or pin operation under a shared lease. */ + async withShared(work: () => Promise): Promise { + const lease = await this.acquireShared() + try { + return await work() + } finally { + lease.release() + } + } + + /** Run one complete collection operation under the exclusive lease. */ + async withExclusive(work: () => Promise): Promise { + const lease = await this.acquireExclusive() + try { + return await work() + } finally { + lease.release() + } + } + + private lease(mode: Waiter['mode']): StorageOperationLease { + let released = false + + return { + release: (): void => { + if (released) { + return + } + released = true + + if (mode === 'shared') { + this.readers = Math.max(0, this.readers - 1) + } else { + this.writer = false + } + + this.drain() + } + } + } + + /** Grant queued work fairly: a waiting collector blocks later readers. */ + private drain(): void { + if (this.writer || this.readers > 0) { + return + } + + const first = this.waiters[0] + if (first === undefined) { + return + } + + if (first.mode === 'exclusive') { + this.waiters.shift() + this.writer = true + first.resolve(this.lease('exclusive')) + return + } + + while (this.waiters[0]?.mode === 'shared') { + const reader = this.waiters.shift() + if (reader === undefined) { + return + } + this.readers += 1 + reader.resolve(this.lease('shared')) + } + } +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 9103af5..8fbf9af 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -40,7 +40,7 @@ import { type TransferProgress } from './meter.js' import { claimedBytes, claimSpace, type Claim } from './reservation.js' -import { fileRegistry, incomingCopyLimiter } from './state.js' +import { fileRegistry, incomingCopyLimiter, storageOperationLock } from './state.js' import { nextSweepBatch } from './sweep.js' const callOptions = (): ReplicationCallOptions => ({ @@ -188,15 +188,17 @@ export async function confirmFile( cid: string, options: { registerUnknown?: boolean } = {} ): Promise { - const confirmed = await confirmStoredFile({ - ...lifecycleTarget(), - unixfs: ifs, - cid: CID.parse(cid), - name: cid, - registerUnknown: options.registerUnknown, - temporaryTtlMs: config.storage.temporaryTtlMs, - pinTimeoutMs: config.replication.requestTimeoutMs - }) + const confirmed = await storageOperationLock.withShared(() => + confirmStoredFile({ + ...lifecycleTarget(), + unixfs: ifs, + cid: CID.parse(cid), + name: cid, + registerUnknown: options.registerUnknown, + temporaryTtlMs: config.storage.temporaryTtlMs, + pinTimeoutMs: config.replication.requestTimeoutMs + }) + ) if (!confirmed) { return undefined @@ -234,17 +236,19 @@ export async function releaseFile(cid: string): Promise /** Store a copy requested by another ADAMANT node. */ export async function acceptReplica(cid: string): Promise { - // Pulling the DAG first is what bounds this. Pinning would fetch it too, but - // with no limit on how many transfers run at once, how much space they may - // take together, or how large the content may be. The `accept` probe that - // normally precedes a `store` is an optimisation, not a guarantee: nothing in - // the protocol requires it, and several peers can pass their own probe and - // then transfer at the same time. - await pullUnderIntakeLimits(cid) - - // Every block is local now, so pinning walks the blockstore rather than the - // network. - return registerPinned(CID.parse(cid), cid) + return storageOperationLock.withShared(async () => { + // Pulling the DAG first is what bounds this. Pinning would fetch it too, but + // with no limit on how many transfers run at once, how much space they may + // take together, or how large the content may be. The `accept` probe that + // normally precedes a `store` is an optimisation, not a guarantee: nothing in + // the protocol requires it, and several peers can pass their own probe and + // then transfer at the same time. + await pullUnderIntakeLimits(cid) + + // Every block is local now, so pinning walks the blockstore rather than the + // network. The shared lease prevents collection between these two steps. + return registerPinned(CID.parse(cid), cid) + }) } /** @@ -374,6 +378,11 @@ async function pullUnderIntakeLimits( * @returns Bytes pulled */ export async function cacheFileLocally(cid: string, peerId: string): Promise { + return storageOperationLock.withShared(() => cacheFileLocallyUnderLock(cid, peerId)) +} + +/** Pull one best-effort cache copy while collection is excluded. */ +async function cacheFileLocallyUnderLock(cid: string, peerId: string): Promise { // A copy is held to the same size an upload is, so a node accepts as a copy // what it would accept as a file. Taking the headroom off this instead would // make the two disagree, and quietly: at a 20 MiB request limit a copy would @@ -470,37 +479,40 @@ async function rescueOrphanedFiles( } try { - // Only worth pinning if the whole DAG is still here; an offline stat says - // so without going near the network. - await ifs.stat(cid, { extended: true, offline: true }) - - // The record was chosen before the probes, which take as long as the - // network does. Writing the snapshot back would undo whatever happened to - // the file meanwhile — a re-upload, a release, an updated replica list — - // so the pin and the state change happen together, against the record as - // it is now. - const kept = await fileRegistry.withExclusiveCids([record.cid], async (registry) => { - const current = await registry.get(record.cid) - - if ( - current === undefined || - current.revision !== record.revision || - current.state !== 'confirmed' || - current.heldLocally - ) { - return undefined - } + const kept = await storageOperationLock.withShared(async () => { + // Only worth pinning if the whole DAG is still here; an offline stat says + // so without going near the network. Collection cannot invalidate that + // result before the following pin is committed. + await ifs.stat(cid, { extended: true, offline: true }) + + // The record was chosen before the probes, which take as long as the + // network does. Writing the snapshot back would undo whatever happened to + // the file meanwhile — a re-upload, a release, an updated replica list — + // so the pin and the state change happen together, against the record as + // it is now. + return fileRegistry.withExclusiveCids([record.cid], async (registry) => { + const current = await registry.get(record.cid) + + if ( + current === undefined || + current.revision !== record.revision || + current.state !== 'confirmed' || + current.heldLocally + ) { + return undefined + } - const createdPin = await pinFile(helia, cid) + const createdPin = await pinFile(helia, cid) - try { - return await registry.save({ ...current, pinned: true, heldLocally: true }) - } catch (err) { - if (createdPin && current.pinned !== true) { - await unpinFile(helia, cid) + try { + return await registry.save({ ...current, pinned: true, heldLocally: true }) + } catch (err) { + if (createdPin && current.pinned !== true) { + await unpinFile(helia, cid) + } + throw err } - throw err - } + }) }) if (kept === undefined) { diff --git a/src/storage/state.ts b/src/storage/state.ts index 298c7eb..b8a8a0a 100644 --- a/src/storage/state.ts +++ b/src/storage/state.ts @@ -1,6 +1,7 @@ import { config } from '../config.js' import { datastore } from '../store.js' import { ConcurrencyLimiter } from './limits.js' +import { StorageOperationLock } from './operationLock.js' import { FileRegistry } from './registry.js' /** @@ -9,6 +10,9 @@ import { FileRegistry } from './registry.js' */ export const fileRegistry = new FileRegistry(datastore) +/** Keeps collection outside write-to-pin and copy-to-pin critical sections. */ +export const storageOperationLock = new StorageOperationLock() + /** Guards how many uploads may write into the blockstore at the same time. */ export const uploadLimiter = new ConcurrencyLimiter(config.storage.maxConcurrentUploads) diff --git a/src/storage/uploadSession.ts b/src/storage/uploadSession.ts index 6eedfdb..210f94f 100644 --- a/src/storage/uploadSession.ts +++ b/src/storage/uploadSession.ts @@ -120,6 +120,8 @@ export interface UploadSessionOptions { maxRequestSizeBytes: number parseCid: (cid: string) => CID onCleanupError?: (err: Error) => void + /** Releases the shared storage-operation lease after commit or cleanup. */ + onSettle?: () => void } /** @@ -171,7 +173,11 @@ export class UploadSession { } this.settled = true - this.blockstore.releaseAll() + try { + this.blockstore.releaseAll() + } finally { + this.options.onSettle?.() + } } /** @@ -190,28 +196,32 @@ export class UploadSession { } this.settled = true - const releasable = new Set(this.blockstore.releaseAll()) - let removed = 0 - - for (const key of this.blockstore.created) { - if (!releasable.has(key)) { - continue - } + try { + const releasable = new Set(this.blockstore.releaseAll()) + let removed = 0 - try { - const cid = this.options.parseCid(key) - - if (await this.options.isPinned(cid)) { + for (const key of this.blockstore.created) { + if (!releasable.has(key)) { continue } - await this.options.deleteBlock(cid) - removed += 1 - } catch (err) { - this.options.onCleanupError?.(err as Error) + try { + const cid = this.options.parseCid(key) + + if (await this.options.isPinned(cid)) { + continue + } + + await this.options.deleteBlock(cid) + removed += 1 + } catch (err) { + this.options.onCleanupError?.(err as Error) + } } - } - return removed + return removed + } finally { + this.options.onSettle?.() + } } } diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 04aa874..fbc0f71 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -21,6 +21,7 @@ import { INTAKE_READ_CONCURRENCY, meteredBlocks } from '../../src/storage/meter.js' +import { StorageOperationLock } from '../../src/storage/operationLock.js' import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' import { UploadSession } from '../../src/storage/uploadSession.js' @@ -47,14 +48,18 @@ function createRegistry(): FileRegistry { return new FileRegistry(datastore, `/adm/lifecycle-${prefixCounter}`) } -function createSession(maxRequestSizeBytes = 64 * 1024 * 1024): UploadSession { +function createSession( + maxRequestSizeBytes = 64 * 1024 * 1024, + onSettle?: () => void +): UploadSession { return new UploadSession({ blockstore: node.blockstore, isPinned: (cid) => node.pins.isPinned(cid), deleteBlock: (cid) => blockstore.delete(cid), maxRequestSizeBytes, parseCid: (value) => CID.parse(value), - onCleanupError: (err) => assert.fail(`unexpected cleanup error: ${err.message}`) + onCleanupError: (err) => assert.fail(`unexpected cleanup error: ${err.message}`), + onSettle }) } @@ -187,6 +192,21 @@ describe('upload session cleanup', () => { assert.equal(await blockstore.has(cid), true) }) + it('releases its operation lease once cleanup settles', async () => { + let settled = 0 + const session = createSession(64 * 1024 * 1024, () => { + settled += 1 + }) + await unixfs({ blockstore: session.blockstore }).addBytes( + deterministicBytes(2048, 'cleanup-settles') + ) + + await session.cleanup() + await session.cleanup() + + assert.equal(settled, 1) + }) + it('leaves blocks that already existed before the upload', async () => { const content = deterministicBytes(2048, 'pre-existing') const cid = await ifs.addBytes(content) @@ -233,6 +253,32 @@ describe('upload session cleanup', () => { session.budget.consume(1000) assert.throws(() => session.budget.consume(100), /Request size limit exceeded/) }) + + it('keeps collection outside the import-to-pin window', async () => { + const lock = new StorageOperationLock() + const lease = await lock.acquireShared() + const session = createSession(64 * 1024 * 1024, () => lease.release()) + const cid = await unixfs({ blockstore: session.blockstore }).addBytes( + deterministicBytes(3 * 1024 * 1024, 'gc-race') + ) + let collectionStarted = false + + const collection = lock.withExclusive(async () => { + collectionStarted = true + await node.gc() + }) + + await new Promise((resolve) => setImmediate(resolve)) + assert.equal(collectionStarted, false) + + await pinFile(node, cid) + session.commit() + await collection + + assert.equal(collectionStarted, true) + assert.equal(await isDirectlyPinned(node, cid), true) + assert.equal(await blockstore.has(cid), true) + }) }) describe('garbage collection against fixture data', () => { @@ -649,6 +695,30 @@ describe('lifecycle transitions', () => { }) as FileRegistry } + /** A registry whose next save lands but reports failure to its caller. */ + function failsAfterNextSave(registry: FileRegistry): FileRegistry { + let fail = true + + return new Proxy(registry, { + get: (target, property) => { + if (property !== 'save') { + return Reflect.get(target, property, target) + } + + return async (value: FileRecord): Promise => { + const stored = await target.save(value) + + if (fail) { + fail = false + throw new Error('datastore acknowledgement failed') + } + + return stored + } + } + }) as FileRegistry + } + it('adopts an unknown CID only when asked to', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-unknown')) @@ -701,6 +771,40 @@ describe('lifecycle transitions', () => { assert.equal(await isDirectlyPinned(node, cid), true) }) + it('passes the configured timeout to a known CID pin', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-timeout')) + await registry.save( + record({ cid: cid.toString(), state: 'temporary', expiresAt: 9000, pinned: false }) + ) + let receivedSignal: AbortSignal | undefined + const fakeNode = { + pins: { + get: async (): Promise => { + const error = new Error('not pinned') + error.name = 'NotFoundError' + throw error + }, + add: async function* (_cid: CID, options?: { signal?: AbortSignal }): AsyncGenerator { + receivedSignal = options?.signal + yield cid + } + } + } as unknown as IpfsNode + + await confirmStoredFile({ + node: fakeNode, + registry, + unixfs: ifs, + cid, + temporaryTtlMs: TTL, + pinTimeoutMs: 30_000 + }) + + assert.ok(receivedSignal instanceof AbortSignal) + assert.equal(receivedSignal.aborted, false) + }) + it('removes the pin it created when confirmation cannot be recorded', async () => { // A record claiming durability for content the blockstore does not protect // is the one outcome this must never leave behind @@ -726,6 +830,53 @@ describe('lifecycle transitions', () => { assert.equal((await registry.get(cid.toString()))?.state, 'temporary') }) + it('restores confirmation state when a failed save actually landed', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-after-write')) + await registry.save( + record({ cid: cid.toString(), state: 'temporary', expiresAt: 9000, pinned: false }) + ) + + await assert.rejects( + () => + confirmStoredFile({ + node, + registry: failsAfterNextSave(registry), + unixfs: ifs, + cid, + temporaryTtlMs: TTL + }), + /datastore acknowledgement failed/ + ) + + const restored = await registry.get(cid.toString()) + assert.equal(restored?.state, 'temporary') + assert.equal(restored?.expiresAt, 9000) + assert.equal(restored?.pinned, false) + assert.equal(restored?.heldLocally, true) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + + it('removes a new record when registration failed after writing it', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'register-after-write')) + + await assert.rejects( + () => + registerPinnedFile({ + node, + registry: failsAfterNextSave(registry), + unixfs: ifs, + cid, + temporaryTtlMs: TTL + }), + /datastore acknowledgement failed/ + ) + + assert.equal(await registry.get(cid.toString()), undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + it('releases a registered file and removes its pin', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'release-known')) @@ -765,6 +916,23 @@ describe('lifecycle transitions', () => { assert.equal((await registry.get(cid.toString()))?.pinned, true) assert.equal(await isDirectlyPinned(node, cid), true) }) + + it('restores release state when a failed save actually landed', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(2048, 'release-after-write')) + await registerPinnedFile({ node, registry, unixfs: ifs, cid, temporaryTtlMs: TTL }) + + await assert.rejects( + () => releaseStoredFile({ node, registry: failsAfterNextSave(registry), cid }), + /datastore acknowledgement failed/ + ) + + const restored = await registry.get(cid.toString()) + assert.equal(restored?.state, 'confirmed') + assert.equal(restored?.pinned, true) + assert.equal(restored?.heldLocally, true) + assert.equal(await isDirectlyPinned(node, cid), true) + }) }) describe('registry backfill', () => { diff --git a/test/storageOperationLock.test.ts b/test/storageOperationLock.test.ts new file mode 100644 index 0000000..89b0afa --- /dev/null +++ b/test/storageOperationLock.test.ts @@ -0,0 +1,64 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { StorageOperationLock } from '../src/storage/operationLock.js' + +describe('storage operation lock', () => { + it('allows shared work to overlap and waits to collect', async () => { + const lock = new StorageOperationLock() + const first = await lock.acquireShared() + const second = await lock.acquireShared() + let collectorStarted = false + + const collector = lock.acquireExclusive().then((lease) => { + collectorStarted = true + return lease + }) + + await Promise.resolve() + assert.equal(collectorStarted, false) + + first.release() + await Promise.resolve() + assert.equal(collectorStarted, false) + + second.release() + const collectorLease = await collector + assert.equal(collectorStarted, true) + collectorLease.release() + }) + + it('does not let later intake overtake a queued collector', async () => { + const lock = new StorageOperationLock() + const activeIntake = await lock.acquireShared() + const order: string[] = [] + + const collector = lock.acquireExclusive().then((lease) => { + order.push('collector') + return lease + }) + const laterIntake = lock.acquireShared().then((lease) => { + order.push('intake') + return lease + }) + + activeIntake.release() + const collectorLease = await collector + assert.deepEqual(order, ['collector']) + + collectorLease.release() + const intakeLease = await laterIntake + assert.deepEqual(order, ['collector', 'intake']) + intakeLease.release() + }) + + it('makes releasing a lease idempotent', async () => { + const lock = new StorageOperationLock() + const lease = await lock.acquireShared() + + lease.release() + lease.release() + + const collector = await lock.acquireExclusive() + collector.release() + }) +}) From 2d763c83e5c73e655d230e4956a07722db6c295f Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 25 Aug 2026 20:08:18 +0300 Subject: [PATCH 121/167] fix(storage): refactor garbage collection and introduce collectStorage function with operation locking --- src/gc.cron.ts | 65 ++++++------------ src/storage/collection.ts | 71 +++++++++++++++++++ src/storage/operationLock.ts | 39 ++++++++++- test/integration/storageLifecycle.test.ts | 83 +++++++++++++++++++++++ test/storageOperationLock.test.ts | 25 +++++++ 5 files changed, 238 insertions(+), 45 deletions(-) create mode 100644 src/storage/collection.ts diff --git a/src/gc.cron.ts b/src/gc.cron.ts index 673e577..b2ca50e 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -1,18 +1,12 @@ import { CronJob } from 'cron' import { config } from './config.js' import { helia } from './helia.js' -import { runGarbageCollection, type GcReport } from './storage/gc.js' +import { collectStorage, type CollectionReport } from './storage/collection.js' import { refreshStorageMetrics } from './storage/metrics.js' import { demoteReleasableCopies } from './storage/service.js' import { fileRegistry, storageOperationLock } from './storage/state.js' import { logger } from './utils/logger.js' -/** A collection pass, plus the copies it handed over to other nodes first. */ -export interface CollectionReport extends GcReport { - /** Files whose local copy was released because peers hold enough copies. */ - demoted: string[] -} - let running = false let lastReport: CollectionReport | null = null @@ -24,10 +18,10 @@ export class GarbageCollectionBusyError extends Error { } /** - * Run one collection pass. + * Run one collection pass against this node. * - * Metrics are refreshed first so the watermark decision uses the current - * blockstore size rather than the value cached by the disk usage schedule. + * The pass itself lives in `collectStorage`, which takes what it works on; this + * is the wiring plus the guard that keeps two passes from overlapping. * * @param options.dryRun report the plan without unpinning or deleting anything * @param options.force collect even when the blockstore is below the high watermark @@ -41,41 +35,26 @@ export async function collectGarbage( running = true try { - return await storageOperationLock.withExclusive(async () => { - // Hand copies over before collecting, so the blocks they free are reclaimed - // by the same pass instead of waiting for the next one. - // - // A dry run evaluates them too. A handover unpins a local copy, so a plan - // that leaves handovers out is not the plan the real run follows — and the - // pre-upgrade dry run is read precisely to see which local pins go. It asks - // the same peers the real pass would, and stops short of acting on the - // answer. - const demoted = (await demoteReleasableCopies({ dryRun: options.dryRun })).demoted - - const metrics = await refreshStorageMetrics() - - const report = await runGarbageCollection({ - node: helia, - registry: fileRegistry, - watermarks: config.storage.gc, - blockstoreBytes: metrics.blockstoreBytes, - availableBytes: metrics.availableBytes, - reserveBytes: config.storage.diskReserveBytes, - dryRun: options.dryRun, - force: options.force, - pinTimeoutMs: config.replication.requestTimeoutMs, - log: (message) => logger.info(message) - }) - - const collection: CollectionReport = { ...report, demoted } + const collection = await collectStorage({ + lock: storageOperationLock, + node: helia, + registry: fileRegistry, + watermarks: config.storage.gc, + reserveBytes: config.storage.diskReserveBytes, + pinTimeoutMs: config.replication.requestTimeoutMs, + demote: demoteReleasableCopies, + measure: refreshStorageMetrics, + dryRun: options.dryRun, + force: options.force, + log: (message) => logger.info(message) + }) - if (!collection.dryRun) { - lastReport = collection - await refreshStorageMetrics() - } + if (!collection.dryRun) { + lastReport = collection + await refreshStorageMetrics() + } - return collection - }) + return collection } finally { running = false } diff --git a/src/storage/collection.ts b/src/storage/collection.ts new file mode 100644 index 0000000..65a5607 --- /dev/null +++ b/src/storage/collection.ts @@ -0,0 +1,71 @@ +import type { IpfsNode } from '../ipfs-node.js' +import { runGarbageCollection, type GcReport, type Watermarks } from './gc.js' +import type { StorageOperationLock } from './operationLock.js' +import type { FileRegistry } from './registry.js' + +/** A collection pass, plus the copies it handed over to other nodes first. */ +export interface CollectionReport extends GcReport { + /** Files whose local copy was released because peers hold enough copies. */ + demoted: string[] +} + +export interface CollectStorageOptions { + lock: StorageOperationLock + node: IpfsNode + registry: FileRegistry + watermarks: Watermarks + /** Free bytes uploads must never consume. */ + reserveBytes: number + /** Bounds the DAG walk when a missing pin has to be restored. */ + pinTimeoutMs?: number + /** Hands copies over to their designated holders, and reports which. */ + demote: (options: { dryRun?: boolean }) => Promise<{ demoted: string[] }> + /** Measures the blockstore and the volume it sits on. */ + measure: () => Promise<{ blockstoreBytes: number; availableBytes: number }> + dryRun?: boolean + force?: boolean + log?: (message: string) => void +} + +/** + * Run one collection pass: hand over what belongs elsewhere, then reclaim. + * + * Everything it needs is passed in rather than imported, which is what lets a + * test drive it against a throwaway node and a real lock. The wiring is the + * part worth testing here: this pass must not start while an upload is between + * its first block write and its pin, and that is a property of *taking* the + * exclusive lease, not of the lock in isolation. + * + * The handover runs **before** the lease is taken. It asks every designated + * holder whether it has the file, one candidate after another, and each of + * those questions may wait for a network timeout — a batch of them inside the + * exclusive lease would hold every upload on the node for as long as the + * slowest peer takes to not answer. Nothing it does needs the lease: it unpins + * copies that other nodes already hold, which is work this pass is about to + * reclaim anyway. + */ +export async function collectStorage(options: CollectStorageOptions): Promise { + const { demoted } = await options.demote({ dryRun: options.dryRun }) + + return options.lock.withExclusive(async () => { + // Measured inside the lease so the watermark decision reflects the + // blockstore as it is now, rather than as it was before waiting for an + // upload to finish. + const metrics = await options.measure() + + const report = await runGarbageCollection({ + node: options.node, + registry: options.registry, + watermarks: options.watermarks, + blockstoreBytes: metrics.blockstoreBytes, + availableBytes: metrics.availableBytes, + reserveBytes: options.reserveBytes, + dryRun: options.dryRun, + force: options.force, + pinTimeoutMs: options.pinTimeoutMs, + log: options.log + }) + + return { ...report, demoted } + }) +} diff --git a/src/storage/operationLock.ts b/src/storage/operationLock.ts index 65566cb..6bf333b 100644 --- a/src/storage/operationLock.ts +++ b/src/storage/operationLock.ts @@ -1,3 +1,5 @@ +import { AsyncLocalStorage } from 'node:async_hooks' + /** A held storage-operation lock. Calling `release` more than once is safe. */ export interface StorageOperationLease { release(): void @@ -21,8 +23,28 @@ export class StorageOperationLock { private writer = false private readonly waiters: Waiter[] = [] + /** + * The lease the currently running callback holds, when it took one. + * + * Taking a second lease while holding one is a deadlock, and a quiet one: + * with a collector queued, a shared holder that asks for another shared lease + * waits behind the collector, and the collector waits for the readers to + * reach zero. Neither ever moves, and nothing is logged. + * + * There is no path that does this today. The guard exists so that the next + * one fails loudly at the mistake instead of hanging the request. + * + * It covers work run through {@link withShared} and {@link withExclusive}. A + * lease taken with {@link acquireShared} spans an HTTP request rather than a + * callback, so no asynchronous context can carry it; that is why upload + * admission holds one and this cannot see it. + */ + private readonly held = new AsyncLocalStorage() + /** Take a shared lease for an upload, copy, or pin operation. */ acquireShared(): Promise { + this.refuseReentry('shared') + if (!this.writer && this.waiters.length === 0) { this.readers += 1 return Promise.resolve(this.lease('shared')) @@ -33,6 +55,8 @@ export class StorageOperationLock { /** Take the exclusive lease used by one complete collection pass. */ acquireExclusive(): Promise { + this.refuseReentry('exclusive') + if (!this.writer && this.readers === 0 && this.waiters.length === 0) { this.writer = true return Promise.resolve(this.lease('exclusive')) @@ -45,7 +69,7 @@ export class StorageOperationLock { async withShared(work: () => Promise): Promise { const lease = await this.acquireShared() try { - return await work() + return await this.held.run('shared', work) } finally { lease.release() } @@ -55,12 +79,23 @@ export class StorageOperationLock { async withExclusive(work: () => Promise): Promise { const lease = await this.acquireExclusive() try { - return await work() + return await this.held.run('exclusive', work) } finally { lease.release() } } + private refuseReentry(wanted: Waiter['mode']): void { + const holding = this.held.getStore() + + if (holding !== undefined) { + throw new Error( + `Deadlock avoided: this operation already holds the ${holding} storage lease and ` + + `asked for a ${wanted} one. Do the work inside the lease it already has.` + ) + } + } + private lease(mode: Waiter['mode']): StorageOperationLease { let released = false diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index fbc0f71..6468d67 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -10,6 +10,7 @@ import { fixedSize } from 'ipfs-unixfs-importer/chunker' import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' +import { collectStorage } from '../../src/storage/collection.js' import { runGarbageCollection } from '../../src/storage/gc.js' import { confirmStoredFile, @@ -680,6 +681,88 @@ describe('block metering', () => { }) }) +describe('collection pass', () => { + const watermarks = { highWatermarkBytes: 0, lowWatermarkBytes: 0 } + + function pass( + lock: StorageOperationLock, + registry: FileRegistry, + overrides: Partial[0]> = {} + ): Promise<{ demoted: string[]; collected: boolean }> { + return collectStorage({ + lock, + node, + registry, + watermarks, + reserveBytes: 0, + demote: async () => ({ demoted: [] }), + measure: async () => ({ blockstoreBytes: 0, availableBytes: 1_000_000_000 }), + force: true, + ...overrides + }) + } + + it('waits for an upload that is between its first block and its pin', async () => { + // The lease has to be taken by the pass itself. Testing the lock alone + // leaves the wiring free to disappear without a test noticing. + // + // `measure` runs inside the lease and nowhere else, so whether it has been + // called says exactly whether the pass got in. + const lock = new StorageOperationLock() + const registry = createRegistry() + const lease = await lock.acquireShared() + let entered = false + + const collection = pass(lock, registry, { + measure: async () => { + entered = true + return { blockstoreBytes: 0, availableBytes: 1_000_000_000 } + } + }) + + for (let tick = 0; tick < 10; tick += 1) { + await new Promise((resolve) => setImmediate(resolve)) + } + + assert.equal(entered, false, 'the pass must not start while an upload holds its lease') + + lease.release() + await collection + + assert.equal(entered, true) + }) + + it('asks peers before taking the lease, not while holding it', async () => { + // Handover probes wait for the network. Inside the lease they would hold + // every upload on the node for as long as the slowest peer takes to answer. + const lock = new StorageOperationLock() + const registry = createRegistry() + let heldDuringDemote: boolean | undefined + + await pass(lock, registry, { + demote: async () => { + // A shared lease can only be taken while the pass does not hold the + // exclusive one + const probe = await lock.acquireShared() + heldDuringDemote = true + probe.release() + return { demoted: ['handed-over'] } + } + }) + + assert.equal(heldDuringDemote, true) + }) + + it('reports what the handover released alongside the collection', async () => { + const lock = new StorageOperationLock() + const report = await pass(lock, createRegistry(), { + demote: async () => ({ demoted: ['a', 'b'] }) + }) + + assert.deepEqual(report.demoted, ['a', 'b']) + }) +}) + describe('lifecycle transitions', () => { const TTL = 60_000 diff --git a/test/storageOperationLock.test.ts b/test/storageOperationLock.test.ts index 89b0afa..3c76237 100644 --- a/test/storageOperationLock.test.ts +++ b/test/storageOperationLock.test.ts @@ -51,6 +51,31 @@ describe('storage operation lock', () => { intakeLease.release() }) + it('refuses a second lease to work that already holds one', async () => { + // With a collector queued, a shared holder asking for another shared lease + // waits behind the collector while the collector waits for the readers to + // reach zero. Nothing moves and nothing is logged, so the guard makes the + // mistake fail where it is made. + const lock = new StorageOperationLock() + + await assert.rejects( + () => lock.withShared(async () => lock.withShared(async () => undefined)), + /Deadlock avoided/ + ) + + await assert.rejects( + () => lock.withShared(async () => lock.withExclusive(async () => undefined)), + /Deadlock avoided/ + ) + }) + + it('lets sequential leases through after the first one is given back', async () => { + const lock = new StorageOperationLock() + + await lock.withShared(async () => undefined) + await assert.doesNotReject(() => lock.withExclusive(async () => undefined)) + }) + it('makes releasing a lease idempotent', async () => { const lock = new StorageOperationLock() const lease = await lock.acquireShared() From 8cd03a95094de9b62502f800556338d8b893ce90 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Wed, 26 Aug 2026 00:31:06 +0300 Subject: [PATCH 122/167] fix(storage): enhance storage lifecycle management with improved garbage collection and upload admission handling --- docs/storage-lifecycle.md | 14 +- src/index.ts | 2 +- src/middleware/uploadAdmission.ts | 179 +++++++++++++++++ src/middleware/uploadGuards.ts | 175 ++--------------- src/storage/collection.ts | 39 ++-- src/storage/config.ts | 6 +- src/storage/gc.ts | 43 +++-- src/storage/operationLock.ts | 4 +- src/storage/registry.ts | 96 +++++++--- test/fileRegistry.test.ts | 88 +++++++++ test/integration/storageLifecycle.test.ts | 224 +++++++++++++++++++++- 11 files changed, 633 insertions(+), 237 deletions(-) create mode 100644 src/middleware/uploadAdmission.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 3180986..ce09737 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -180,8 +180,8 @@ pinned block. Before deleting anything, the collector verifies that every confirmed file it holds is still protected and restores a missing pin first. `storage.gc.enabled` controls the **scheduled** collector. It is on by default, -which costs nothing while there is room: the collector releases what the rules -say, and frees bytes only under the pressure described above. The authorized +but frees no bytes while there is room: the collector releases what the rules +say, and reclaims blocks only under the pressure described above. The authorized `POST /api/storage/gc` endpoint always runs on demand, `?dryRun=true` reports the exact CIDs that would be released and retained without touching a block, and `?force=true` frees them regardless of pressure. @@ -192,10 +192,12 @@ codec while listing, so an entry can read differently from the CID a file was uploaded under. Intake and pinning hold a process-level shared storage-operation lease from the -first block write or copy through registry commit or cleanup. Collection holds -the exclusive lease for its entire demotion and deletion pass, so it cannot -remove an upload's earlier blocks while the DAG is still unpinned. Once a -collector is queued, later intake waits behind it to prevent collection +first block write or copy through registry commit or cleanup. Handover, storage +measurement, registry planning and dry runs stay outside the exclusive lease; +they do not delete blocks and may scale with the existing corpus. Only Helia's +destructive GC holds the exclusive lease, so it waits for every unpinned upload +to commit or finish cleanup without blocking new uploads during full scans. +Once deletion is queued, later intake waits behind it to prevent collection starvation. Schedule long forced collections accordingly. ### What a run triggered by free space reclaims diff --git a/src/index.ts b/src/index.ts index ea2c2f3..8142385 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,7 @@ await registerReplicationProtocol(helia, createReplicationHandlers()) function startLifecycleJobs(): void { // The collector frees blocks only when space is short: above the high // watermark or once free space falls into the disk reserve. It never selects - // a confirmed file this node holds, so leaving it on costs nothing while + // a confirmed file this node holds, so leaving it on frees nothing while // there is room. if (config.storage.gc.enabled) { logger.info( diff --git a/src/middleware/uploadAdmission.ts b/src/middleware/uploadAdmission.ts new file mode 100644 index 0000000..9ce0539 --- /dev/null +++ b/src/middleware/uploadAdmission.ts @@ -0,0 +1,179 @@ +import type { NextFunction, Request, RequestHandler, Response } from 'express' +import type { Blockstore } from 'interface-blockstore' +import type { CID } from 'multiformats/cid' +import { checkRequestSize, parseContentLength } from '../storage/limits.js' +import type { StorageOperationLease, StorageOperationLock } from '../storage/operationLock.js' +import { claimSpace } from '../storage/reservation.js' +import type { StorageConfig } from '../storage/config.js' +import { UploadSession } from '../storage/uploadSession.js' + +const SESSION_KEY = Symbol('uploadSession') + +type RequestWithSession = Request & { [SESSION_KEY]?: UploadSession } + +interface UploadConcurrencyLimiter { + tryAcquire(): boolean + release(): void +} + +interface UploadAdmissionLog { + info(message: string): void + warn(message: string): void + error(message: string): void +} + +export interface UploadAdmissionDependencies { + storage: Pick + limiter: UploadConcurrencyLimiter + operationLock: Pick + availableStorageSize: () => Promise + blockstore: Blockstore + isPinned: (cid: CID) => Promise + deleteBlock: (cid: CID) => Promise + parseCid: (cid: string) => CID + log: UploadAdmissionLog +} + +/** + * Session of the current upload request. + * + * Throws when called outside an admitted upload, which would otherwise let + * blocks be written with nobody owning their cleanup. + */ +export function getUploadSession(req: Request): UploadSession { + const session = (req as RequestWithSession)[SESSION_KEY] + + if (!session) { + throw new Error('Upload session is missing. The upload guards did not run for this request.') + } + + return session +} + +/** + * Build admission control for uploads from explicit storage dependencies. + * + * Runs before the multipart parser so a request that cannot be stored is + * refused before any block reaches the blockstore. The returned middleware + * holds a shared storage-operation lease through pin/commit or cleanup, which + * keeps destructive GC outside the import-to-pin window. + */ +export function createUploadAdmission(dependencies: UploadAdmissionDependencies): RequestHandler { + return (req: Request, res: Response, next: NextFunction): void => { + const { storage } = dependencies + + if (!dependencies.limiter.tryAcquire()) { + res.set('Retry-After', '5') + res.status(429).send({ error: 'Too many concurrent uploads. Please try again later.' }) + return + } + + let released = false + let claimed: { release(): void } | undefined + let session: UploadSession | undefined + let operationLease: StorageOperationLease | undefined + const release = (): void => { + if (released) { + return + } + released = true + claimed?.release() + dependencies.limiter.release() + + // An admitted session owns this lease until it has committed or finished + // cleanup. Releasing it on `close` before cleanup would reopen the exact + // window in which GC can delete the session's unpinned blocks. + if (session === undefined || session.isSettled) { + operationLease?.release() + } + } + + // Attached before anything is awaited, and it covers every way a request can + // end without a durable result: a parser abort, a route failure, or a client + // that disconnected mid-upload. + // + // A client that hangs up while free space is being measured fires `close` + // inside that window. A listener added afterwards would never run, and the + // upload slot and the space claim would stay taken until the process + // restarts — which anyone could trigger, repeatedly, just by disconnecting. + res.on('close', () => { + release() + + if (session === undefined || session.isSettled) { + return + } + + session + .cleanup() + .then((removed) => { + if (removed > 0) { + dependencies.log.info(`Removed ${removed} blocks left by an unfinished upload`) + } + }) + .catch((err) => dependencies.log.error(`Upload cleanup failed: ${(err as Error).message}`)) + }) + + const declaredBytes = parseContentLength(req.headers['content-length']) + + if (!checkRequestSize(declaredBytes, storage.maxRequestSizeBytes).allowed) { + release() + res.status(413).send({ error: 'Upload size limit exceeded' }) + return + } + + void (async () => { + operationLease = await dependencies.operationLock.acquireShared() + + if (released) { + operationLease.release() + return + } + + const available = await dependencies.availableStorageSize() + + // The client is already gone and the slot went back with it. Claiming + // space now would promise it to a request that no longer exists. + if (released) { + operationLease.release() + return + } + + // A chunked request declares no size, so the most it could write is what + // its aggregate limit allows. Claiming that much keeps the reserve honest + // for a request whose size is unknown, and claiming at all is what stops + // several uploads from each seeing the same free space and passing. + const claim = claimSpace({ + bytes: declaredBytes ?? storage.maxRequestSizeBytes, + availableBytes: Number(available), + reserveBytes: storage.diskReserveBytes + }) + + if (claim === undefined) { + dependencies.log.warn( + `Upload rejected: free space ${String(available)} bytes would fall into the ` + + `${storage.diskReserveBytes} byte reserve` + ) + release() + res.status(507).send({ error: 'Insufficient storage' }) + return + } + + session = new UploadSession({ + blockstore: dependencies.blockstore, + isPinned: dependencies.isPinned, + deleteBlock: dependencies.deleteBlock, + maxRequestSizeBytes: storage.maxRequestSizeBytes, + parseCid: dependencies.parseCid, + onCleanupError: (err) => dependencies.log.error(`Upload cleanup error: ${err.message}`), + onSettle: () => operationLease?.release() + }) + claimed = claim + ;(req as RequestWithSession)[SESSION_KEY] = session + + next() + })().catch((err) => { + release() + next(err) + }) + } +} diff --git a/src/middleware/uploadGuards.ts b/src/middleware/uploadGuards.ts index 6c35921..13fcd30 100644 --- a/src/middleware/uploadGuards.ts +++ b/src/middleware/uploadGuards.ts @@ -1,165 +1,24 @@ -import type { NextFunction, Request, RequestHandler, Response } from 'express' +import type { RequestHandler } from 'express' import { config } from '../config.js' import { helia } from '../helia.js' import { blockstore, blockstorePath } from '../store.js' -import { checkRequestSize, parseContentLength } from '../storage/limits.js' -import type { StorageOperationLease } from '../storage/operationLock.js' -import { claimSpace } from '../storage/reservation.js' import { storageOperationLock, uploadLimiter } from '../storage/state.js' -import { UploadSession } from '../storage/uploadSession.js' import { parseCid } from '../utils/cid.js' import { logger } from '../utils/logger.js' import { availableStorageSize } from '../utils/utils.js' - -const SESSION_KEY = Symbol('uploadSession') - -type RequestWithSession = Request & { [SESSION_KEY]?: UploadSession } - -/** - * Session of the current upload request. - * - * Throws when called outside an admitted upload, which would otherwise let - * blocks be written with nobody owning their cleanup. - */ -export function getUploadSession(req: Request): UploadSession { - const session = (req as RequestWithSession)[SESSION_KEY] - - if (!session) { - throw new Error('Upload session is missing. The upload guards did not run for this request.') - } - - return session -} - -/** - * Admission control for uploads. - * - * Runs before the multipart parser so that a request which cannot be stored is - * refused before any block reaches the blockstore. It enforces the concurrent - * upload limit, the aggregate request size and the disk reserve, then attaches - * the session that owns cleanup for whatever the request writes. - * - * The per-file size and the file count are enforced by the parser itself; see - * `createMultipartLimits`. - */ -export const admitUpload: RequestHandler = ( - req: Request, - res: Response, - next: NextFunction -): void => { - const { storage } = config - - if (!uploadLimiter.tryAcquire()) { - res.set('Retry-After', '5') - res.status(429).send({ error: 'Too many concurrent uploads. Please try again later.' }) - return - } - - let released = false - let claimed: { release(): void } | undefined - let session: UploadSession | undefined - let operationLease: StorageOperationLease | undefined - const release = (): void => { - if (released) { - return - } - released = true - claimed?.release() - uploadLimiter.release() - - // An admitted session owns this lease until it has committed or finished - // cleanup. Releasing it on `close` before cleanup would reopen the exact - // window in which GC can delete the session's unpinned blocks. - if (session === undefined || session.isSettled) { - operationLease?.release() - } - } - - // Attached before anything is awaited, and it covers every way a request can - // end without a durable result: a parser abort, a route failure, or a client - // that disconnected mid-upload. - // - // A client that hangs up while free space is being measured fires `close` - // inside that window. A listener added afterwards would never run, and the - // upload slot and the space claim would stay taken until the process - // restarts — which anyone could trigger, repeatedly, just by disconnecting. - res.on('close', () => { - release() - - if (session === undefined || session.isSettled) { - return - } - - session - .cleanup() - .then((removed) => { - if (removed > 0) { - logger.info(`Removed ${removed} blocks left by an unfinished upload`) - } - }) - .catch((err) => logger.error(`Upload cleanup failed: ${(err as Error).message}`)) - }) - - const declaredBytes = parseContentLength(req.headers['content-length']) - - if (!checkRequestSize(declaredBytes, storage.maxRequestSizeBytes).allowed) { - release() - res.status(413).send({ error: 'Upload size limit exceeded' }) - return - } - - void (async () => { - operationLease = await storageOperationLock.acquireShared() - - if (released) { - operationLease.release() - return - } - - const available = await availableStorageSize(blockstorePath) - - // The client is already gone and the slot went back with it. Claiming - // space now would promise it to a request that no longer exists. - if (released) { - operationLease.release() - return - } - - // A chunked request declares no size, so the most it could write is what - // its aggregate limit allows. Claiming that much keeps the reserve honest - // for a request whose size is unknown, and claiming at all is what stops - // several uploads from each seeing the same free space and passing. - const claim = claimSpace({ - bytes: declaredBytes ?? storage.maxRequestSizeBytes, - availableBytes: Number(available), - reserveBytes: storage.diskReserveBytes - }) - - if (claim === undefined) { - logger.warn( - `Upload rejected: free space ${String(available)} bytes would fall into the ` + - `${storage.diskReserveBytes} byte reserve` - ) - release() - res.status(507).send({ error: 'Insufficient storage' }) - return - } - - session = new UploadSession({ - blockstore: helia.blockstore, - isPinned: (cid) => helia.pins.isPinned(cid), - deleteBlock: (cid) => blockstore.delete(cid), - maxRequestSizeBytes: storage.maxRequestSizeBytes, - parseCid, - onCleanupError: (err) => logger.error(`Upload cleanup error: ${err.message}`), - onSettle: () => operationLease?.release() - }) - claimed = claim - ;(req as RequestWithSession)[SESSION_KEY] = session - - next() - })().catch((err) => { - release() - next(err) - }) -} +import { createUploadAdmission } from './uploadAdmission.js' + +export { getUploadSession } from './uploadAdmission.js' + +/** Production upload admission wired to this node's stores and limits. */ +export const admitUpload: RequestHandler = createUploadAdmission({ + storage: config.storage, + limiter: uploadLimiter, + operationLock: storageOperationLock, + availableStorageSize: () => availableStorageSize(blockstorePath), + blockstore: helia.blockstore, + isPinned: (cid) => helia.pins.isPinned(cid), + deleteBlock: (cid) => blockstore.delete(cid), + parseCid, + log: logger +}) diff --git a/src/storage/collection.ts b/src/storage/collection.ts index 65a5607..bfe184d 100644 --- a/src/storage/collection.ts +++ b/src/storage/collection.ts @@ -32,9 +32,9 @@ export interface CollectStorageOptions { * * Everything it needs is passed in rather than imported, which is what lets a * test drive it against a throwaway node and a real lock. The wiring is the - * part worth testing here: this pass must not start while an upload is between + * part worth testing here: Helia GC must not start while an upload is between * its first block write and its pin, and that is a property of *taking* the - * exclusive lease, not of the lock in isolation. + * exclusive lease at deletion, not of the lock in isolation. * * The handover runs **before** the lease is taken. It asks every designated * holder whether it has the file, one candidate after another, and each of @@ -46,26 +46,21 @@ export interface CollectStorageOptions { */ export async function collectStorage(options: CollectStorageOptions): Promise { const { demoted } = await options.demote({ dryRun: options.dryRun }) + const metrics = await options.measure() - return options.lock.withExclusive(async () => { - // Measured inside the lease so the watermark decision reflects the - // blockstore as it is now, rather than as it was before waiting for an - // upload to finish. - const metrics = await options.measure() - - const report = await runGarbageCollection({ - node: options.node, - registry: options.registry, - watermarks: options.watermarks, - blockstoreBytes: metrics.blockstoreBytes, - availableBytes: metrics.availableBytes, - reserveBytes: options.reserveBytes, - dryRun: options.dryRun, - force: options.force, - pinTimeoutMs: options.pinTimeoutMs, - log: options.log - }) - - return { ...report, demoted } + const report = await runGarbageCollection({ + node: options.node, + registry: options.registry, + watermarks: options.watermarks, + blockstoreBytes: metrics.blockstoreBytes, + availableBytes: metrics.availableBytes, + reserveBytes: options.reserveBytes, + dryRun: options.dryRun, + force: options.force, + pinTimeoutMs: options.pinTimeoutMs, + withCollectionLease: (work) => options.lock.withExclusive(work), + log: options.log }) + + return { ...report, demoted } } diff --git a/src/storage/config.ts b/src/storage/config.ts index 53e6bcb..e3983c1 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -15,9 +15,9 @@ export interface GarbageCollectionConfig { /** * Whether the scheduled collector runs. The admin endpoint works regardless. * - * The collector frees blocks only when space is short, so leaving it on costs - * nothing until the blockstore passes `highWatermarkBytes` or free space - * falls into `storage.diskReserveBytes`. + * The collector frees blocks only when space is short: after the blockstore + * passes `highWatermarkBytes` or free space falls into + * `storage.diskReserveBytes`. */ enabled: boolean /** Collection schedule in cron format. */ diff --git a/src/storage/gc.ts b/src/storage/gc.ts index eb5b10a..ee5db1e 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -188,6 +188,8 @@ export interface GcRunOptions { force?: boolean /** Bounds the DAG walk when a missing pin has to be restored. */ pinTimeoutMs?: number + /** Excludes in-flight unpinned writes while Helia deletes blocks. */ + withCollectionLease?: (work: () => Promise) => Promise now?: number log?: (message: string) => void } @@ -377,21 +379,33 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { - if (event.type === 'helia:gc:deleted') { - report.removedBlocks += 1 - if (report.removedCids.length < MAX_REPORTED_CIDS) { - report.removedCids.push(event.detail.toString()) + const collectBlocks = async (): Promise => { + await options.node.gc({ + onProgress: (event) => { + if (event.type === 'helia:gc:deleted') { + report.removedBlocks += 1 + if (report.removedCids.length < MAX_REPORTED_CIDS) { + report.removedCids.push(event.detail.toString()) + } } - } - if (event.type === 'helia:gc:error') { - deletionErrors += 1 - errors.push(String(event.detail)) + if (event.type === 'helia:gc:error') { + deletionErrors += 1 + errors.push(String(event.detail)) + } } - } - }) + }) + } + + // Planning, registry scans and pin verification do not delete blocks and can + // safely overlap uploads. Only Helia GC needs the exclusive lease: it waits + // for every import-to-pin session to settle before inspecting unpinned + // blocks, without holding new uploads behind full-corpus scans or dry runs. + if (options.withCollectionLease === undefined) { + await collectBlocks() + } else { + await options.withCollectionLease(collectBlocks) + } // Helia resolves even when it could not delete everything. Which file a // surviving block belongs to is unknowable — the blockstore addresses content @@ -425,10 +439,11 @@ export async function runGarbageCollection(options: GcRunOptions): Promise this.waiters.push({ mode: 'shared', resolve })) } - /** Take the exclusive lease used by one complete collection pass. */ + /** Take the exclusive lease used while Helia deletes unpinned blocks. */ acquireExclusive(): Promise { this.refuseReentry('exclusive') @@ -75,7 +75,7 @@ export class StorageOperationLock { } } - /** Run one complete collection operation under the exclusive lease. */ + /** Run Helia block deletion under the exclusive lease. */ async withExclusive(work: () => Promise): Promise { const lease = await this.acquireExclusive() try { diff --git a/src/storage/registry.ts b/src/storage/registry.ts index b8e8b70..c6292e7 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -1,3 +1,4 @@ +import { randomUUID } from 'node:crypto' import { AsyncLocalStorage } from 'node:async_hooks' import { Key } from 'interface-datastore' import type { Datastore, Pair } from 'interface-datastore' @@ -11,15 +12,6 @@ export const REGISTRY_PREFIX = '/adm/files' const encoder = new TextEncoder() const decoder = new TextDecoder() -/** - * Stamps every stored record, so two writes are never mistaken for one. - * - * A counter carried on the record itself would reset whenever a writer builds a - * fresh record rather than editing the stored one, which is exactly what - * registering an already-known CID does. - */ -let writes = 0 - /** Lifecycle state of a file known to this node. */ export type FileState = 'temporary' | 'confirmed' | 'expired' @@ -56,11 +48,61 @@ export interface FileRecord { * all do network or blockstore work after selecting a record; without this * stamp they could apply that stale decision to a re-uploaded CID. * - * It is unique within a process run and means nothing across restarts, which - * is all it is compared over: a caller only ever checks a record against a - * stamp it was handed by its own write. + * New writes use UUIDs so a record selected before a restart cannot be + * mistaken for a different write made afterwards. Numbers remain accepted + * for records written by earlier versions. */ - revision?: number + revision?: string | number +} + +function isFiniteNonNegative(value: unknown): value is number { + return typeof value === 'number' && Number.isFinite(value) && value >= 0 +} + +function isNullableTimestamp(value: unknown): value is number | null { + return value === null || isFiniteNonNegative(value) +} + +/** Validate the durable shape before lifecycle policy is allowed to trust it. */ +function isFileRecord(value: unknown): value is FileRecord { + if (typeof value !== 'object' || value === null) { + return false + } + + const record = value as Partial + const states: FileState[] = ['temporary', 'confirmed', 'expired'] + const revisionIsValid = + record.revision === undefined || + (typeof record.revision === 'string' && record.revision.length > 0) || + isFiniteNonNegative(record.revision) + + return ( + typeof record.cid === 'string' && + record.cid.length > 0 && + typeof record.name === 'string' && + record.state !== undefined && + states.includes(record.state) && + isFiniteNonNegative(record.createdAt) && + isNullableTimestamp(record.expiresAt) && + isNullableTimestamp(record.confirmedAt) && + isFiniteNonNegative(record.fileSize) && + isFiniteNonNegative(record.storedBytes) && + typeof record.pinned === 'boolean' && + typeof record.heldLocally === 'boolean' && + Array.isArray(record.replicas) && + record.replicas.every((peer) => typeof peer === 'string') && + revisionIsValid + ) +} + +/** Decode a record and ensure it belongs to the datastore key that carried it. */ +function decodeRecord(value: Uint8Array, expectedCid: string): FileRecord | undefined { + try { + const parsed: unknown = JSON.parse(decoder.decode(value)) + return isFileRecord(parsed) && parsed.cid === expectedCid ? parsed : undefined + } catch { + return undefined + } } /** What {@link FileRegistry.transition} should do with a record. */ @@ -276,7 +318,13 @@ export class FileRegistry { async get(cid: string): Promise { try { - return JSON.parse(decoder.decode(await this.datastore.get(this.key(cid)))) as FileRecord + const record = decodeRecord(await this.datastore.get(this.key(cid)), cid) + + if (record === undefined) { + throw new Error(`Invalid lifecycle registry record for ${cid}`) + } + + return record } catch (err) { if (isNotFound(err)) { return undefined @@ -286,8 +334,7 @@ export class FileRegistry { } async save(record: FileRecord): Promise { - writes += 1 - const stored: FileRecord = { ...record, revision: writes } + const stored: FileRecord = { ...record, revision: randomUUID() } await this.datastore.put(this.key(stored.cid), encoder.encode(JSON.stringify(stored))) return stored } @@ -330,13 +377,18 @@ export class FileRegistry { return } - try { - yield JSON.parse(decoder.decode(entry.value.value)) as FileRecord - } catch { - // A corrupted entry must not stop the whole sweep; garbage collection - // ignores it and the blocks stay protected until it is repaired. - continue + const key = entry.value.key.toString() + const keyPrefix = `${this.prefix}/` + const expectedCid = key.startsWith(keyPrefix) ? key.slice(keyPrefix.length) : '' + const record = decodeRecord(entry.value.value, expectedCid) + + if (record !== undefined) { + yield record } + + // A syntactically or structurally corrupted entry must not be treated as + // reclaimable. Ignoring it leaves its Helia pin in force until an + // operator repairs the registry. } } diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index dca0ec9..be37f09 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -4,6 +4,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { after, before, describe, it } from 'node:test' import { FsDatastore } from 'datastore-fs' +import { Key } from 'interface-datastore' import { FileRegistry, isExpired, isReclaimable, type FileRecord } from '../src/storage/registry.js' const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' @@ -53,6 +54,93 @@ after(async () => { }) describe('FileRegistry', () => { + it('does not reuse a revision persisted by a previous process', async () => { + const prefix = '/adm/restart-revision' + const previous: FileRecord = { + cid: CID_A, + name: 'before-restart.jpg', + state: 'temporary', + createdAt: 1, + expiresAt: 2, + confirmedAt: null, + fileSize: 100, + storedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [], + revision: 1 + } + + // Seed the store directly: the in-memory revision generator belongs to the + // new process and has never seen this write. + await datastore.put( + new Key(`${prefix}/${CID_A}`), + new TextEncoder().encode(JSON.stringify(previous)) + ) + + const registry = new FileRegistry(datastore, prefix) + const current = await registry.save({ ...previous, name: 'after-restart.jpg' }) + + assert.notEqual(current.revision, previous.revision) + assert.equal(typeof current.revision, 'string') + }) + + it('fails closed for valid JSON that is not a FileRecord', async () => { + const prefix = '/adm/invalid-record' + const malformed = { + cid: CID_A, + name: 'durable.jpg', + state: 'corrupted', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + fileSize: 100, + storedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [], + revision: 1 + } + + await datastore.put( + new Key(`${prefix}/${CID_A}`), + new TextEncoder().encode(JSON.stringify(malformed)) + ) + + const registry = new FileRegistry(datastore, prefix) + + await assert.rejects(() => registry.get(CID_A), /Invalid lifecycle registry record/) + assert.deepEqual(await registry.all(), []) + }) + + it('rejects a record whose payload names another CID', async () => { + const prefix = '/adm/mismatched-record' + const mismatched: FileRecord = { + cid: CID_B, + name: 'wrong-key.jpg', + state: 'confirmed', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + fileSize: 100, + storedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [], + revision: 'old-process:1' + } + + await datastore.put( + new Key(`${prefix}/${CID_A}`), + new TextEncoder().encode(JSON.stringify(mismatched)) + ) + + const registry = new FileRegistry(datastore, prefix) + + await assert.rejects(() => registry.get(CID_A), /Invalid lifecycle registry record/) + assert.deepEqual(await registry.all(), []) + }) + it('registers an upload as confirmed when confirmation is not required', async () => { const registry = createRegistry() diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 6468d67..9e66ae5 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1,4 +1,5 @@ import assert from 'node:assert/strict' +import { EventEmitter } from 'node:events' import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -6,9 +7,11 @@ import { after, before, describe, it } from 'node:test' import { unixfs, type UnixFS } from '@helia/unixfs' import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' +import { Key } from 'interface-datastore' import { fixedSize } from 'ipfs-unixfs-importer/chunker' import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' +import { createUploadAdmission, getUploadSession } from '../../src/middleware/uploadAdmission.js' import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' import { collectStorage } from '../../src/storage/collection.js' import { runGarbageCollection } from '../../src/storage/gc.js' @@ -22,6 +25,7 @@ import { INTAKE_READ_CONCURRENCY, meteredBlocks } from '../../src/storage/meter.js' +import { ConcurrencyLimiter } from '../../src/storage/limits.js' import { StorageOperationLock } from '../../src/storage/operationLock.js' import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' @@ -64,6 +68,58 @@ function createSession( }) } +class AdmissionResponse extends EventEmitter { + statusCode = 200 + body: unknown + + set(): this { + return this + } + + status(value: number): this { + this.statusCode = value + return this + } + + send(value: unknown): this { + this.body = value + return this + } +} + +/** Admit one request through the same middleware implementation production uses. */ +async function admitThrough(lock: StorageOperationLock): Promise<{ + response: AdmissionResponse + session: UploadSession +}> { + const admission = createUploadAdmission({ + storage: { maxRequestSizeBytes: 64 * 1024 * 1024, diskReserveBytes: 1 }, + limiter: new ConcurrencyLimiter(2), + operationLock: lock, + availableStorageSize: async () => 1024n ** 4n, + blockstore: node.blockstore, + isPinned: (cid) => node.pins.isPinned(cid), + deleteBlock: (cid) => blockstore.delete(cid), + parseCid: (value) => CID.parse(value), + log: { + info: () => undefined, + warn: () => undefined, + error: (message) => assert.fail(String(message)) + } + }) + const request = { headers: { 'content-length': '1048576' } } + const response = new AdmissionResponse() + + await new Promise((resolve, reject) => { + admission(request as never, response as never, (error?: unknown) => + error === undefined ? resolve() : reject(error) + ) + }) + + assert.equal(response.statusCode, 200) + return { response, session: getUploadSession(request as never) } +} + /** Register a file the way an upload does: under the CID lock. */ function registerFile( registry: FileRegistry, @@ -282,6 +338,54 @@ describe('upload session cleanup', () => { }) }) +describe('upload admission wiring', () => { + it('holds the production shared lease until the request commits', async () => { + const lock = new StorageOperationLock() + const admitted = await admitThrough(lock) + let collectionStarted = false + + const collection = lock.withExclusive(async () => { + collectionStarted = true + }) + + for (let tick = 0; tick < 10; tick += 1) { + await new Promise((resolve) => setImmediate(resolve)) + } + assert.equal(collectionStarted, false) + + admitted.session.commit() + await collection + admitted.response.emit('close') + + assert.equal(collectionStarted, true) + }) + + it('keeps the lease through disconnect cleanup and removes unfinished blocks', async () => { + const lock = new StorageOperationLock() + const admitted = await admitThrough(lock) + const cid = await unixfs({ blockstore: admitted.session.blockstore }).addBytes( + deterministicBytes(2 * 1024 * 1024, 'admission-disconnect') + ) + assert.equal(await blockstore.has(cid), true) + + admitted.response.emit('close') + let collectionStarted = false + const collection = lock.withExclusive(async () => { + collectionStarted = true + }) + + for (let tick = 0; tick < 10; tick += 1) { + await new Promise((resolve) => setImmediate(resolve)) + } + assert.equal(collectionStarted, false) + + await collection + assert.equal(admitted.session.isSettled, true) + assert.equal(collectionStarted, true) + assert.equal(await blockstore.has(cid), false) + }) +}) + describe('garbage collection against fixture data', () => { /** * One confirmed file, one abandoned upload, one live upload, and a block that @@ -487,6 +591,60 @@ describe('garbage collection against fixture data', () => { assert.equal((await registry.get(cids.abandoned))?.state, 'expired') }) + it('does not report a clean collection when registry cleanup fails', async () => { + const { registry, cids } = await createFixture() + const cleanupFails = new Proxy(registry, { + get: (target, property) => + property === 'transition' + ? async (): Promise => { + throw new Error('registry cleanup failed') + } + : Reflect.get(target, property, target) + }) as FileRegistry + + const report = await runGarbageCollection({ + node, + registry: cleanupFails, + watermarks: TIGHT, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.equal(report.collected, false) + assert.ok(report.errors.some((message) => message.includes('registry cleanup failed'))) + assert.equal((await registry.get(cids.abandoned))?.state, 'expired') + }) + + it('does not reclaim a syntactically valid but structurally invalid record', async () => { + const prefix = `/adm/invalid-lifecycle-${prefixCounter}` + const cid = await ifs.addBytes(deterministicBytes(2048, 'invalid-lifecycle')) + await pinFile(node, cid) + await datastore.put( + new Key(`${prefix}/${cid.toString()}`), + new TextEncoder().encode( + JSON.stringify({ + ...record({ cid: cid.toString(), state: 'confirmed' }), + state: 'corrupted', + revision: 1 + }) + ) + ) + + const registry = new FileRegistry(datastore, prefix) + const report = await runGarbageCollection({ + node, + registry, + watermarks: TIGHT, + blockstoreBytes: 4096, + now: 1000 + }) + + assert.deepEqual(report.releasedCids, []) + assert.equal(await isDirectlyPinned(node, cid), true) + assert.equal(await blockstore.has(cid), true) + await assert.rejects(() => registry.get(cid.toString()), /Invalid lifecycle registry record/) + }) + it('does not apply a plan the registry has moved past', async () => { const { registry, cids } = await createFixture() @@ -684,6 +842,21 @@ describe('block metering', () => { describe('collection pass', () => { const watermarks = { highWatermarkBytes: 0, lowWatermarkBytes: 0 } + async function withoutExclusiveWait(work: Promise): Promise { + let timer: ReturnType | undefined + const timeout = new Promise((_resolve, reject) => { + timer = setTimeout(() => reject(new Error('collection waited for the shared lease')), 1000) + }) + + try { + return await Promise.race([work, timeout]) + } finally { + if (timer !== undefined) { + clearTimeout(timer) + } + } + } + function pass( lock: StorageOperationLock, registry: FileRegistry, @@ -702,34 +875,67 @@ describe('collection pass', () => { }) } - it('waits for an upload that is between its first block and its pin', async () => { + it('waits for an upload before Helia starts deleting blocks', async () => { // The lease has to be taken by the pass itself. Testing the lock alone - // leaves the wiring free to disappear without a test noticing. - // - // `measure` runs inside the lease and nowhere else, so whether it has been - // called says exactly whether the pass got in. + // leaves the wiring free to disappear without a test noticing. Planning is + // allowed to finish; the injected `gc` marks the exact destructive edge. const lock = new StorageOperationLock() const registry = createRegistry() const lease = await lock.acquireShared() - let entered = false + let deletionStarted = false + let measured: (() => void) | undefined + const measurementFinished = new Promise((resolve) => { + measured = resolve + }) + const collectingNode = { + gc: async (): Promise => { + deletionStarted = true + } + } as IpfsNode const collection = pass(lock, registry, { + node: collectingNode, measure: async () => { - entered = true + measured?.() return { blockstoreBytes: 0, availableBytes: 1_000_000_000 } } }) + await measurementFinished for (let tick = 0; tick < 10; tick += 1) { await new Promise((resolve) => setImmediate(resolve)) } - assert.equal(entered, false, 'the pass must not start while an upload holds its lease') + assert.equal(deletionStarted, false, 'Helia GC must wait while an upload holds its lease') lease.release() await collection - assert.equal(entered, true) + assert.equal(deletionStarted, true) + }) + + it('does not take the exclusive lease for a dry run', async () => { + const lock = new StorageOperationLock() + const lease = await lock.acquireShared() + + try { + const report = await withoutExclusiveWait(pass(lock, createRegistry(), { dryRun: true })) + assert.equal(report.collected, false) + } finally { + lease.release() + } + }) + + it('does not take the exclusive lease when no deletion is needed', async () => { + const lock = new StorageOperationLock() + const lease = await lock.acquireShared() + + try { + const report = await withoutExclusiveWait(pass(lock, createRegistry(), { force: false })) + assert.equal(report.collected, false) + } finally { + lease.release() + } }) it('asks peers before taking the lease, not while holding it', async () => { From 8bf119ad3d9b4a0b70759b254e0d1ab36249f679 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Wed, 26 Aug 2026 01:10:10 +0300 Subject: [PATCH 123/167] feat(upload): implement upload handler for multipart file uploads with improved error handling and session management --- src/api/file.ts | 148 ++----------- src/api/uploadRoute.ts | 177 ++++++++++++++++ src/storage/gc.ts | 16 +- src/storage/registry.ts | 21 ++ src/utils/unixfs-multer.storage.ts | 34 ++- test/fileRegistry.test.ts | 23 +++ test/integration/storageLifecycle.test.ts | 69 +++++++ test/integration/uploadRoute.test.ts | 241 ++++++++++++++++++++++ 8 files changed, 581 insertions(+), 148 deletions(-) create mode 100644 src/api/uploadRoute.ts create mode 100644 test/integration/uploadRoute.test.ts diff --git a/src/api/file.ts b/src/api/file.ts index 004e6dd..ede46a9 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -4,7 +4,6 @@ import { helia } from '../helia.js' import { admitUpload, getUploadSession } from '../middleware/uploadGuards.js' import { readLimiter, uploadLimiter } from '../middleware/rateLimiter.js' import { multerStorage } from '../multer.js' -import { pinFile, unpinFile } from '../storage/pinning.js' import { confirmFile, effectiveQuorum, @@ -12,14 +11,12 @@ import { releaseFile, replicateFile } from '../storage/service.js' -import { rollbackUpload } from '../storage/rollback.js' import { fileRegistry } from '../storage/state.js' +import { createUploadHandler } from './uploadRoute.js' import { parseCid } from '../utils/cid.js' import { sendDownloadStream } from '../utils/downloadResponse.js' import { downloadFile, getFileStats } from '../utils/file.js' import { logger } from '../utils/logger.js' -import { UnixFsMulterFile } from '../utils/types.js' -import { flatFiles } from '../utils/utils.js' const router = Router() @@ -43,138 +40,17 @@ router.post( uploadLimiter, admitUpload, multerStorage.array('files'), - async (req, res, next) => { - if (!Array.isArray(req.files) || req.files.length === 0) { - res.statusCode = 400 - return res.send({ error: 'No file uploaded' }) - } - - const session = getUploadSession(req) - - try { - const files = flatFiles(req.files as UnixFsMulterFile[]) - logger.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) - - // Hold all involved CIDs until this request either commits or restores - // every pin and lifecycle record it changed. Sorting happens inside the - // registry, so overlapping multi-file requests cannot deadlock. - const records = await fileRegistry.withExclusiveCids( - files.map((file) => file.cid.toString()), - async (registry) => { - const undo: Array<() => Promise> = [] - - try { - const stored = [] - - for (const file of files) { - const cid = file.cid.toString() - logger.info(`Successfully added file ${cid}`) - - const previous = await registry.get(cid) - const createdPin = await pinFile(helia, file.cid) - - let registration - try { - registration = await registry.registerReplacing( - { - cid, - name: file.originalname, - fileSize: file.size, - storedBytes: file.storedBytes - }, - { - confirmationRequired: config.storage.confirmationRequired, - temporaryTtlMs: config.storage.temporaryTtlMs - } - ) - } catch (err) { - // A datastore failure can happen before or after it touches the - // key. Restore both sides from the baseline captured under the - // same CID lock, so neither outcome leaks a pin or a record. - await rollbackUpload({ - registry, - cid, - previous, - createdPin, - unpin: async () => { - await unpinFile(helia, file.cid) - } - }) - throw err - } - - undo.push(() => - rollbackUpload({ - registry, - cid, - previous: registration.previous, - createdPin, - unpin: async () => { - await unpinFile(helia, file.cid) - } - }) - ) - - stored.push(registration.record) - } - - // Every file is stored and pinned. Ownership passes from the - // request session to the registry before any CID lock is released. - session.commit() - return stored - } catch (err) { - for (const step of undo.reverse()) { - try { - await step() - } catch (undoError) { - logger.error(`Could not undo a failed upload: ${(undoError as Error).message}`) - } - } - throw err - } - } - ) - - const durable = records.filter((record) => record.state === 'confirmed') - const replication = await Promise.all( - durable.map(async (record) => ({ - cid: record.cid, - report: await replicateFile(record.cid) - })) - ) - const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) - - if ( - config.replication.requireQuorumOnUpload && - replication.some((item) => !item.report.satisfied) - ) { - // The policy demands a quorum, so the upload is not durable. The files are - // released instead of being kept as a copy nobody promised to hold. - await Promise.all(durable.map((record) => releaseFile(record.cid))) - - return res.status(503).send({ error: 'Replication quorum not reached' }) - } - - res.send({ - filesNames: files.map((file) => file.originalname), - cids: records.map((record) => record.cid), - files: records.map((record) => ({ - cid: record.cid, - name: record.name, - state: record.state, - expiresAt: record.expiresAt, - // Placement is decided per CID, so one file of a request can meet its - // quorum while another does not, on different peers. - replication: replicationByCid.get(record.cid) ?? null - })), - // The first file's report, kept for clients written against it. New - // ones should read the per-file field above. - replication: replication[0]?.report ?? null - }) - } catch (err) { - next(err) - } - } + createUploadHandler({ + node: helia, + registry: fileRegistry, + getSession: getUploadSession, + confirmationRequired: config.storage.confirmationRequired, + temporaryTtlMs: config.storage.temporaryTtlMs, + requireQuorumOnUpload: config.replication.requireQuorumOnUpload, + replicate: replicateFile, + release: releaseFile, + log: logger + }) ) /** diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts new file mode 100644 index 0000000..0cd0e81 --- /dev/null +++ b/src/api/uploadRoute.ts @@ -0,0 +1,177 @@ +import type { NextFunction, Request, RequestHandler, Response } from 'express' +import type { IpfsNode } from '../ipfs-node.js' +import { pinFile, unpinFile } from '../storage/pinning.js' +import type { FileRecord, FileRegistry } from '../storage/registry.js' +import type { ReplicationReport } from '../storage/replication.js' +import { rollbackUpload } from '../storage/rollback.js' +import type { UploadSession } from '../storage/uploadSession.js' +import { UnixFsMulterFile } from '../utils/types.js' +import { flatFiles } from '../utils/utils.js' + +interface UploadRouteLog { + info(message: string): void + error(message: string): void +} + +export interface UploadRouteDependencies { + node: IpfsNode + registry: FileRegistry + /** Session that owns the blocks this request wrote. */ + getSession: (req: Request) => UploadSession + /** Whether an upload stays temporary until an authorized confirmation. */ + confirmationRequired: boolean + /** Lifetime of an unconfirmed upload before it becomes reclaimable. */ + temporaryTtlMs: number + /** Whether an upload that missed its quorum is refused rather than kept. */ + requireQuorumOnUpload: boolean + replicate: (cid: string) => Promise + release: (cid: string) => Promise + log: UploadRouteLog +} + +/** + * Build the handler that turns imported parts into durable, registered files. + * + * The parts are already in the blockstore by the time this runs — the multipart + * parser wrote them through the request session — and they are unpinned until + * this handler pins them. That is why it takes what it works on rather than + * importing the node this process happens to have started: the window between + * import and pin is the one thing about uploads worth testing end to end, and a + * handler bound to singletons cannot be driven by a test at all. + */ +export function createUploadHandler(dependencies: UploadRouteDependencies): RequestHandler { + const { node, registry, log } = dependencies + + return async (req: Request, res: Response, next: NextFunction) => { + if (!Array.isArray(req.files) || req.files.length === 0) { + res.statusCode = 400 + return res.send({ error: 'No file uploaded' }) + } + + const session = dependencies.getSession(req) + + try { + const files = flatFiles(req.files as UnixFsMulterFile[]) + log.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) + + // Hold all involved CIDs until this request either commits or restores + // every pin and lifecycle record it changed. Sorting happens inside the + // registry, so overlapping multi-file requests cannot deadlock. + const records = await registry.withExclusiveCids( + files.map((file) => file.cid.toString()), + async (locked) => { + const undo: Array<() => Promise> = [] + + try { + const stored = [] + + for (const file of files) { + const cid = file.cid.toString() + log.info(`Successfully added file ${cid}`) + + const previous = await locked.get(cid) + const createdPin = await pinFile(node, file.cid) + + let registration + try { + registration = await locked.registerReplacing( + { + cid, + name: file.originalname, + fileSize: file.size, + storedBytes: file.storedBytes + }, + { + confirmationRequired: dependencies.confirmationRequired, + temporaryTtlMs: dependencies.temporaryTtlMs + } + ) + } catch (err) { + // A datastore failure can happen before or after it touches the + // key. Restore both sides from the baseline captured under the + // same CID lock, so neither outcome leaks a pin or a record. + await rollbackUpload({ + registry: locked, + cid, + previous, + createdPin, + unpin: async () => { + await unpinFile(node, file.cid) + } + }) + throw err + } + + undo.push(() => + rollbackUpload({ + registry: locked, + cid, + previous: registration.previous, + createdPin, + unpin: async () => { + await unpinFile(node, file.cid) + } + }) + ) + + stored.push(registration.record) + } + + // Every file is stored and pinned. Ownership passes from the + // request session to the registry before any CID lock is released. + session.commit() + return stored + } catch (err) { + for (const step of undo.reverse()) { + try { + await step() + } catch (undoError) { + log.error(`Could not undo a failed upload: ${(undoError as Error).message}`) + } + } + throw err + } + } + ) + + const durable = records.filter((record) => record.state === 'confirmed') + const replication = await Promise.all( + durable.map(async (record) => ({ + cid: record.cid, + report: await dependencies.replicate(record.cid) + })) + ) + const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) + + if ( + dependencies.requireQuorumOnUpload && + replication.some((item) => !item.report.satisfied) + ) { + // The policy demands a quorum, so the upload is not durable. The files + // are released instead of being kept as a copy nobody promised to hold. + await Promise.all(durable.map((record) => dependencies.release(record.cid))) + + return res.status(503).send({ error: 'Replication quorum not reached' }) + } + + res.send({ + filesNames: files.map((file) => file.originalname), + cids: records.map((record) => record.cid), + files: records.map((record) => ({ + cid: record.cid, + name: record.name, + state: record.state, + expiresAt: record.expiresAt, + // Placement is decided per CID, so one file of a request can meet its + // quorum while another does not, on different peers. + replication: replicationByCid.get(record.cid) ?? null + })), + // The first file's report, kept for clients written against it. New + // ones should read the per-file field above. + replication: replication[0]?.report ?? null + }) + } catch (err) { + next(err) + } + } +} diff --git a/src/storage/gc.ts b/src/storage/gc.ts index ee5db1e..bd29c08 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -188,8 +188,14 @@ export interface GcRunOptions { force?: boolean /** Bounds the DAG walk when a missing pin has to be restored. */ pinTimeoutMs?: number - /** Excludes in-flight unpinned writes while Helia deletes blocks. */ - withCollectionLease?: (work: () => Promise) => Promise + /** + * Excludes in-flight unpinned writes while Helia deletes blocks. + * + * Required rather than optional: a caller that forgets it would delete blocks + * with no lease at all, and nothing would say so. A caller that genuinely has + * no concurrent writers passes a function that just runs the work. + */ + withCollectionLease: (work: () => Promise) => Promise now?: number log?: (message: string) => void } @@ -401,11 +407,7 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { try { const record = decodeRecord(await this.datastore.get(this.key(cid)), cid) @@ -335,6 +345,17 @@ export class FileRegistry { async save(record: FileRecord): Promise { const stored: FileRecord = { ...record, revision: randomUUID() } + + // Checked on the way in as well as on the way out. Validating reads alone + // lets a record be persisted that can then never be read: an absent number + // survives `JSON.stringify` as a missing key, and the file it describes + // disappears from the report, from repair and from every plan while its + // pin quietly keeps the blocks. Refusing the write puts the failure where + // the mistake is. + if (!isFileRecord(stored)) { + throw new Error(`Refusing to store an invalid lifecycle record for ${record.cid}`) + } + await this.datastore.put(this.key(stored.cid), encoder.encode(JSON.stringify(stored))) return stored } diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index f1bd8d6..4632e82 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -13,18 +13,32 @@ export interface UnixfsStorageOptions { getSession: (req: e.Request) => UploadSession } +/** A part stream being metered, and how much of it has gone by. */ +interface MeteredStream { + stream: Readable + /** Bytes of this part that reached the importer. */ + readonly bytes: number +} + /** * Wrap a part stream so that the aggregate request budget is enforced while the - * bytes flow. + * bytes flow, and count the part itself. * * `Content-Length` is checked before parsing starts, but a chunked request * declares no size, so the limit is applied here as well. + * + * The per-part count is the file's own size. Nothing else knows it: multer + * fills `size` only for engines that report it, and the blocks written are not + * the same number — re-uploading content this node already holds writes none at + * all. */ -function budgeted(stream: Readable, budget: RequestSizeBudget): Readable { +function budgeted(stream: Readable, budget: RequestSizeBudget): MeteredStream { + let bytes = 0 const meter = new Transform({ transform(chunk: Buffer, encoding, callback) { try { budget.consume(chunk.length) + bytes += chunk.length callback(null, chunk) } catch (err) { callback(err as Error) @@ -32,7 +46,12 @@ function budgeted(stream: Readable, budget: RequestSizeBudget): Readable { } }) - return stream.pipe(meter) + return { + stream: stream.pipe(meter), + get bytes() { + return bytes + } + } } /** @@ -66,11 +85,16 @@ export class UnixfsMulterStorage implements StorageEngine { const session = this.options.getSession(req) const write = session.beginFile() + const metered = budgeted(file.stream, session.budget) unixfs({ blockstore: session.blockstore }) - .addByteStream(budgeted(file.stream, session.budget)) + .addByteStream(metered.stream) .then((cid) => { - callback(undefined, { ...file, cid, storedBytes: write.storedBytes }) + // `size` is what multer would have set for a disk engine, and what the + // lifecycle record stores as the file's size. Leaving it unset wrote + // records without one, which nothing noticed until the registry began + // validating what it reads. + callback(undefined, { ...file, cid, size: metered.bytes, storedBytes: write.storedBytes }) }) .catch((err) => { callback(err, undefined) diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index be37f09..804e7de 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -287,6 +287,29 @@ describe('FileRegistry', () => { assert.equal(await registry.get(CID_A), undefined) }) + it('refuses to store a record it would not be able to read back', async () => { + // `JSON.stringify` drops an absent number, so a record written without one + // comes back as a shape the registry rejects. Validating only on the way + // out let such a record be persisted and then vanish from the report, from + // repair and from every plan, while its pin quietly kept the blocks. + const registry = createRegistry() + const incomplete = { + cid: CID_A, + name: 'photo.jpg', + state: 'confirmed', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + storedBytes: 10, + pinned: true, + heldLocally: true, + replicas: [] + } as unknown as FileRecord + + await assert.rejects(() => registry.save(incomplete), /Refusing to store an invalid/) + assert.equal(await registry.get(CID_A), undefined) + }) + it('creates a record only while the registry does not know the CID', async () => { const registry = createRegistry() const legacy: FileRecord = { diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 9e66ae5..dc33e4b 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -9,7 +9,11 @@ import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' import { Key } from 'interface-datastore' import { fixedSize } from 'ipfs-unixfs-importer/chunker' +import { Readable } from 'node:stream' +import type { Request } from 'express' import { CID } from 'multiformats/cid' +import { UnixfsMulterStorage } from '../../src/utils/unixfs-multer.storage.js' +import type { UnixFsMulterFile } from '../../src/utils/types.js' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { createUploadAdmission, getUploadSession } from '../../src/middleware/uploadAdmission.js' import { backfillRegistryFromPins, snapshotPins } from '../../src/storage/backfill.js' @@ -32,6 +36,14 @@ import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' import { UploadSession } from '../../src/storage/uploadSession.js' import { deterministicBytes } from '../fixtures.js' +/** + * Deletion fencing for a test that is the only writer. + * + * The real collector passes the storage lease here. Stating it at every call + * keeps the parameter impossible to forget where it does matter. + */ +const runWithoutOtherWriters = (work: () => Promise): Promise => work() + /** Only loopback, and port 0 so the OS picks a free port. */ const LISTEN = ['/ip4/127.0.0.1/tcp/0'] const WATERMARKS = { highWatermarkBytes: 1_000_000_000, lowWatermarkBytes: 500_000_000 } @@ -426,6 +438,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: WATERMARKS, blockstoreBytes: 4096, dryRun: true, @@ -445,6 +458,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 @@ -472,6 +486,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: WATERMARKS, blockstoreBytes: 4096, now: 1000 @@ -496,6 +511,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: WATERMARKS, blockstoreBytes: 4096, availableBytes: 1024, @@ -514,6 +530,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: { highWatermarkBytes: 2048, lowWatermarkBytes: 1024 }, blockstoreBytes: 4096, now: 1000 @@ -540,6 +557,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, pinTimeoutMs: 1500, @@ -579,6 +597,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node: flaky, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 @@ -605,6 +624,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry: cleanupFails, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 @@ -634,6 +654,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 @@ -667,6 +688,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry: planned, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 @@ -697,6 +719,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry: planned, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 2000 @@ -731,6 +754,7 @@ describe('garbage collection against fixture data', () => { await runGarbageCollection({ node: racing, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, now: 1000 @@ -749,6 +773,7 @@ describe('garbage collection against fixture data', () => { const report = await runGarbageCollection({ node, registry, + withCollectionLease: runWithoutOtherWriters, watermarks: WATERMARKS, blockstoreBytes: 4096, now: 1000 @@ -839,6 +864,50 @@ describe('block metering', () => { }) }) +describe('multipart import', () => { + it('reports the size of the part it imported', async () => { + // Nothing else knows it. Multer fills `size` only for engines that report + // it, and the blocks written are a different number — re-uploading content + // this node already holds writes none at all. Leaving it unset produced + // lifecycle records without a file size, which the registry cannot read. + const session = createSession() + const storage = new UnixfsMulterStorage({ getSession: () => session }) + const payload = deterministicBytes(140_000, 'multipart-size') + + const imported = await new Promise>((resolve, reject) => { + storage._handleFile( + {} as unknown as Request, + { originalname: 'photo.jpg', stream: Readable.from([payload]) } as Express.Multer.File, + (err, info) => (err ? reject(err) : resolve(info as Partial)) + ) + }) + + assert.equal(imported.size, payload.byteLength) + assert.notEqual(imported.cid, undefined) + assert.ok((imported.storedBytes ?? 0) > 0) + }) + + it('reports a size even when the content was already stored', async () => { + const payload = deterministicBytes(90_000, 'multipart-known') + await ifs.addBytes(payload) + + const session = createSession() + const storage = new UnixfsMulterStorage({ getSession: () => session }) + + const imported = await new Promise>((resolve, reject) => { + storage._handleFile( + {} as unknown as Request, + { originalname: 'again.jpg', stream: Readable.from([payload]) } as Express.Multer.File, + (err, info) => (err ? reject(err) : resolve(info as Partial)) + ) + }) + + // No new blocks, but the file still has a size + assert.equal(imported.storedBytes, 0) + assert.equal(imported.size, payload.byteLength) + }) +}) + describe('collection pass', () => { const watermarks = { highWatermarkBytes: 0, lowWatermarkBytes: 0 } diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts new file mode 100644 index 0000000..f172765 --- /dev/null +++ b/test/integration/uploadRoute.test.ts @@ -0,0 +1,241 @@ +import assert from 'node:assert/strict' +import { createServer, type Server } from 'node:http' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { type UnixFS, unixfs } from '@helia/unixfs' +import { FsBlockstore } from 'blockstore-fs' +import { FsDatastore } from 'datastore-fs' +import express from 'express' +import multer from 'multer' +import { CID } from 'multiformats/cid' +import { createUploadHandler } from '../../src/api/uploadRoute.js' +import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' +import { createUploadAdmission, getUploadSession } from '../../src/middleware/uploadAdmission.js' +import { ConcurrencyLimiter } from '../../src/storage/limits.js' +import { StorageOperationLock } from '../../src/storage/operationLock.js' +import { isDirectlyPinned } from '../../src/storage/pinning.js' +import { FileRegistry } from '../../src/storage/registry.js' +import type { ReplicationReport } from '../../src/storage/replication.js' +import { resetClaims } from '../../src/storage/reservation.js' +import { UnixfsMulterStorage } from '../../src/utils/unixfs-multer.storage.js' +import { deterministicBytes } from '../fixtures.js' + +const LISTEN = ['/ip4/127.0.0.1/tcp/0'] +const MiB = 1024 * 1024 + +let storeDir: string +let blockstore: FsBlockstore +let datastore: FsDatastore +let node: IpfsNode +let ifs: UnixFS +let prefix = 0 + +/** A best-effort report, which is what a node with replication off produces. */ +const bestEffort = (): ReplicationReport => ({ + mode: 'best-effort', + desiredCopies: 1, + copies: 1, + required: 1, + acknowledged: 1, + replicas: [], + cached: [], + satisfied: true, + networkTooSmall: true, + attempts: [] +}) + +interface Harness { + server: Server + url: string + registry: FileRegistry + lock: StorageOperationLock + replicated: string[] +} + +/** + * The upload endpoint as it is actually assembled: admission, the multipart + * parser that imports straight into the blockstore, and the route handler. + * + * Only the network-facing side is stubbed. Everything that touches storage is + * the production code, because the point of this file is the path a request + * really takes. + */ +async function serveUpload( + overrides: { replicate?: (cid: string) => Promise } = {} +): Promise { + prefix += 1 + const registry = new FileRegistry(datastore, `/adm/route-${prefix}`) + const lock = new StorageOperationLock() + const replicated: string[] = [] + + const admit = createUploadAdmission({ + storage: { maxRequestSizeBytes: 64 * MiB, diskReserveBytes: 0 }, + limiter: new ConcurrencyLimiter(4), + operationLock: lock, + availableStorageSize: async () => BigInt(1024 * MiB), + blockstore: node.blockstore, + isPinned: (cid) => node.pins.isPinned(cid), + deleteBlock: (cid) => blockstore.delete(cid), + parseCid: (value) => CID.parse(value), + log: { info: () => undefined, warn: () => undefined, error: () => undefined } + }) + + const app = express() + app.post( + '/upload', + admit, + multer({ storage: new UnixfsMulterStorage({ getSession: getUploadSession }) }).array('files'), + createUploadHandler({ + node, + registry, + getSession: getUploadSession, + confirmationRequired: false, + temporaryTtlMs: 60_000, + requireQuorumOnUpload: false, + replicate: + overrides.replicate ?? + (async (cid) => { + replicated.push(cid) + return bestEffort() + }), + release: async () => undefined, + log: { info: () => undefined, error: () => undefined } + }) + ) + + const server = createServer(app) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + const port = typeof address === 'object' && address !== null ? address.port : 0 + + return { server, url: `http://127.0.0.1:${port}/upload`, registry, lock, replicated } +} + +async function post( + url: string, + parts: Array<{ name: string; bytes: Uint8Array }> +): Promise { + const form = new FormData() + for (const part of parts) { + form.append('files', new Blob([part.bytes as BlobPart]), part.name) + } + + return fetch(url, { method: 'POST', body: form }) +} + +before(async () => { + storeDir = await mkdtemp(join(tmpdir(), 'ipfs-node-upload-route-')) + blockstore = new FsBlockstore(join(storeDir, 'blockstore')) + datastore = new FsDatastore(join(storeDir, 'datastore')) + await blockstore.open() + await datastore.open() + node = await createIpfsNode({ blockstore, datastore, listen: LISTEN, bootstrap: [], allow: [] }) + ifs = unixfs(node) +}) + +after(async () => { + await node.stop() + await blockstore.close() + await datastore.close() + await rm(storeDir, { recursive: true, force: true }) + resetClaims() +}) + +describe('the upload endpoint end to end', () => { + it('stores a file, records it whole, and pins it', async () => { + // Every earlier test built its records by hand, which is why a route that + // wrote records without a file size went unnoticed until a node refused to + // read its own registry. + const harness = await serveUpload() + const payload = deterministicBytes(140_000, 'route-single') + + try { + const response = await post(harness.url, [{ name: 'photo.jpg', bytes: payload }]) + const body = (await response.json()) as { cids: string[] } + + assert.equal(response.status, 200) + assert.equal(body.cids.length, 1) + + const record = await harness.registry.get(body.cids[0]) + + assert.equal(record?.state, 'confirmed') + assert.equal(record?.name, 'photo.jpg') + assert.equal(record?.fileSize, payload.byteLength) + assert.ok((record?.storedBytes ?? 0) > 0) + assert.equal(await isDirectlyPinned(node, CID.parse(body.cids[0])), true) + assert.deepEqual(harness.replicated, [body.cids[0]]) + } finally { + harness.server.close() + } + }) + + it('records every file of a multi-part request', async () => { + const harness = await serveUpload() + + try { + const response = await post(harness.url, [ + { name: 'one.bin', bytes: deterministicBytes(90_000, 'route-one') }, + { name: 'two.bin', bytes: deterministicBytes(70_000, 'route-two') } + ]) + const body = (await response.json()) as { cids: string[] } + + assert.equal(response.status, 200) + assert.equal(new Set(body.cids).size, 2) + + for (const cid of body.cids) { + const record = await harness.registry.get(cid) + assert.equal(record?.state, 'confirmed') + assert.ok((record?.fileSize ?? 0) > 0, `${cid} was recorded without a file size`) + } + } finally { + harness.server.close() + } + }) + + it('gives the storage lease back once the request is done', async () => { + // Deletion waits behind this lease. A request that never returned it would + // stop the collector from ever running again. + const harness = await serveUpload() + + try { + await post(harness.url, [ + { name: 'lease.bin', bytes: deterministicBytes(50_000, 'route-lease') } + ]) + + const exclusive = await Promise.race([ + harness.lock.acquireExclusive().then(() => 'granted'), + new Promise((resolve) => setTimeout(() => resolve('still held'), 1000)) + ]) + + assert.equal(exclusive, 'granted') + } finally { + harness.server.close() + } + }) + + it('leaves nothing behind when the request fails after importing', async () => { + const harness = await serveUpload({ + replicate: async () => { + throw new Error('replication exploded') + } + }) + const payload = deterministicBytes(60_000, 'route-fails') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const response = await post(harness.url, [{ name: 'doomed.bin', bytes: payload }]) + + assert.equal(response.status, 500) + + // The handler committed before replication, so the file stays: the blocks + // are pinned and the record describes them. What must not happen is a pin + // with no record, or a record with no pin. + const record = await harness.registry.get(cid) + assert.equal(record !== undefined, await isDirectlyPinned(node, CID.parse(cid))) + } finally { + harness.server.close() + } + }) +}) From e57b1bf49ed85b7c5c69639151768957001dd5ae Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Wed, 26 Aug 2026 01:46:01 +0300 Subject: [PATCH 124/167] fix(storage): enhance storage metrics and lifecycle management with protected bytes tracking --- docs/storage-lifecycle.md | 4 +- src/api/file.ts | 5 +- src/api/uploadRoute.ts | 123 ++++++++++++++++------ src/config.ts | 43 +++++++- src/storage/backfill.ts | 1 + src/storage/collection.ts | 8 +- src/storage/gc.ts | 21 +++- src/storage/lifecycle.ts | 3 +- src/storage/metrics.ts | 6 +- src/storage/registry.ts | 29 +++++ src/storage/replication.ts | 29 ++++- src/storage/service.ts | 29 +++-- src/storage/uploadSession.ts | 59 +++++++++-- src/utils/types.ts | 2 + src/utils/unixfs-multer.storage.ts | 10 +- test/config.test.ts | 14 ++- test/garbageCollectionPlan.test.ts | 22 ++++ test/integration/storageLifecycle.test.ts | 4 + test/integration/uploadRoute.test.ts | 61 +++++++++-- test/replication.test.ts | 33 ++++++ 20 files changed, 429 insertions(+), 77 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index ce09737..b47c2a8 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -458,8 +458,8 @@ operational cost. `GET /api/storage/metrics` reports: -- `pinnedBytes` — content protected by a pin, estimated from the blocks this node - wrote for each registered file +- `pinnedBytes` — content protected by a pin, estimated from the full DAG of + each registered file, including blocks that existed before its latest upload - `reclaimableBytes` — blockstore bytes that no pin protects - `availableBytes` — free space on the blockstore filesystem - `reservedBytes` and `usableBytes` — the disk reserve and what is left for uploads diff --git a/src/api/file.ts b/src/api/file.ts index ede46a9..50b3168 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -9,7 +9,7 @@ import { effectiveQuorum, prepareFileRetrieval, releaseFile, - replicateFile + replicateUploadedFile } from '../storage/service.js' import { fileRegistry } from '../storage/state.js' import { createUploadHandler } from './uploadRoute.js' @@ -47,8 +47,7 @@ router.post( confirmationRequired: config.storage.confirmationRequired, temporaryTtlMs: config.storage.temporaryTtlMs, requireQuorumOnUpload: config.replication.requireQuorumOnUpload, - replicate: replicateFile, - release: releaseFile, + replicate: replicateUploadedFile, log: logger }) ) diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index 0cd0e81..e929fdc 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -25,7 +25,6 @@ export interface UploadRouteDependencies { /** Whether an upload that missed its quorum is refused rather than kept. */ requireQuorumOnUpload: boolean replicate: (cid: string) => Promise - release: (cid: string) => Promise log: UploadRouteLog } @@ -57,13 +56,33 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // Hold all involved CIDs until this request either commits or restores // every pin and lifecycle record it changed. Sorting happens inside the // registry, so overlapping multi-file requests cannot deadlock. - const records = await registry.withExclusiveCids( + const outcome = await registry.withExclusiveCids( files.map((file) => file.cid.toString()), async (locked) => { const undo: Array<() => Promise> = [] + const restoreBaselines = async (): Promise => { + const failures: Error[] = [] + + while (undo.length > 0) { + const step = undo.pop() + + try { + await step?.() + } catch (err) { + const failure = err as Error + failures.push(failure) + log.error(`Could not undo a failed upload: ${failure.message}`) + } + } + + if (failures.length > 0) { + throw new AggregateError(failures, 'Could not fully restore the upload baseline') + } + } + try { - const stored = [] + const stored: FileRecord[] = [] for (const file of files) { const cid = file.cid.toString() @@ -79,7 +98,8 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ cid, name: file.originalname, fileSize: file.size, - storedBytes: file.storedBytes + storedBytes: file.storedBytes, + protectedBytes: file.protectedBytes }, { confirmationRequired: dependencies.confirmationRequired, @@ -117,43 +137,84 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ stored.push(registration.record) } - // Every file is stored and pinned. Ownership passes from the - // request session to the registry before any CID lock is released. + // One request may contain the same content more than once. Place + // each CID once, using the last registration stored above. + const durableByCid = new Map( + stored + .filter((record) => record.state === 'confirmed') + .map((record) => [record.cid, record] as const) + ) + const durable = [...durableByCid.values()] + const replication = await Promise.all( + durable.map(async (record) => ({ + cid: record.cid, + report: await dependencies.replicate(record.cid) + })) + ) + + if ( + dependencies.requireQuorumOnUpload && + replication.some((item) => !item.report.satisfied) + ) { + // The request is rejected as a transaction. Restoring the exact + // pre-request records and pins keeps a durable re-upload durable + // instead of turning it into an expired, unpinned file. + await restoreBaselines() + await session.cleanup() + + return { records: stored, replication, quorumReached: false } + } + + // Replication performs network work only. Persist acknowledgements + // here under the same locks as registration, so a concurrent + // lifecycle action cannot be overwritten by a late report. + const finalized = new Map() + for (const item of replication) { + const record = durableByCid.get(item.cid) + + if (record === undefined || item.report.mode !== 'quorum') { + continue + } + + finalized.set( + item.cid, + await locked.save({ ...record, replicas: item.report.replicas }) + ) + } + + const records = stored.map((record) => finalized.get(record.cid) ?? record) + + // Every file is stored, pinned, registered and—where enabled—has + // its replication report recorded. Ownership can now pass from the + // request session to the registry before the CID locks are released. session.commit() - return stored + + return { records, replication, quorumReached: true } } catch (err) { - for (const step of undo.reverse()) { - try { - await step() - } catch (undoError) { - log.error(`Could not undo a failed upload: ${(undoError as Error).message}`) - } + let failure = err as Error + + try { + await restoreBaselines() + } catch (rollbackError) { + failure = new AggregateError( + [failure, rollbackError as Error], + 'Upload failed and its previous lifecycle could not be fully restored' + ) } - throw err + + await session.cleanup() + throw failure } } ) - const durable = records.filter((record) => record.state === 'confirmed') - const replication = await Promise.all( - durable.map(async (record) => ({ - cid: record.cid, - report: await dependencies.replicate(record.cid) - })) - ) - const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) - - if ( - dependencies.requireQuorumOnUpload && - replication.some((item) => !item.report.satisfied) - ) { - // The policy demands a quorum, so the upload is not durable. The files - // are released instead of being kept as a copy nobody promised to hold. - await Promise.all(durable.map((record) => dependencies.release(record.cid))) - + if (!outcome.quorumReached) { return res.status(503).send({ error: 'Replication quorum not reached' }) } + const { records, replication } = outcome + const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) + res.send({ filesNames: files.map((file) => file.originalname), cids: records.map((record) => record.cid), diff --git a/src/config.ts b/src/config.ts index 2b4eeeb..027c5ab 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,6 +2,8 @@ import * as fs from 'fs' import { fileURLToPath } from 'url' import { dirname, join } from 'path' import JSON5 from 'json5' +import { peerIdFromString } from '@libp2p/peer-id' +import { multiaddr } from '@multiformats/multiaddr' import { validateSecurityConfig } from './security/config.js' import type { RateLimitPolicy } from './security/rateLimit.js' import type { TrustProxySetting } from './security/trustProxy.js' @@ -142,6 +144,26 @@ function requireStringArray(value: unknown, path: string): string[] { return value.map((item, index) => requireString(item, `${path}[${index}]`)) } +/** Validate a node multiaddr and return its authenticated peer identity. */ +function requireNodePeerId(value: string, path: string): string { + try { + const components = multiaddr(value).getComponents() + const peerComponent = components.filter((component) => component.name === 'p2p').at(-1) + + if (peerComponent?.value === undefined) { + fail(path, 'must contain a /p2p/ component') + } + + return peerIdFromString(peerComponent.value).toString() + } catch (err) { + if (err instanceof ConfigError) { + throw err + } + + fail(path, 'must be a valid multiaddr with a peer id') + } +} + /** * Validate a positive integer. * @@ -175,12 +197,29 @@ export function validateConfig(raw: unknown): Config { } const nodes = root.nodes.map((node, index) => { const entry = requireObject(node, `nodes[${index}]`) + const multiAddr = requireString(entry.multiAddr, `nodes[${index}].multiAddr`) + return { name: requireString(entry.name, `nodes[${index}].name`), - multiAddr: requireString(entry.multiAddr, `nodes[${index}].multiAddr`) + multiAddr, + peerId: requireNodePeerId(multiAddr, `nodes[${index}].multiAddr`) } }) + const peerIndexes = new Map() + for (const [index, node] of nodes.entries()) { + const previousIndex = peerIndexes.get(node.peerId) + + if (previousIndex !== undefined) { + fail( + `nodes[${index}].multiAddr`, + `must identify a unique peer; it duplicates nodes[${previousIndex}]` + ) + } + + peerIndexes.set(node.peerId, index) + } + const logLevel = requireString(root.logLevel, 'logLevel') if (!(LOG_LEVELS as readonly string[]).includes(logLevel)) { fail('logLevel', `must be one of: ${LOG_LEVELS.join(', ')}`) @@ -215,7 +254,7 @@ export function validateConfig(raw: unknown): Config { const replication = resolveReplicationConfig(root.replication) return { - nodes, + nodes: nodes.map(({ name, multiAddr }) => ({ name, multiAddr })), storeFolder, logLevel: logLevel as LogLevel, peerDiscovery: { bootstrap, listen }, diff --git a/src/storage/backfill.ts b/src/storage/backfill.ts index 34a50e1..c980199 100644 --- a/src/storage/backfill.ts +++ b/src/storage/backfill.ts @@ -130,6 +130,7 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis confirmedAt: now, fileSize: Number(stats.size), storedBytes: Number(stats.deduplicatedDagSize), + protectedBytes: Number(stats.deduplicatedDagSize), pinned: true, heldLocally: true, replicas: [] diff --git a/src/storage/collection.ts b/src/storage/collection.ts index bfe184d..528fc9d 100644 --- a/src/storage/collection.ts +++ b/src/storage/collection.ts @@ -21,7 +21,12 @@ export interface CollectStorageOptions { /** Hands copies over to their designated holders, and reports which. */ demote: (options: { dryRun?: boolean }) => Promise<{ demoted: string[] }> /** Measures the blockstore and the volume it sits on. */ - measure: () => Promise<{ blockstoreBytes: number; availableBytes: number }> + measure: () => Promise<{ + blockstoreBytes: number + availableBytes: number + /** Bytes already unpinned and removable without evicting another file. */ + reclaimableBytes?: number + }> dryRun?: boolean force?: boolean log?: (message: string) => void @@ -53,6 +58,7 @@ export async function collectStorage(options: CollectStorageOptions): Promise @@ -18,6 +18,8 @@ export const RESERVE_RECOVERY_MARGIN = 0.25 export interface GcPlanInput { /** Current blockstore size in bytes. */ blockstoreBytes: number + /** Bytes already unpinned and removable without releasing another file. */ + reclaimableBytes?: number watermarks: Watermarks /** Free bytes on the blockstore filesystem, when known. */ availableBytes?: number @@ -69,9 +71,17 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { const expired = releasable.filter((record) => isExpired(record, now)) const alive = releasable.filter((record) => !isExpired(record, now)) - let estimatedBytesAfter = input.blockstoreBytes + // Helia GC removes every unpinned block, including read cache and files a + // previous handover already released. Count that space before selecting a + // live temporary upload, or the planner evicts protected content to solve + // pressure that the existing cache alone would have relieved. + let estimatedBytesAfter = + input.blockstoreBytes - + Math.min(input.blockstoreBytes, Math.max(0, input.reclaimableBytes ?? 0)) for (const record of expired) { - estimatedBytesAfter -= record.storedBytes + if (record.pinned) { + estimatedBytesAfter -= protectedStorageBytes(record) + } } // Space is short when the blockstore passed its ceiling, or when the volume @@ -117,7 +127,7 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { } evicted.push(record) - estimatedBytesAfter -= record.storedBytes + estimatedBytesAfter -= protectedStorageBytes(record) } } @@ -178,6 +188,8 @@ export interface GcRunOptions { registry: FileRegistry watermarks: Watermarks blockstoreBytes: number + /** Bytes already unpinned and removable by this run. */ + reclaimableBytes?: number /** Free bytes on the blockstore filesystem. */ availableBytes?: number /** Free bytes uploads must never consume. */ @@ -217,6 +229,7 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { const pinnedBytes = records .filter((record) => record.pinned) - .reduce((total, record) => total + record.storedBytes, 0) + .reduce((total, record) => total + protectedStorageBytes(record), 0) const reservedBytes = config.storage.diskReserveBytes diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 6807a7c..1386508 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -28,6 +28,14 @@ export interface FileRecord { fileSize: number /** Bytes of blocks this node actually wrote for the file. */ storedBytes: number + /** + * Deduplicated bytes in the file's DAG, whether or not they already existed. + * + * Optional for records written before this field was introduced. Callers + * that need a protection estimate must use {@link protectedStorageBytes} so + * those records remain usable after an in-place upgrade. + */ + protectedBytes?: number /** True while the root CID is pinned and therefore protected from collection. */ pinned: boolean /** @@ -87,6 +95,7 @@ function isFileRecord(value: unknown): value is FileRecord { isNullableTimestamp(record.confirmedAt) && isFiniteNonNegative(record.fileSize) && isFiniteNonNegative(record.storedBytes) && + (record.protectedBytes === undefined || isFiniteNonNegative(record.protectedBytes)) && typeof record.pinned === 'boolean' && typeof record.heldLocally === 'boolean' && Array.isArray(record.replicas) && @@ -113,6 +122,8 @@ export interface NewFileRecord { name: string fileSize: number storedBytes: number + /** Full DAG size when the importer or UnixFS stat can provide it. */ + protectedBytes?: number } /** @@ -452,6 +463,10 @@ export class FileRegistry { const record = await this.save({ ...existing, storedBytes: Math.max(existing.storedBytes, file.storedBytes), + protectedBytes: Math.max( + protectedStorageBytes(existing), + file.protectedBytes ?? Math.max(file.storedBytes, file.fileSize) + ), fileSize: file.fileSize > 0 ? file.fileSize : existing.fileSize, pinned: true, heldLocally: true @@ -471,6 +486,7 @@ export class FileRegistry { confirmedAt: confirmed ? now : null, fileSize: file.fileSize, storedBytes: file.storedBytes, + protectedBytes: file.protectedBytes ?? Math.max(file.storedBytes, file.fileSize), pinned: true, heldLocally: true, replicas: existing?.replicas ?? [] @@ -487,6 +503,19 @@ export class FileRegistry { } } +/** + * Estimate how many blockstore bytes a record's pin protects. + * + * Older records did not persist the full DAG size. Their write delta is exact + * for a first import, while the logical file size prevents a cached re-upload + * from being treated as zero bytes until it is registered again. + */ +export function protectedStorageBytes( + record: Pick +): number { + return record.protectedBytes ?? Math.max(record.storedBytes, record.fileSize) +} + /** * True when a temporary upload was abandoned, meaning nobody confirmed it * before its TTL elapsed. Confirmed files never expire. diff --git a/src/storage/replication.ts b/src/storage/replication.ts index 2c79ffd..4fb3af4 100644 --- a/src/storage/replication.ts +++ b/src/storage/replication.ts @@ -90,6 +90,25 @@ export function requiredAcks(config: ReplicationConfig, placement: Placement): n return Math.max(1, Math.min(config.ackQuorum, placement.copies)) } +/** + * Keep one address for each remote peer identity. + * + * A peer can be reachable through several configured multiaddrs, but those + * addresses still lead to one durability domain. Counting them separately + * would let one machine satisfy several copies of the quorum. + */ +function distinctPeers(peers: ReplicationPeer[], selfPeerId: string): ReplicationPeer[] { + const byPeerId = new Map() + + for (const peer of peers) { + if (peer.peerId !== selfPeerId && !byPeerId.has(peer.peerId)) { + byPeerId.set(peer.peerId, peer) + } + } + + return [...byPeerId.values()] +} + /** * Place copies of a file on the nodes that should hold it. * @@ -119,16 +138,18 @@ export async function replicate(options: ReplicateOptions): Promise peer.peerId) + peerIds: peers.map((peer) => peer.peerId) }) const targetIds = new Set(storageTargets(placement, options.selfPeerId)) - const targets = options.peers.filter((peer) => targetIds.has(peer.peerId)) + const targets = peers.filter((peer) => targetIds.has(peer.peerId)) const attempts = await Promise.all( targets.map(async (peer): Promise => { @@ -156,12 +177,12 @@ export async function replicate(options: ReplicateOptions): Promise peer.peerId)) const ranked = rankHolders( cid, - options.peers.map((peer) => peer.peerId) + peers.map((peer) => peer.peerId) ) const extra = ranked .filter((peerId) => !tried.has(peerId)) .slice(0, CACHE_WIDENING) - .map((peerId) => options.peers.find((peer) => peer.peerId === peerId)) + .map((peerId) => peers.find((peer) => peer.peerId === peerId)) .filter((peer): peer is ReplicationPeer => peer !== undefined) const widened = await Promise.all( diff --git a/src/storage/service.ts b/src/storage/service.ts index 8fbf9af..b201751 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -77,19 +77,16 @@ function placementFor(cid: string, createdAt: number, peers: ReplicationPeer[]): } /** - * Place copies of a file on the nodes that should hold it, and record which of - * them acknowledged. + * Place copies of a file on the nodes that should hold it. * * Failures are reported, never thrown: an upload that is already stored and * pinned locally stays valid when a peer is unavailable, and the repair job * retries later. */ -export async function replicateFile(cid: string): Promise { - const record = await fileRegistry.get(cid) - - const report = await replicate({ +async function placeReplicas(cid: string, createdAt: number): Promise { + return replicate({ cid, - ageMs: Math.max(0, Date.now() - (record?.createdAt ?? Date.now())), + ageMs: Math.max(0, Date.now() - createdAt), selfPeerId: selfPeerId(), peers: getReplicationPeers(), config: config.replication, @@ -98,6 +95,24 @@ export async function replicateFile(cid: string): Promise { await requestCache(helia, peer.multiAddr, cid, callOptions()) } }) +} + +/** + * Place copies for an upload without changing its lifecycle record. + * + * The upload handler owns the CID lock and records the report itself. Keeping + * network placement separate prevents the service from queuing a registry + * mutation behind the lock its caller already holds. + */ +export async function replicateUploadedFile(cid: string): Promise { + const record = await fileRegistry.get(cid) + return placeReplicas(cid, record?.createdAt ?? Date.now()) +} + +/** Place copies of a known file and persist the peers that acknowledged it. */ +export async function replicateFile(cid: string): Promise { + const record = await fileRegistry.get(cid) + const report = await placeReplicas(cid, record?.createdAt ?? Date.now()) if (report.mode === 'quorum') { await fileRegistry.setReplicas(cid, report.replicas) diff --git a/src/storage/uploadSession.ts b/src/storage/uploadSession.ts index 210f94f..69ca48c 100644 --- a/src/storage/uploadSession.ts +++ b/src/storage/uploadSession.ts @@ -84,6 +84,50 @@ class RecordingBlockstore implements Pick { return this.base.has(cid, options) } + /** + * Create a facade that also counts the unique DAG blocks of one file. + * + * The ordinary session counter only measures newly written bytes. This + * facade counts blocks even when they were already present, which is the + * amount the resulting pin protects and the collector must account for. + */ + beginFile(): { + blockstore: Pick + readonly storedBytes: number + readonly protectedBytes: number + } { + const before = this.bytesWritten + const referenced = new Set() + let protectedBytes = 0 + const storedBytes = (): number => this.bytesWritten - before + + return { + blockstore: { + get: (cid, options) => this.get(cid, options), + has: (cid, options) => this.has(cid, options), + put: async (cid, block, options) => { + const result = await this.put(cid, block, options) + const key = cid.toString() + + if (!referenced.has(key)) { + referenced.add(key) + // The UnixFS importer supplies complete blocks as Uint8Arrays. + // A streamed value cannot be measured without consuming it. + protectedBytes += block instanceof Uint8Array ? block.byteLength : 0 + } + + return result + } + }, + get storedBytes() { + return storedBytes() + }, + get protectedBytes() { + return protectedBytes + } + } + } + /** Drop the in-flight references taken by `put`. */ releaseAll(): string[] { const zeroed: string[] = [] @@ -155,15 +199,12 @@ export class UploadSession { * Parts of a multipart request are imported one after another, so the delta * of the session counter is the number of new bytes this part contributed. */ - beginFile(): { readonly storedBytes: number } { - const before = this.blockstore.bytesWritten - const blockstore = this.blockstore - - return { - get storedBytes() { - return blockstore.bytesWritten - before - } - } + beginFile(): { + blockstore: Pick + readonly storedBytes: number + readonly protectedBytes: number + } { + return this.blockstore.beginFile() } /** Keep everything this session wrote. Called once the upload is durable. */ diff --git a/src/utils/types.ts b/src/utils/types.ts index b89fde3..4d9f9b3 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -17,4 +17,6 @@ export type UnixFsMulterFile = Express.Multer.File & { cid: CID /** Bytes of blocks this node wrote while importing the part. */ storedBytes: number + /** Deduplicated bytes in the file's DAG, including blocks already present. */ + protectedBytes: number } diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index 4632e82..b27a8c0 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -87,14 +87,20 @@ export class UnixfsMulterStorage implements StorageEngine { const write = session.beginFile() const metered = budgeted(file.stream, session.budget) - unixfs({ blockstore: session.blockstore }) + unixfs({ blockstore: write.blockstore }) .addByteStream(metered.stream) .then((cid) => { // `size` is what multer would have set for a disk engine, and what the // lifecycle record stores as the file's size. Leaving it unset wrote // records without one, which nothing noticed until the registry began // validating what it reads. - callback(undefined, { ...file, cid, size: metered.bytes, storedBytes: write.storedBytes }) + callback(undefined, { + ...file, + cid, + size: metered.bytes, + storedBytes: write.storedBytes, + protectedBytes: write.protectedBytes + }) }) .catch((err) => { callback(err, undefined) diff --git a/test/config.test.ts b/test/config.test.ts index 355ab98..8ef841d 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -2,10 +2,12 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { ConfigError, config, configFileName, validateConfig } from '../src/config.js' +const PEER_ID = '12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq' + /** A minimal config that passes validation; individual tests break one field. */ function validRaw(): Record { return { - nodes: [{ name: 'ipfs1', multiAddr: '/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWTest' }], + nodes: [{ name: 'ipfs1', multiAddr: `/ip4/127.0.0.1/tcp/4001/p2p/${PEER_ID}` }], storeFolder: '.adm-ipfs', logLevel: 'debug', peerDiscovery: { @@ -53,6 +55,16 @@ describe('validateConfig', () => { assert.doesNotThrow(() => validateConfig({ ...validRaw(), someFutureOption: 42 })) }) + it('rejects two configured addresses for the same peer identity', () => { + const raw = validRaw() + raw.nodes = [ + { name: 'ipfs1', multiAddr: `/ip4/127.0.0.1/tcp/4001/p2p/${PEER_ID}` }, + { name: 'ipfs1-alias', multiAddr: `/ip4/127.0.0.2/tcp/4001/p2p/${PEER_ID}` } + ] + + assert.throws(() => validateConfig(raw), /must identify a unique peer/) + }) + const invalidCases: Array<[string, (raw: Record) => void, RegExp]> = [ ['nodes is missing', (raw) => delete raw.nodes, /nodes/], [ diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts index 4d03cbc..a799858 100644 --- a/test/garbageCollectionPlan.test.ts +++ b/test/garbageCollectionPlan.test.ts @@ -161,4 +161,26 @@ describe('planGarbageCollection', () => { ['a'] ) }) + + it('collects already-unpinned bytes before evicting a live upload', () => { + const plan = planGarbageCollection({ + blockstoreBytes: 1100, + reclaimableBytes: 600, + watermarks, + records: [ + record({ + cid: 'pending', + state: 'temporary', + expiresAt: 9000, + storedBytes: 400, + protectedBytes: 400 + }) + ], + now: 1000 + }) + + assert.equal(plan.shouldCollect, true) + assert.deepEqual(plan.evicted, []) + assert.equal(plan.estimatedBytesAfter, 500) + }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index dc33e4b..b338d86 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -905,6 +905,10 @@ describe('multipart import', () => { // No new blocks, but the file still has a size assert.equal(imported.storedBytes, 0) assert.equal(imported.size, payload.byteLength) + assert.ok( + (imported.protectedBytes ?? 0) > 0, + 'an existing DAG still contributes bytes protected by its pin' + ) }) }) diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts index f172765..ab515d4 100644 --- a/test/integration/uploadRoute.test.ts +++ b/test/integration/uploadRoute.test.ts @@ -63,7 +63,10 @@ interface Harness { * really takes. */ async function serveUpload( - overrides: { replicate?: (cid: string) => Promise } = {} + overrides: { + replicate?: (cid: string) => Promise + requireQuorumOnUpload?: boolean + } = {} ): Promise { prefix += 1 const registry = new FileRegistry(datastore, `/adm/route-${prefix}`) @@ -93,14 +96,13 @@ async function serveUpload( getSession: getUploadSession, confirmationRequired: false, temporaryTtlMs: 60_000, - requireQuorumOnUpload: false, + requireQuorumOnUpload: overrides.requireQuorumOnUpload ?? false, replicate: overrides.replicate ?? (async (cid) => { replicated.push(cid) return bestEffort() }), - release: async () => undefined, log: { info: () => undefined, error: () => undefined } }) ) @@ -164,6 +166,7 @@ describe('the upload endpoint end to end', () => { assert.equal(record?.name, 'photo.jpg') assert.equal(record?.fileSize, payload.byteLength) assert.ok((record?.storedBytes ?? 0) > 0) + assert.ok((record?.protectedBytes ?? 0) >= (record?.storedBytes ?? 0)) assert.equal(await isDirectlyPinned(node, CID.parse(body.cids[0])), true) assert.deepEqual(harness.replicated, [body.cids[0]]) } finally { @@ -229,11 +232,55 @@ describe('the upload endpoint end to end', () => { assert.equal(response.status, 500) - // The handler committed before replication, so the file stays: the blocks - // are pinned and the record describes them. What must not happen is a pin - // with no record, or a record with no pin. + // A replication failure rejects the transaction before the request gives + // its blocks to the registry. const record = await harness.registry.get(cid) - assert.equal(record !== undefined, await isDirectlyPinned(node, CID.parse(cid))) + assert.equal(record, undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + } finally { + harness.server.close() + } + }) + + it('restores a pre-existing durable file when a re-upload misses strict quorum', async () => { + let satisfied = true + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => ({ + mode: 'quorum', + desiredCopies: 2, + copies: 2, + required: 2, + acknowledged: satisfied ? 2 : 1, + replicas: satisfied ? ['peer'] : [], + cached: [], + satisfied, + networkTooSmall: false, + attempts: [] + }) + }) + const payload = deterministicBytes(75_000, 'strict-reupload') + + try { + const first = await post(harness.url, [{ name: 'original.bin', bytes: payload }]) + const firstBody = (await first.json()) as { cids: string[] } + const cid = firstBody.cids[0] + const before = await harness.registry.get(cid) + + assert.equal(first.status, 200) + assert.equal(before?.state, 'confirmed') + assert.deepEqual(before?.replicas, ['peer']) + + satisfied = false + const second = await post(harness.url, [{ name: 'replacement.bin', bytes: payload }]) + const after = await harness.registry.get(cid) + + assert.equal(second.status, 503) + assert.equal(after?.state, 'confirmed') + assert.equal(after?.name, 'original.bin') + assert.deepEqual(after?.replicas, ['peer']) + assert.equal(after?.pinned, true) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), true) } finally { harness.server.close() } diff --git a/test/replication.test.ts b/test/replication.test.ts index d81e16c..6503b9d 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -163,6 +163,39 @@ describe('replicate', () => { assert.equal(report.required, 2) assert.equal(report.satisfied, true) }) + + it('counts each peer identity once when it has several configured addresses', async () => { + const duplicate = { + ...peer('ipfs2-alias'), + peerId: PEERS[0].peerId, + multiAddr: multiaddr('/ip4/127.0.0.2/tcp/4001') + } + const uniquePeers = [PEERS[0], duplicate, PEERS[1]] + const contacted = new Set() + + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: uniquePeers, + config: { + ...baseConfig, + placement: [{ minAgeMs: 0, copies: 4 }], + ackQuorum: 4 + }, + store: async (target) => { + assert.equal(contacted.has(target.peerId), false, `${target.peerId} was contacted twice`) + contacted.add(target.peerId) + return 'stored' + } + }) + + assert.equal(contacted.size, 2) + assert.equal(report.copies, 3) + assert.equal(report.required, 3) + assert.equal(report.acknowledged, 3) + assert.equal(report.satisfied, true) + }) }) describe('placement outcomes', () => { From 33440c5a26d3a38f69ef6bc6b01a69916ab0c483 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Wed, 26 Aug 2026 09:06:46 +0300 Subject: [PATCH 125/167] feat(storage): implement rollback mechanism for upload requests and enhance storage metrics calculations --- src/api/uploadRoute.ts | 229 +++++++++++++++++---------- src/storage/metrics.ts | 24 ++- src/storage/rollback.ts | 17 ++ test/integration/uploadRoute.test.ts | 22 +++ test/storageMetrics.test.ts | 45 ++++++ 5 files changed, 252 insertions(+), 85 deletions(-) create mode 100644 test/storageMetrics.test.ts diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index e929fdc..9e35f61 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -28,6 +28,47 @@ export interface UploadRouteDependencies { log: UploadRouteLog } +interface UploadBaseline { + cid: string + /** The record the CID had before this request, when it had one. */ + previous?: FileRecord + /** Whether this request created the pin. */ + createdPin: boolean + /** The record this request stored. */ + written: FileRecord + unpin: () => Promise +} + +/** + * Undo an admitted request whose quorum was refused afterwards. + * + * Each CID is taken again on its own, because the locks were released for the + * network work. Only a record still carrying this request's write is rolled + * back; anything written since belongs to somebody else. + */ +async function rollbackAdmitted( + registry: FileRegistry, + baselines: UploadBaseline[], + log: UploadRouteLog +): Promise { + for (const baseline of [...baselines].reverse()) { + try { + await registry.withExclusiveCids([baseline.cid], (locked) => + rollbackUpload({ + registry: locked, + cid: baseline.cid, + previous: baseline.previous, + written: baseline.written, + createdPin: baseline.createdPin, + unpin: () => baseline.unpin() + }) + ) + } catch (err) { + log.error(`Could not undo a refused upload: ${(err as Error).message}`) + } + } +} + /** * Build the handler that turns imported parts into durable, registered files. * @@ -53,22 +94,32 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ const files = flatFiles(req.files as UnixFsMulterFile[]) log.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) - // Hold all involved CIDs until this request either commits or restores - // every pin and lifecycle record it changed. Sorting happens inside the - // registry, so overlapping multi-file requests cannot deadlock. - const outcome = await registry.withExclusiveCids( + // Hold every CID this request touches while its pin and its record are + // written, and no longer than that. Sorting happens inside the registry, + // so overlapping multi-file requests cannot deadlock. + const admitted = await registry.withExclusiveCids( files.map((file) => file.cid.toString()), async (locked) => { - const undo: Array<() => Promise> = [] + const baselines: UploadBaseline[] = [] const restoreBaselines = async (): Promise => { const failures: Error[] = [] - while (undo.length > 0) { - const step = undo.pop() + while (baselines.length > 0) { + const baseline = baselines.pop() + + if (baseline === undefined) { + continue + } try { - await step?.() + await rollbackUpload({ + registry: locked, + cid: baseline.cid, + previous: baseline.previous, + createdPin: baseline.createdPin, + unpin: () => baseline.unpin() + }) } catch (err) { const failure = err as Error failures.push(failure) @@ -88,6 +139,9 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ const cid = file.cid.toString() log.info(`Successfully added file ${cid}`) + const unpin = async (): Promise => { + await unpinFile(node, file.cid) + } const previous = await locked.get(cid) const createdPin = await pinFile(node, file.cid) @@ -110,86 +164,29 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // A datastore failure can happen before or after it touches the // key. Restore both sides from the baseline captured under the // same CID lock, so neither outcome leaks a pin or a record. - await rollbackUpload({ - registry: locked, - cid, - previous, - createdPin, - unpin: async () => { - await unpinFile(node, file.cid) - } - }) + await rollbackUpload({ registry: locked, cid, previous, createdPin, unpin }) throw err } - undo.push(() => - rollbackUpload({ - registry: locked, - cid, - previous: registration.previous, - createdPin, - unpin: async () => { - await unpinFile(node, file.cid) - } - }) - ) - + baselines.push({ + cid, + previous, + createdPin, + written: registration.record, + unpin + }) stored.push(registration.record) } - // One request may contain the same content more than once. Place - // each CID once, using the last registration stored above. - const durableByCid = new Map( - stored - .filter((record) => record.state === 'confirmed') - .map((record) => [record.cid, record] as const) - ) - const durable = [...durableByCid.values()] - const replication = await Promise.all( - durable.map(async (record) => ({ - cid: record.cid, - report: await dependencies.replicate(record.cid) - })) - ) - - if ( - dependencies.requireQuorumOnUpload && - replication.some((item) => !item.report.satisfied) - ) { - // The request is rejected as a transaction. Restoring the exact - // pre-request records and pins keeps a durable re-upload durable - // instead of turning it into an expired, unpinned file. - await restoreBaselines() - await session.cleanup() - - return { records: stored, replication, quorumReached: false } - } - - // Replication performs network work only. Persist acknowledgements - // here under the same locks as registration, so a concurrent - // lifecycle action cannot be overwritten by a late report. - const finalized = new Map() - for (const item of replication) { - const record = durableByCid.get(item.cid) - - if (record === undefined || item.report.mode !== 'quorum') { - continue - } - - finalized.set( - item.cid, - await locked.save({ ...record, replicas: item.report.replicas }) - ) - } - - const records = stored.map((record) => finalized.get(record.cid) ?? record) - - // Every file is stored, pinned, registered and—where enabled—has - // its replication report recorded. Ownership can now pass from the - // request session to the registry before the CID locks are released. + // Every file is stored, pinned and registered, so nothing this + // request wrote is unprotected any more. Ownership passes from the + // session to the registry here rather than after replication: the + // session holds the shared storage lease, and keeping it across a + // network round would stop collection — and, behind it, every other + // upload — for as long as the slowest peer takes to answer. session.commit() - return { records, replication, quorumReached: true } + return { records: stored, baselines } } catch (err) { let failure = err as Error @@ -208,11 +205,81 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } ) + // One request may contain the same content more than once. Place each CID + // once, using the registration that stored it last. + const durableByCid = new Map( + admitted.records + .filter((record) => record.state === 'confirmed') + .map((record) => [record.cid, record] as const) + ) + + let replication + try { + replication = await Promise.all( + [...durableByCid.values()].map(async (record) => ({ + cid: record.cid, + report: await dependencies.replicate(record.cid) + })) + ) + } catch (err) { + // Placement reports failures rather than throwing, so this is something + // unforeseen. The request is undone rather than half-kept: the caller is + // told it failed, and nothing durable is left claiming otherwise. + await rollbackAdmitted(registry, admitted.baselines, log) + throw err + } + + const outcome = { + records: admitted.records, + replication, + quorumReached: + !dependencies.requireQuorumOnUpload || replication.every((item) => item.report.satisfied) + } + + if (!outcome.quorumReached) { + // The request is rejected as a transaction. Restoring the exact + // pre-request records and pins keeps a durable re-upload durable + // instead of turning it into an expired, unpinned file, and leaves a + // brand-new one with neither record nor pin. + // + // The blocks stay on disk, unprotected, until the collector runs. That + // is what releasing means everywhere else here, and it is the price of + // not holding the storage lease across replication. + await rollbackAdmitted(registry, admitted.baselines, log) + + return res.status(503).send({ error: 'Replication quorum not reached' }) + } + + // Acknowledgements are written after the network work, each under its own + // CID lock and only while the record is still the one this request wrote. + // A late report must not overwrite a lifecycle somebody else has since + // changed. + const finalized = new Map() + for (const item of replication) { + const written = durableByCid.get(item.cid) + + if (written === undefined || item.report.mode !== 'quorum') { + continue + } + + const updated = await registry.transition(item.cid, async (current) => { + if (current === undefined || current.revision !== written.revision) { + return 'keep' + } + + return { ...current, replicas: item.report.replicas } + }) + + if (updated !== undefined) { + finalized.set(item.cid, updated) + } + } + if (!outcome.quorumReached) { return res.status(503).send({ error: 'Replication quorum not reached' }) } - const { records, replication } = outcome + const records = outcome.records.map((record) => finalized.get(record.cid) ?? record) const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) res.send({ diff --git a/src/storage/metrics.ts b/src/storage/metrics.ts index fea4871..76d9b3b 100644 --- a/src/storage/metrics.ts +++ b/src/storage/metrics.ts @@ -2,7 +2,7 @@ import { config } from '../config.js' import { blockstorePath, datastorePath } from '../store.js' import { logger } from '../utils/logger.js' import { availableStorageSize, dirSize } from '../utils/utils.js' -import { countByState, protectedStorageBytes, type FileState } from './registry.js' +import { countByState, protectedStorageBytes, type FileRecord, type FileState } from './registry.js' import { fileRegistry } from './state.js' export interface StorageMetrics { @@ -53,6 +53,24 @@ let cached: StorageMetrics = { * Directory scans and a full registry sweep are too expensive for a request * path, so this runs on the disk usage schedule and the API serves the cache. */ +/** + * Bytes the pins on this node protect. + * + * Measured from the DAG each record protects, not from the blocks its upload + * happened to write: content that was already on disk writes none, and counting + * that as zero reports a pinned file as reclaimable space. + * + * Exported so the sum can be checked without a node behind it — the reduction + * is the part that decides what the report claims. + */ +export function pinnedStorageBytes( + records: Array> +): number { + return records + .filter((record) => record.pinned) + .reduce((total, record) => total + protectedStorageBytes(record), 0) +} + export async function refreshStorageMetrics(): Promise { const [blockstoreBytes, datastoreBytes, availableBytes, records] = await Promise.all([ dirSize(blockstorePath), @@ -61,9 +79,7 @@ export async function refreshStorageMetrics(): Promise { fileRegistry.all() ]) - const pinnedBytes = records - .filter((record) => record.pinned) - .reduce((total, record) => total + protectedStorageBytes(record), 0) + const pinnedBytes = pinnedStorageBytes(records) const reservedBytes = config.storage.diskReserveBytes diff --git a/src/storage/rollback.ts b/src/storage/rollback.ts index 75c2c2a..1597bb1 100644 --- a/src/storage/rollback.ts +++ b/src/storage/rollback.ts @@ -4,6 +4,14 @@ export interface UploadRollback { /** Registry view whose CID lock the caller already holds. */ registry: LockedFileRegistry cid: string + /** + * The record this request stored, when the lock was released in between. + * + * A rollback decided while the lock was held needs no such check. One that + * runs after replication does: another request may have written to the CID + * meanwhile, and undoing its work would take away a lifecycle it owns. + */ + written?: FileRecord /** What that write replaced, as reported by the write itself. */ previous?: FileRecord /** Whether the pin on this CID was created by this request. */ @@ -23,6 +31,15 @@ export interface UploadRollback { * is not. */ export async function rollbackUpload(options: UploadRollback): Promise { + if (options.written !== undefined) { + const current = await options.registry.get(options.cid) + + // Somebody else wrote after this request did; the CID is theirs now. + if (current?.revision !== options.written.revision) { + return + } + } + // `pinFile` returns true only when there was no direct pin before this // request. Keep a newly repaired pin only when the earlier record expected // one; otherwise restore the real state as well as its metadata. diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts index ab515d4..3a8b7c5 100644 --- a/test/integration/uploadRoute.test.ts +++ b/test/integration/uploadRoute.test.ts @@ -242,6 +242,28 @@ describe('the upload endpoint end to end', () => { } }) + it('leaves neither record nor pin when new content misses strict quorum', async () => { + // The other direction of the same rule. A pre-existing file must survive a + // refused re-upload, and content this request introduced must not survive + // the refusal — the caller was told it was rejected. + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => ({ ...bestEffort(), mode: 'quorum' as const, satisfied: false }) + }) + const payload = deterministicBytes(70_000, 'route-new-quorum') + + try { + const response = await post(harness.url, [{ name: 'fresh.bin', bytes: payload }]) + const cid = (await ifs.addBytes(payload)).toString() + + assert.equal(response.status, 503) + assert.equal(await harness.registry.get(cid), undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + } finally { + harness.server.close() + } + }) + it('restores a pre-existing durable file when a re-upload misses strict quorum', async () => { let satisfied = true const harness = await serveUpload({ diff --git a/test/storageMetrics.test.ts b/test/storageMetrics.test.ts new file mode 100644 index 0000000..3b6e653 --- /dev/null +++ b/test/storageMetrics.test.ts @@ -0,0 +1,45 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { pinnedStorageBytes } from '../src/storage/metrics.js' +import type { FileRecord } from '../src/storage/registry.js' + +type Measured = Pick + +const file = (overrides: Partial = {}): Measured => ({ + pinned: true, + fileSize: 1000, + storedBytes: 1000, + protectedBytes: 1000, + ...overrides +}) + +describe('pinnedStorageBytes', () => { + it('measures what a pin protects, not what its upload happened to write', () => { + // An upload of content this node already held writes no blocks at all. Its + // pin still protects the whole DAG, and reporting zero would put that + // storage in the reclaimable column. + const cachedReupload = file({ storedBytes: 0, protectedBytes: 90_000, fileSize: 90_000 }) + + assert.equal(pinnedStorageBytes([cachedReupload]), 90_000) + }) + + it('ignores content no pin protects', () => { + assert.equal(pinnedStorageBytes([file({ pinned: false })]), 0) + }) + + it('adds up every pinned record', () => { + assert.equal( + pinnedStorageBytes([file({ protectedBytes: 10 }), file({ protectedBytes: 25 })]), + 35 + ) + }) + + it('falls back for records written before the DAG size was stored', () => { + // The write delta is exact for a first import, and the logical size keeps a + // cached re-upload from reading as nothing until it is registered again. + assert.equal( + pinnedStorageBytes([file({ protectedBytes: undefined, storedBytes: 0, fileSize: 700 })]), + 700 + ) + }) +}) From 917f491232e041f7d9e4271ff9c4d4b071682773 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Wed, 26 Aug 2026 10:35:54 +0300 Subject: [PATCH 126/167] feat: enhance replication protocol with transaction support - Added support for staging, committing, and aborting uploads in the replication protocol. - Updated ReplicationAck and ReplicationStoreResult interfaces to include transaction-related fields. - Modified replicate function to handle transaction IDs and report on staged copies. - Implemented requestStage, requestCommit, and requestAbort functions for managing upload transactions. - Enhanced rollback functionality to preserve upload ownership across metadata changes. - Updated tests to cover new transaction features and ensure correct behavior during replication and rollback scenarios. --- README.md | 19 +- docs/storage-lifecycle.md | 37 ++- src/api/file.ts | 4 + src/api/uploadRoute.ts | 267 +++++++++++---- src/storage/gc.ts | 35 +- src/storage/lifecycle.ts | 8 +- src/storage/registry.ts | 88 ++++- src/storage/replicaStage.ts | 329 +++++++++++++++++++ src/storage/replication.ts | 27 +- src/storage/replicationProtocol.ts | 100 +++++- src/storage/rollback.ts | 29 +- src/storage/service.ts | 177 +++++++++- test/fileRegistry.test.ts | 16 + test/garbageCollectionPlan.test.ts | 20 ++ test/integration/replicationProtocol.test.ts | 45 ++- test/integration/storageLifecycle.test.ts | 124 +++++++ test/integration/uploadRoute.test.ts | 250 +++++++++++++- test/replication.test.ts | 22 ++ test/rollback.test.ts | 58 ++++ 19 files changed, 1527 insertions(+), 128 deletions(-) create mode 100644 src/storage/replicaStage.ts diff --git a/README.md b/README.md index e3f721e..9225645 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ This is an explicit compatibility decision, not an authorization guarantee. A de The multipart contract accepts `files` parts only. Text fields are rejected with a controlled `400 Bad Request` response. -An interrupted upload no longer leaves blocks behind. Each request owns a session that records the blocks it created; a rejected, aborted, or partially failed request removes exactly those, skipping blocks that already existed, blocks a concurrent upload is still writing, and blocks a pin protects. Whatever survives cleanup is unpinned and reclaimable by garbage collection. Blockstore growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the collection watermarks — see [docs/storage-lifecycle.md](docs/storage-lifecycle.md). +An interrupted upload no longer leaves blocks behind. Each request owns a session that records the blocks it created; a rejected, aborted, or partially failed request removes exactly those, skipping blocks that already existed, blocks a concurrent upload is still writing, and blocks a pin protects. Whatever survives cleanup is unpinned and reclaimable by garbage collection. Strict-quorum uploads prepare rollback-capable remote pins and commit them only after the local decision is durable. Blockstore growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the collection watermarks — see [docs/storage-lifecycle.md](docs/storage-lifecycle.md). ### CORS @@ -275,14 +275,15 @@ Example response: Upload responses use the following stable status contract: -| Status | Meaning | -| -------------------------- | -------------------------------------------------------------------------- | -| `200 OK` | Every file was stored and pinned | -| `400 Bad Request` | No file was sent, too many files, or a single file exceeded its size limit | -| `413 Payload Too Large` | The combined size of the files exceeded `storage.maxRequestSizeBytes` | -| `429 Too Many Requests` | The upload rate limit or the concurrent upload limit was exceeded | -| `503 Service Unavailable` | The replication quorum was required and could not be reached | -| `507 Insufficient Storage` | Storing the request would consume `storage.diskReserveBytes` | +| Status | Meaning | +| --------------------------- | -------------------------------------------------------------------------- | +| `200 OK` | Every file was stored and pinned | +| `400 Bad Request` | No file was sent, too many files, or a single file exceeded its size limit | +| `413 Payload Too Large` | The combined size of the files exceeded `storage.maxRequestSizeBytes` | +| `429 Too Many Requests` | The upload rate limit or the concurrent upload limit was exceeded | +| `503 Service Unavailable` | The replication quorum was required and could not be reached | +| `507 Insufficient Storage` | Storing the request would consume `storage.diskReserveBytes` | +| `500 Internal Server Error` | Storage or replica settlement failed; do not treat it as a clean rejection | ### Check the state of a file diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index b47c2a8..1f081cd 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -80,10 +80,11 @@ pinned, and registered. Any other outcome removes the recorded blocks: Pinning and lifecycle registration are one transaction for concurrency purposes. Before changing either, a request locks every CID it contains in -lexical order and holds those locks through commit or rollback. Another upload, -confirmation, release, collector or handover therefore sees either the state -before the request or the state after it, never a pin from one side and a -registry record from the other. Ordering the locks also keeps two overlapping +lexical order. It releases those locks before network placement, then takes the +same ordered set again for the final local decision. An internal ownership token +lets that decision and a rejected upload compensate their own writes without +overwriting a confirmation, release, collector, handover, or re-upload that +adopted the CID meanwhile. Ordering the locks also keeps two overlapping multi-file requests from deadlocking. A recorded block is deleted only when no other in-flight upload references it and @@ -147,7 +148,9 @@ Collection applies three independent rules: 2. **Watermarks.** When the blockstore grows above `storage.gc.highWatermarkBytes`, the oldest unconfirmed files are released until the estimated size drops below `storage.gc.lowWatermarkBytes`. The gap - between the two thresholds stops the collector from running on every tick. + between the two thresholds stops the collector from running on every tick. A + live transaction-owned replica is retained until its source commits or + aborts it; if the source disappears, its TTL makes it eligible normally. 3. **Handover.** A confirmed file whose copies belong on other nodes is released here once those nodes confirm they hold it. This is how an ageing file drops from four holders to two without ever losing durability; see @@ -235,6 +238,22 @@ A file is placed on a subset of the ADAMANT nodes. The peer pulls the DAG over libp2p and pins it before answering, so an acknowledgement means the copy exists and is protected there, not that a request was queued. +When `replication.requireQuorumOnUpload` is enabled, the local and remote copies +form one rollback-capable upload decision. Each peer first records its copy as a +temporary, transaction-owned pin. If the required acknowledgements are not +reached, the source withdraws every prepared copy and restores its own exact +pre-upload pin and registry state before returning `503`. If any local or remote +compensation fails, the route returns `500` instead: it must not describe an +incomplete rollback as a clean rejection. + +After the local decision is durably recorded, the source commits the prepared +copies. A lost commit acknowledgement does not turn an accepted request into a +failure: the remote copy remains pinned and temporary, repair treats it as +missing until it is promoted, and its TTL eventually restores the prior expired +state or releases a newly created copy if the source never returns. Transaction +owners and their restoration baselines live in the datastore, so restarting a +peer does not turn a prepared copy into a permanent one. + Turning it off with `replication.enabled: false` makes the node store content **best effort** instead: one copy, on this node, pinned, with no promise that any other node holds it. `GET /api/storage/policy` reports @@ -407,9 +426,11 @@ travels by bitswap. Startup peering opens those connections before the API starts serving, so the first upload after a restart already has somewhere to place its copies. -A copy request that a peer cannot answer in time is reported as a shortfall -rather than failing the upload, and the repair job places the missing copies on -its next pass. +A copy request that a peer cannot answer in time is reported as a shortfall. In +best-effort mode the upload stays accepted and repair places the missing copy on +its next pass. With strict quorum enabled, the upload is accepted only when the +configured acknowledgement threshold was reached; otherwise its prepared +copies are withdrawn as described above. ### Protocol version diff --git a/src/api/file.ts b/src/api/file.ts index 50b3168..a297d12 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -5,6 +5,8 @@ import { admitUpload, getUploadSession } from '../middleware/uploadGuards.js' import { readLimiter, uploadLimiter } from '../middleware/rateLimiter.js' import { multerStorage } from '../multer.js' import { + abortUploadedReplicas, + commitUploadedReplicas, confirmFile, effectiveQuorum, prepareFileRetrieval, @@ -48,6 +50,8 @@ router.post( temporaryTtlMs: config.storage.temporaryTtlMs, requireQuorumOnUpload: config.replication.requireQuorumOnUpload, replicate: replicateUploadedFile, + commitReplicas: commitUploadedReplicas, + abortReplicas: abortUploadedReplicas, log: logger }) ) diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index 9e35f61..424bd8d 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -1,3 +1,4 @@ +import { randomUUID } from 'node:crypto' import type { NextFunction, Request, RequestHandler, Response } from 'express' import type { IpfsNode } from '../ipfs-node.js' import { pinFile, unpinFile } from '../storage/pinning.js' @@ -24,7 +25,14 @@ export interface UploadRouteDependencies { temporaryTtlMs: number /** Whether an upload that missed its quorum is refused rather than kept. */ requireQuorumOnUpload: boolean - replicate: (cid: string) => Promise + /** Prepare remote copies when `transactionId` is present. */ + replicate: (cid: string, transactionId?: string) => Promise + /** Make prepared copies permanent after the local decision commits. */ + commitReplicas: (cid: string, transactionId: string, report: ReplicationReport) => Promise + /** Withdraw prepared copies before reporting a strict rejection. */ + abortReplicas: (cid: string, transactionId: string, report: ReplicationReport) => Promise + /** Test seam for pin-datastore failure paths. */ + unpin?: (cid: UnixFsMulterFile['cid']) => Promise log: UploadRouteLog } @@ -34,39 +42,127 @@ interface UploadBaseline { previous?: FileRecord /** Whether this request created the pin. */ createdPin: boolean - /** The record this request stored. */ - written: FileRecord unpin: () => Promise } /** * Undo an admitted request whose quorum was refused afterwards. * - * Each CID is taken again on its own, because the locks were released for the - * network work. Only a record still carrying this request's write is rolled - * back; anything written since belongs to somebody else. + * The ordered CID set is taken again because the locks were released for the + * network work. Only records still carrying this request's ownership token are + * rolled back; anything a later lifecycle adopted is left alone. */ async function rollbackAdmitted( registry: FileRegistry, baselines: UploadBaseline[], + admissionId: string, log: UploadRouteLog ): Promise { - for (const baseline of [...baselines].reverse()) { + const failures: Error[] = [] + + await registry.withExclusiveCids( + baselines.map((baseline) => baseline.cid), + async (locked) => { + for (const baseline of [...baselines].reverse()) { + try { + await rollbackUpload({ + registry: locked, + cid: baseline.cid, + previous: baseline.previous, + admissionId, + createdPin: baseline.createdPin, + unpin: baseline.unpin + }) + } catch (err) { + const failure = err as Error + failures.push(failure) + log.error(`Could not undo a refused upload: ${failure.message}`) + } + } + } + ) + + if (failures.length > 0) { + throw new AggregateError(failures, 'Could not fully undo the refused upload') + } +} + +/** + * Commit every local record as one guarded decision after network placement. + * + * Replica metadata is written first while the admission token is retained on + * every CID. If any write fails, every record can still be compensated under + * the same set of locks. Clearing the internal tokens afterwards is best effort: + * the durable upload decision has already been made, and a cleanup write must + * not turn an accepted upload into an ambiguous failure. + */ +async function finalizeAdmitted( + registry: FileRegistry, + records: FileRecord[], + baselines: UploadBaseline[], + reports: Map, + admissionId: string, + log: UploadRouteLog +): Promise> { + const byCid = new Map(records.map((record) => [record.cid, record])) + + return registry.withExclusiveCids(byCid.keys(), async (locked) => { + const currentByCid = new Map() + + for (const [cid] of byCid) { + const current = await locked.get(cid) + + if (current === undefined || current.admissionId !== admissionId) { + throw new Error(`Upload lifecycle changed before commit for ${cid}`) + } + + currentByCid.set(cid, current) + } + + const finalized = new Map() + try { - await registry.withExclusiveCids([baseline.cid], (locked) => - rollbackUpload({ - registry: locked, - cid: baseline.cid, - previous: baseline.previous, - written: baseline.written, - createdPin: baseline.createdPin, - unpin: () => baseline.unpin() + for (const [cid, current] of currentByCid) { + const report = reports.get(cid) + const stored = await locked.save({ + ...current, + replicas: report?.mode === 'quorum' ? report.replicas : current.replicas }) - ) + finalized.set(cid, stored) + } } catch (err) { - log.error(`Could not undo a refused upload: ${(err as Error).message}`) + const failures: unknown[] = [err] + + for (const baseline of [...baselines].reverse()) { + try { + await rollbackUpload({ + registry: locked, + cid: baseline.cid, + previous: baseline.previous, + admissionId, + createdPin: baseline.createdPin, + unpin: baseline.unpin + }) + } catch (rollbackError) { + failures.push(rollbackError) + } + } + + throw new AggregateError(failures, 'Could not finalize or restore the local upload', { + cause: err + }) } - } + + for (const [cid, stored] of finalized) { + try { + finalized.set(cid, await locked.save({ ...stored, admissionId: undefined })) + } catch (err) { + log.error(`Could not clear settled upload ownership for ${cid}: ${(err as Error).message}`) + } + } + + return finalized + }) } /** @@ -89,6 +185,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } const session = dependencies.getSession(req) + const admissionId = randomUUID() try { const files = flatFiles(req.files as UnixFsMulterFile[]) @@ -100,18 +197,12 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ const admitted = await registry.withExclusiveCids( files.map((file) => file.cid.toString()), async (locked) => { - const baselines: UploadBaseline[] = [] + const baselines = new Map() const restoreBaselines = async (): Promise => { const failures: Error[] = [] - while (baselines.length > 0) { - const baseline = baselines.pop() - - if (baseline === undefined) { - continue - } - + for (const baseline of [...baselines.values()].reverse()) { try { await rollbackUpload({ registry: locked, @@ -140,7 +231,11 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ log.info(`Successfully added file ${cid}`) const unpin = async (): Promise => { - await unpinFile(node, file.cid) + if (dependencies.unpin !== undefined) { + await dependencies.unpin(file.cid) + } else { + await unpinFile(node, file.cid) + } } const previous = await locked.get(cid) const createdPin = await pinFile(node, file.cid) @@ -157,7 +252,8 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ }, { confirmationRequired: dependencies.confirmationRequired, - temporaryTtlMs: dependencies.temporaryTtlMs + temporaryTtlMs: dependencies.temporaryTtlMs, + admissionId } ) } catch (err) { @@ -168,13 +264,9 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ throw err } - baselines.push({ - cid, - previous, - createdPin, - written: registration.record, - unpin - }) + if (!baselines.has(cid)) { + baselines.set(cid, { cid, previous, createdPin, unpin }) + } stored.push(registration.record) } @@ -186,7 +278,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // upload — for as long as the slowest peer takes to answer. session.commit() - return { records: stored, baselines } + return { records: stored, baselines: [...baselines.values()] } } catch (err) { let failure = err as Error @@ -218,14 +310,25 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ replication = await Promise.all( [...durableByCid.values()].map(async (record) => ({ cid: record.cid, - report: await dependencies.replicate(record.cid) + report: await dependencies.replicate( + record.cid, + dependencies.requireQuorumOnUpload ? admissionId : undefined + ) })) ) } catch (err) { // Placement reports failures rather than throwing, so this is something // unforeseen. The request is undone rather than half-kept: the caller is // told it failed, and nothing durable is left claiming otherwise. - await rollbackAdmitted(registry, admitted.baselines, log) + try { + await rollbackAdmitted(registry, admitted.baselines, admissionId, log) + } catch (rollbackError) { + throw new AggregateError( + [err, rollbackError], + 'Replication failed and the upload could not be fully restored', + { cause: rollbackError } + ) + } throw err } @@ -245,38 +348,84 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // The blocks stay on disk, unprotected, until the collector runs. That // is what releasing means everywhere else here, and it is the price of // not holding the storage lease across replication. - await rollbackAdmitted(registry, admitted.baselines, log) + const rollbackFailures: unknown[] = [] + + for (const item of replication) { + try { + await dependencies.abortReplicas(item.cid, admissionId, item.report) + } catch (err) { + rollbackFailures.push(err) + log.error( + `Could not abort prepared replicas for ${item.cid}: ${(err as Error).message}` + ) + } + } + + try { + await rollbackAdmitted(registry, admitted.baselines, admissionId, log) + } catch (err) { + rollbackFailures.push(err) + } + + if (rollbackFailures.length > 0) { + throw new AggregateError( + rollbackFailures, + 'Replication quorum was missed and rollback was incomplete' + ) + } return res.status(503).send({ error: 'Replication quorum not reached' }) } - // Acknowledgements are written after the network work, each under its own - // CID lock and only while the record is still the one this request wrote. - // A late report must not overwrite a lifecycle somebody else has since - // changed. - const finalized = new Map() - for (const item of replication) { - const written = durableByCid.get(item.cid) + // Commit the local decision before making prepared remote copies + // permanent. A lifecycle that changed during placement rejects the whole + // request instead of counting a local copy that no longer exists. + const reportByCid = new Map(replication.map((item) => [item.cid, item.report])) + let finalized: Map - if (written === undefined || item.report.mode !== 'quorum') { - continue - } + try { + finalized = await finalizeAdmitted( + registry, + admitted.records, + admitted.baselines, + reportByCid, + admissionId, + log + ) + } catch (err) { + const failures: unknown[] = [err] - const updated = await registry.transition(item.cid, async (current) => { - if (current === undefined || current.revision !== written.revision) { - return 'keep' + for (const item of replication) { + try { + await dependencies.abortReplicas(item.cid, admissionId, item.report) + } catch (abortError) { + failures.push(abortError) } + } - return { ...current, replicas: item.report.replicas } - }) - - if (updated !== undefined) { - finalized.set(item.cid, updated) + try { + await rollbackAdmitted(registry, admitted.baselines, admissionId, log) + } catch (rollbackError) { + failures.push(rollbackError) } + + throw new AggregateError(failures, 'Upload decision could not be committed or restored', { + cause: err + }) } - if (!outcome.quorumReached) { - return res.status(503).send({ error: 'Replication quorum not reached' }) + if (dependencies.requireQuorumOnUpload) { + for (const item of replication) { + try { + await dependencies.commitReplicas(item.cid, admissionId, item.report) + } catch (err) { + // The local decision is already durable. A prepared remote copy is + // pinned until its TTL, and repair promotes it with permanent store. + log.error( + `Could not commit prepared replicas for ${item.cid}: ${(err as Error).message}` + ) + } + } } const records = outcome.records.map((record) => finalized.get(record.cid) ?? record) diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 79cf0a3..500921a 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -2,6 +2,7 @@ import { CID } from 'multiformats/cid' import type { IpfsNode } from '../ipfs-node.js' import type { GarbageCollectionConfig } from './config.js' import { isProtected, pinFile, unpinFile } from './pinning.js' +import { restoreReplicaStage } from './replicaStage.js' import { FileRegistry, isExpired, protectedStorageBytes, type FileRecord } from './registry.js' export type Watermarks = Pick @@ -59,17 +60,25 @@ export interface GcPlan { * until the estimate drops below the low watermark. Hysteresis between the * two thresholds stops the collector from running on every tick. * - * Confirmed files are never selected, so durable content cannot be reclaimed. + * Confirmed files and live transaction-owned replica stages are never selected, + * so durable content cannot be reclaimed and a strict acknowledgement cannot + * disappear before its source settles it. An abandoned stage becomes eligible + * normally once its TTL expires. */ export function planGarbageCollection(input: GcPlanInput): GcPlan { const now = input.now ?? Date.now() const { highWatermarkBytes, lowWatermarkBytes } = input.watermarks const confirmed = input.records.filter((record) => record.state === 'confirmed') - const releasable = input.records.filter((record) => record.state !== 'confirmed') + const unconfirmed = input.records.filter((record) => record.state !== 'confirmed') - const expired = releasable.filter((record) => isExpired(record, now)) - const alive = releasable.filter((record) => !isExpired(record, now)) + const expired = unconfirmed.filter((record) => isExpired(record, now)) + const prepared = unconfirmed.filter( + (record) => record.replicaStage !== undefined && !isExpired(record, now) + ) + const alive = unconfirmed.filter( + (record) => record.replicaStage === undefined && !isExpired(record, now) + ) // Helia GC removes every unpinned block, including read cache and files a // previous handover already released. Count that space before selecting a @@ -138,7 +147,11 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { trigger, expired, evicted, - retained: [...confirmed, ...alive.filter((record) => !releasedCids.has(record.cid))], + retained: [ + ...confirmed, + ...prepared, + ...alive.filter((record) => !releasedCids.has(record.cid)) + ], estimatedBytesAfter: Math.max(0, estimatedBytesAfter) } } @@ -357,6 +370,14 @@ export async function runGarbageCollection(options: GcRunOptions): Promise + const previous = stage.previous as Partial | null | undefined + const previousIsValid = + previous === null || + (typeof previous === 'object' && + previous !== null && + previous.state === 'expired' && + isNullableTimestamp(previous.expiresAt) && + isNullableTimestamp(previous.confirmedAt) && + previous.pinned === false && + previous.heldLocally === false) + + return ( + Array.isArray(stage.transactionIds) && + stage.transactionIds.length > 0 && + new Set(stage.transactionIds).size === stage.transactionIds.length && + stage.transactionIds.every((id) => typeof id === 'string' && id.length > 0) && + previousIsValid + ) +} + /** Validate the durable shape before lifecycle policy is allowed to trust it. */ function isFileRecord(value: unknown): value is FileRecord { if (typeof value !== 'object' || value === null) { @@ -83,6 +137,16 @@ function isFileRecord(value: unknown): value is FileRecord { record.revision === undefined || (typeof record.revision === 'string' && record.revision.length > 0) || isFiniteNonNegative(record.revision) + const admissionIsValid = + record.admissionId === undefined || + (typeof record.admissionId === 'string' && record.admissionId.length > 0) + const replicaStageIsValid = + record.replicaStage === undefined || + (record.state === 'temporary' && + record.pinned === true && + record.heldLocally === true && + record.expiresAt !== null && + isReplicaStage(record.replicaStage)) return ( typeof record.cid === 'string' && @@ -100,7 +164,9 @@ function isFileRecord(value: unknown): value is FileRecord { typeof record.heldLocally === 'boolean' && Array.isArray(record.replicas) && record.replicas.every((peer) => typeof peer === 'string') && - revisionIsValid + revisionIsValid && + admissionIsValid && + replicaStageIsValid ) } @@ -126,6 +192,14 @@ export interface NewFileRecord { protectedBytes?: number } +export interface RegisterFileOptions { + confirmationRequired: boolean + temporaryTtlMs: number + now?: number + /** Request that owns the write until post-replication settlement. */ + admissionId?: string +} + /** * Registry operations that are safe to compose while their CID locks are held. * @@ -140,7 +214,7 @@ export interface LockedFileRegistry { remove(cid: string): Promise registerReplacing( file: NewFileRecord, - options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } + options: RegisterFileOptions ): Promise<{ record: FileRecord; previous: FileRecord | undefined }> } @@ -452,7 +526,7 @@ export class FileRegistry { */ private async registerExclusively( file: NewFileRecord, - options: { confirmationRequired: boolean; temporaryTtlMs: number; now?: number } + options: RegisterFileOptions ): Promise<{ record: FileRecord; previous: FileRecord | undefined }> { const now = options.now ?? Date.now() const existing = await this.get(file.cid) @@ -469,7 +543,9 @@ export class FileRegistry { ), fileSize: file.fileSize > 0 ? file.fileSize : existing.fileSize, pinned: true, - heldLocally: true + heldLocally: true, + admissionId: options.admissionId, + replicaStage: undefined }) return { record, previous: existing } @@ -489,7 +565,9 @@ export class FileRegistry { protectedBytes: file.protectedBytes ?? Math.max(file.storedBytes, file.fileSize), pinned: true, heldLocally: true, - replicas: existing?.replicas ?? [] + replicas: existing?.replicas ?? [], + admissionId: options.admissionId, + replicaStage: undefined }) return { record, previous: existing } diff --git a/src/storage/replicaStage.ts b/src/storage/replicaStage.ts new file mode 100644 index 0000000..d382d47 --- /dev/null +++ b/src/storage/replicaStage.ts @@ -0,0 +1,329 @@ +import type { UnixFS } from '@helia/unixfs' +import { CID } from 'multiformats/cid' +import type { IpfsNode } from '../ipfs-node.js' +import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' +import type { + FileRecord, + FileRegistry, + LockedFileRegistry, + ReplicaStageBaseline +} from './registry.js' +import { protectedStorageBytes } from './registry.js' + +export interface ReplicaStageTarget { + node: IpfsNode + unixfs: UnixFS + registry: FileRegistry + cid: CID + transactionId: string + temporaryTtlMs: number + pinTimeoutMs?: number + now?: number +} + +export interface ReplicaStageResult { + record: FileRecord + /** Whether this transaction must later commit or abort the prepared copy. */ + staged: boolean +} + +/** Build the optional deadline shared by one pin and its offline stat. */ +function stageSignal(timeoutMs: number | undefined): AbortSignal | undefined { + return timeoutMs === undefined ? undefined : AbortSignal.timeout(timeoutMs) +} + +/** Restore the pin and record after a failed stage write. */ +async function restoreStageWrite( + registry: LockedFileRegistry, + node: IpfsNode, + cid: CID, + previous: FileRecord | undefined, + createdPin: boolean, + originalError: unknown +): Promise { + const failures: unknown[] = [originalError] + + try { + if (createdPin) { + await unpinFile(node, cid) + } + } catch (err) { + failures.push(err) + } + + try { + if (previous === undefined) { + await registry.remove(cid.toString()) + } else { + await registry.save(previous) + } + } catch (err) { + failures.push(err) + } + + if (failures.length > 1) { + throw new AggregateError(failures, 'Replica staging failed and its baseline was not restored') + } + + throw originalError +} + +/** Persist a pre-existing durable pin as a stable, non-transactional replica. */ +async function registerStableReplica( + registry: LockedFileRegistry, + options: ReplicaStageTarget, + previous: FileRecord | undefined, + createdPin: boolean, + now: number, + signal: AbortSignal | undefined +): Promise { + try { + const stats = await options.unixfs.stat(options.cid, { + extended: true, + offline: true, + signal + }) + const dagBytes = Number(stats.deduplicatedDagSize) + const record = await registry.save({ + cid: options.cid.toString(), + name: previous?.name ?? options.cid.toString(), + state: 'confirmed', + createdAt: previous?.createdAt ?? now, + expiresAt: null, + confirmedAt: previous?.confirmedAt ?? now, + fileSize: Number(stats.size), + storedBytes: Math.max(previous?.storedBytes ?? 0, dagBytes), + protectedBytes: Math.max( + previous === undefined ? 0 : protectedStorageBytes(previous), + dagBytes + ), + pinned: true, + heldLocally: true, + replicas: previous?.replicas ?? [], + admissionId: undefined, + replicaStage: undefined + }) + + return { record, staged: false } + } catch (err) { + return restoreStageWrite(registry, options.node, options.cid, previous, createdPin, err) + } +} + +/** + * Prepare a durable copy for a strict upload without making rejection permanent. + * + * A new copy is pinned as `temporary` and carries every upload transaction that + * currently depends on it. One successful transaction commits the shared copy; + * failed transactions remove only their own claim, and the last failure restores + * the expired baseline or removes a record this staging created. + */ +export async function stageReplica(options: ReplicaStageTarget): Promise { + const key = options.cid.toString() + const now = options.now ?? Date.now() + const signal = stageSignal(options.pinTimeoutMs) + + return options.registry.withExclusiveCids([key], async (registry) => { + const previous = await registry.get(key) + + if (previous?.replicaStage !== undefined) { + const createdPin = await pinFile(options.node, options.cid, signal) + + try { + const transactionIds = previous.replicaStage.transactionIds.includes(options.transactionId) + ? previous.replicaStage.transactionIds + : [...previous.replicaStage.transactionIds, options.transactionId] + const record = await registry.save({ + ...previous, + state: 'temporary', + expiresAt: Math.max(previous.expiresAt ?? 0, now + options.temporaryTtlMs), + pinned: true, + heldLocally: true, + replicaStage: { ...previous.replicaStage, transactionIds } + }) + + return { record, staged: true } + } catch (err) { + return restoreStageWrite(registry, options.node, options.cid, previous, createdPin, err) + } + } + + // A local unconfirmed upload owns this lifecycle. An unrelated replication + // request must not silently confirm it, nor can it safely undo it later. + if (previous?.state === 'temporary') { + throw new Error(`CID ${key} already has an unrelated temporary lifecycle`) + } + + const wasDirectlyPinned = await isDirectlyPinned(options.node, options.cid) + + // Existing pins and confirmed records predate this transaction. Repairing + // their protection is safe to keep even if the new upload is rejected. + if (wasDirectlyPinned || previous?.state === 'confirmed') { + const createdPin = await pinFile(options.node, options.cid, signal) + return registerStableReplica(registry, options, previous, createdPin, now, signal) + } + + const createdPin = await pinFile(options.node, options.cid, signal) + + try { + const stats = await options.unixfs.stat(options.cid, { + extended: true, + offline: true, + signal + }) + const dagBytes = Number(stats.deduplicatedDagSize) + const baseline: ReplicaStageBaseline | null = previous + ? { + state: 'expired', + expiresAt: previous.expiresAt, + confirmedAt: previous.confirmedAt, + pinned: false, + heldLocally: false + } + : null + const record = await registry.save({ + cid: key, + name: previous?.name ?? key, + state: 'temporary', + createdAt: previous?.createdAt ?? now, + expiresAt: now + options.temporaryTtlMs, + confirmedAt: null, + fileSize: Number(stats.size), + storedBytes: Math.max(previous?.storedBytes ?? 0, dagBytes), + protectedBytes: Math.max( + previous === undefined ? 0 : protectedStorageBytes(previous), + dagBytes + ), + pinned: true, + heldLocally: true, + replicas: previous?.replicas ?? [], + admissionId: previous?.admissionId, + replicaStage: { transactionIds: [options.transactionId], previous: baseline } + }) + + return { record, staged: true } + } catch (err) { + return restoreStageWrite(registry, options.node, options.cid, previous, createdPin, err) + } + }) +} + +export interface SettleReplicaOptions { + node: IpfsNode + registry: FileRegistry + cid: CID + transactionId: string + now?: number +} + +/** Make a prepared copy durable. One commit settles every transaction sharing it. */ +export async function commitReplica( + options: SettleReplicaOptions +): Promise { + const key = options.cid.toString() + + return options.registry.withExclusiveCids([key], async (registry) => { + const current = await registry.get(key) + + if (!current?.replicaStage?.transactionIds.includes(options.transactionId)) { + return current + } + + return registry.save({ + ...current, + state: 'confirmed', + expiresAt: null, + confirmedAt: current.confirmedAt ?? options.now ?? Date.now(), + pinned: true, + heldLocally: true, + admissionId: undefined, + replicaStage: undefined + }) + }) +} + +/** + * Restore or remove one prepared replica while its CID lock is held. + * + * @param keepCreatedRecord Keep an `expired` entry for GC accounting. Explicit + * aborts remove entries they created; TTL expiry keeps one until collection. + */ +export async function restoreReplicaStage( + registry: LockedFileRegistry, + node: IpfsNode, + current: FileRecord, + keepCreatedRecord: boolean +): Promise { + const removedPin = await unpinFile(node, currentCid(current)) + + try { + const previous = current.replicaStage?.previous + + if (previous === null && !keepCreatedRecord) { + await registry.remove(current.cid) + return undefined + } + + return registry.save({ + ...current, + ...(previous ?? { + state: 'expired' as const, + expiresAt: current.expiresAt, + confirmedAt: null, + pinned: false as const, + heldLocally: false as const + }), + replicaStage: undefined + }) + } catch (err) { + const failures: unknown[] = [err] + + try { + if (removedPin) { + await pinFile(node, currentCid(current)) + } + await registry.save(current) + } catch (restoreError) { + failures.push(restoreError) + } + + throw new AggregateError( + failures, + 'Replica rollback failed and its staged state was restored', + { + cause: err + } + ) + } +} + +/** Parse a record CID only at the pin boundary. */ +function currentCid(record: FileRecord): CID { + return CID.parse(record.cid) +} + +/** Remove this transaction's claim, restoring the baseline after the last claim. */ +export async function abortReplica(options: SettleReplicaOptions): Promise { + const key = options.cid.toString() + + return options.registry.withExclusiveCids([key], async (registry) => { + const current = await registry.get(key) + const stage = current?.replicaStage + + if ( + current === undefined || + stage === undefined || + !stage.transactionIds.includes(options.transactionId) + ) { + return current + } + + const transactionIds = stage.transactionIds.filter((id) => id !== options.transactionId) + + if (transactionIds.length > 0) { + return registry.save({ ...current, replicaStage: { ...stage, transactionIds } }) + } + + return restoreReplicaStage(registry, options.node, current, false) + }) +} diff --git a/src/storage/replication.ts b/src/storage/replication.ts index 4fb3af4..b036982 100644 --- a/src/storage/replication.ts +++ b/src/storage/replication.ts @@ -29,11 +29,20 @@ export type PlacementOutcome = 'stored' | 'cached' export interface ReplicationAck { node: string + /** Stable identity used to settle a prepared copy without trusting display names. */ + peerId: string ok: boolean outcome?: PlacementOutcome + /** True when this copy still belongs to an upload transaction. */ + staged?: boolean error?: string } +export interface ReplicationStoreResult { + outcome: PlacementOutcome + staged?: boolean +} + export interface ReplicationReport { /** * `quorum` when the node actively places copies on its peers, @@ -74,7 +83,7 @@ export interface ReplicateOptions { peers: ReplicationPeer[] config: ReplicationConfig /** Asks one peer to take a copy, and reports what it agreed to. */ - store: (peer: ReplicationPeer, cid: string) => Promise + store: (peer: ReplicationPeer, cid: string) => Promise /** Asks one peer for an unpinned copy, used to widen a spread nobody pins. */ cacheOnly?: (peer: ReplicationPeer, cid: string) => Promise } @@ -154,9 +163,17 @@ export async function replicate(options: ReplicateOptions): Promise => { try { - return { node: peer.name, ok: true, outcome: await options.store(peer, cid) } + const stored = await options.store(peer, cid) + const result = typeof stored === 'string' ? { outcome: stored } : stored + return { + node: peer.name, + peerId: peer.peerId, + ok: true, + outcome: result.outcome, + staged: result.staged + } } catch (err) { - return { node: peer.name, ok: false, error: (err as Error).message } + return { node: peer.name, peerId: peer.peerId, ok: false, error: (err as Error).message } } }) ) @@ -189,9 +206,9 @@ export async function replicate(options: ReplicateOptions): Promise => { try { await options.cacheOnly?.(peer, cid) - return { node: peer.name, ok: true, outcome: 'cached' } + return { node: peer.name, peerId: peer.peerId, ok: true, outcome: 'cached' } } catch (err) { - return { node: peer.name, ok: false, error: (err as Error).message } + return { node: peer.name, peerId: peer.peerId, ok: false, error: (err as Error).message } } }) ) diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index 6c7b40c..8d530ac 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -37,12 +37,18 @@ export const REPLICATION_PROTOCOL = `/adamant/replication/${REPLICATION_PROTOCOL */ export const SUPPORTED_REPLICATION_PROTOCOLS = [REPLICATION_PROTOCOL] -/** Requests are two short fields; anything larger is a protocol violation. */ +/** Control messages are short; anything larger is a protocol violation. */ const MAX_MESSAGE_BYTES = 4096 export type ReplicationRequest = /** Store and pin `cid`, pulling the blocks from the requesting peer. */ | { op: 'store'; cid: string } + /** Prepare a pin that becomes permanent only after the upload commits. */ + | { op: 'stage'; cid: string; transactionId: string } + /** Make a prepared copy permanent. */ + | { op: 'commit'; cid: string; transactionId: string } + /** Withdraw one upload's claim on a prepared copy. */ + | { op: 'abort'; cid: string; transactionId: string } /** Report whether this node deliberately holds `cid`. */ | { op: 'have'; cid: string } /** Report whether this node has room to take another copy. */ @@ -58,6 +64,9 @@ export type ReplicationRequest = export type ReplicationResponse = | { ok: true; op: 'store'; storedBytes: number } + | { ok: true; op: 'stage'; storedBytes: number; staged: boolean } + | { ok: true; op: 'commit' } + | { ok: true; op: 'abort' } | { ok: true; op: 'have'; has: boolean } | { ok: true; op: 'accept'; willAccept: boolean } | { ok: true; op: 'cache'; cachedBytes: number } @@ -79,6 +88,12 @@ export interface ReplicationHandlers { isAuthorized(peerId: string): boolean /** Pin `cid` and register it, returning the bytes now held for it. */ store(cid: string): Promise + /** Prepare a rollback-capable copy for a strict upload. */ + stage(cid: string, transactionId: string): Promise<{ storedBytes: number; staged: boolean }> + /** Commit a prepared copy. */ + commit(cid: string, transactionId: string): Promise + /** Abort one transaction's claim on a prepared copy. */ + abort(cid: string, transactionId: string): Promise /** Whether this node holds `cid` durably, not merely cached. */ have(cid: string): Promise /** @@ -179,13 +194,31 @@ function parseRequest(value: unknown): ReplicationRequest { throw new Error('Replication request is missing a CID') } - const operations: ReplicationRequest['op'][] = ['store', 'have', 'accept', 'cache'] + const operations: ReplicationRequest['op'][] = [ + 'store', + 'stage', + 'commit', + 'abort', + 'have', + 'accept', + 'cache' + ] const op = operations.find((known) => known === message.op) if (op === undefined) { throw new Error('Unknown replication operation') } + if (op === 'stage' || op === 'commit' || op === 'abort') { + const transactionId = (value as { transactionId?: unknown }).transactionId + + if (typeof transactionId !== 'string' || transactionId.length === 0) { + throw new Error('Replication request is missing a transaction id') + } + + return { op, cid: message.cid, transactionId } + } + return { op, cid: message.cid } } @@ -228,6 +261,24 @@ async function respond( return } + if (request.op === 'stage') { + const staged = await handlers.stage(request.cid, request.transactionId) + sendMessage(stream, { ok: true, op: 'stage', ...staged }) + return + } + + if (request.op === 'commit') { + await handlers.commit(request.cid, request.transactionId) + sendMessage(stream, { ok: true, op: 'commit' }) + return + } + + if (request.op === 'abort') { + await handlers.abort(request.cid, request.transactionId) + sendMessage(stream, { ok: true, op: 'abort' }) + return + } + const storedBytes = await handlers.store(request.cid) sendMessage(stream, { ok: true, op: 'store', storedBytes }) } @@ -296,6 +347,51 @@ export async function requestStore( return response.ok && response.op === 'store' ? response.storedBytes : 0 } +/** Prepare a rollback-capable copy for a strict upload. */ +export async function requestStage( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + transactionId: string, + options: ReplicationCallOptions +): Promise<{ storedBytes: number; staged: boolean }> { + const response = await call(node, peer, { op: 'stage', cid, transactionId }, options) + + return response.ok && response.op === 'stage' + ? { storedBytes: response.storedBytes, staged: response.staged } + : { storedBytes: 0, staged: false } +} + +/** Commit a copy prepared by {@link requestStage}. */ +export async function requestCommit( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + transactionId: string, + options: ReplicationCallOptions +): Promise { + const response = await call(node, peer, { op: 'commit', cid, transactionId }, options) + + if (!response.ok || response.op !== 'commit') { + throw new Error('Unexpected replication commit response') + } +} + +/** Abort one transaction's claim on a prepared copy. */ +export async function requestAbort( + node: IpfsNode, + peer: PeerId | Multiaddr, + cid: string, + transactionId: string, + options: ReplicationCallOptions +): Promise { + const response = await call(node, peer, { op: 'abort', cid, transactionId }, options) + + if (!response.ok || response.op !== 'abort') { + throw new Error('Unexpected replication abort response') + } +} + /** * Ask a peer whether it has room for another copy. * diff --git a/src/storage/rollback.ts b/src/storage/rollback.ts index 1597bb1..a87c0cb 100644 --- a/src/storage/rollback.ts +++ b/src/storage/rollback.ts @@ -4,14 +4,8 @@ export interface UploadRollback { /** Registry view whose CID lock the caller already holds. */ registry: LockedFileRegistry cid: string - /** - * The record this request stored, when the lock was released in between. - * - * A rollback decided while the lock was held needs no such check. One that - * runs after replication does: another request may have written to the CID - * meanwhile, and undoing its work would take away a lifecycle it owns. - */ - written?: FileRecord + /** Request ownership token to check after releasing the lock for replication. */ + admissionId?: string /** What that write replaced, as reported by the write itself. */ previous?: FileRecord /** Whether the pin on this CID was created by this request. */ @@ -23,19 +17,20 @@ export interface UploadRollback { /** * Undo what one file of a failed upload left behind. * - * The caller holds the CID lock from registration through request commit or - * rollback. No other request can adopt the pin between these steps, so this - * first restores the real pin state and only then restores or removes the - * registry entry. If unpinning fails, the current entry deliberately remains: - * a visible record for protected content is recoverable, while an orphan pin - * is not. + * The caller holds the CID lock for this compensation. When network work + * happened between registration and rollback, `admissionId` proves the record + * still belongs to this request; a lifecycle that adopted it is left alone. + * The real pin state is restored before the registry entry. If unpinning fails, + * the current entry deliberately remains: a visible record for protected + * content is recoverable, while an orphan pin is not. */ export async function rollbackUpload(options: UploadRollback): Promise { - if (options.written !== undefined) { + if (options.admissionId !== undefined) { const current = await options.registry.get(options.cid) - // Somebody else wrote after this request did; the CID is theirs now. - if (current?.revision !== options.written.revision) { + // A lifecycle adoption replaces or clears this token. Replica metadata + // updates deliberately preserve it and therefore do not defeat rollback. + if (current?.admissionId !== options.admissionId) { return } } diff --git a/src/storage/service.ts b/src/storage/service.ts index b201751..90897a9 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -14,23 +14,28 @@ import { releaseStoredFile, type LifecycleTarget } from './lifecycle.js' -import type { FileRecord } from './registry.js' +import { protectedStorageBytes, type FileRecord } from './registry.js' import { isUnderReplicated, replicate, requiredAcks, type PlacementOutcome, type ReplicationPeer, - type ReplicationReport + type ReplicationReport, + type ReplicationStoreResult } from './replication.js' import { probeAccept, probeHave, + requestAbort, requestCache, + requestCommit, + requestStage, requestStore, type ReplicationHandlers, type ReplicationCallOptions } from './replicationProtocol.js' +import { abortReplica, commitReplica, stageReplica } from './replicaStage.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { PER_PEER_INTAKE_BYTES, reserveIntake } from './intakeBudget.js' import { @@ -83,14 +88,18 @@ function placementFor(cid: string, createdAt: number, peers: ReplicationPeer[]): * pinned locally stays valid when a peer is unavailable, and the repair job * retries later. */ -async function placeReplicas(cid: string, createdAt: number): Promise { +async function placeReplicas( + cid: string, + createdAt: number, + transactionId?: string +): Promise { return replicate({ cid, ageMs: Math.max(0, Date.now() - createdAt), selfPeerId: selfPeerId(), peers: getReplicationPeers(), config: config.replication, - store: (peer) => placeCopy(peer, cid), + store: (peer) => placeCopy(peer, cid, transactionId), cacheOnly: async (peer) => { await requestCache(helia, peer.multiAddr, cid, callOptions()) } @@ -104,9 +113,69 @@ async function placeReplicas(cid: string, createdAt: number): Promise { +export async function replicateUploadedFile( + cid: string, + transactionId?: string +): Promise { const record = await fileRegistry.get(cid) - return placeReplicas(cid, record?.createdAt ?? Date.now()) + return placeReplicas(cid, record?.createdAt ?? Date.now(), transactionId) +} + +/** Peers whose copies still belong to this upload transaction. */ +function stagedPeers(report: ReplicationReport): ReplicationPeer[] { + const peers = new Map(getReplicationPeers().map((peer) => [peer.peerId, peer])) + + return report.attempts + .filter((attempt) => attempt.ok && attempt.outcome === 'stored' && attempt.staged === true) + .map((attempt) => peers.get(attempt.peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) +} + +/** + * Make every prepared copy permanent after the local upload decision commits. + * + * A lost acknowledgement cannot turn an accepted upload into a rejection. The + * copy remains pinned as temporary, and replication repair probes it as missing + * and promotes it with the idempotent permanent `store` operation. + */ +export async function commitUploadedReplicas( + cid: string, + transactionId: string, + report: ReplicationReport +): Promise { + const peers = stagedPeers(report) + const results = await Promise.allSettled( + peers.map((peer) => requestCommit(helia, peer.multiAddr, cid, transactionId, callOptions())) + ) + + for (const [index, result] of results.entries()) { + if (result.status === 'rejected') { + logger.warn( + `Could not commit prepared replica ${cid} on ${peers[index]?.name}: ${result.reason}` + ) + } + } +} + +/** Withdraw every prepared remote copy before reporting a strict rejection. */ +export async function abortUploadedReplicas( + cid: string, + transactionId: string, + report: ReplicationReport +): Promise { + const peers = stagedPeers(report) + const results = await Promise.allSettled( + peers.map((peer) => requestAbort(helia, peer.multiAddr, cid, transactionId, callOptions())) + ) + const failures = results.flatMap((result, index) => + result.status === 'rejected' + ? [new Error(`Could not abort replica on ${peers[index]?.name}: ${result.reason}`)] + : [] + ) + + if (failures.length > 0) { + throw new AggregateError(failures, `Could not abort every prepared replica for ${cid}`) + } } /** Place copies of a known file and persist the peers that acknowledged it. */ @@ -155,18 +224,27 @@ export async function prepareFileRetrieval(cid: CID): Promise { * offered as an unpinned copy instead: the peer can serve it from then on, and * nothing is counted as durable that is not. */ -async function placeCopy(peer: ReplicationPeer, cid: string): Promise { +async function placeCopy( + peer: ReplicationPeer, + cid: string, + transactionId?: string +): Promise { try { if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { - return 'stored' + return { outcome: 'stored', staged: false } } if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { throw new Error('peer has no room for another copy') } + if (transactionId !== undefined) { + const result = await requestStage(helia, peer.multiAddr, cid, transactionId, callOptions()) + return { outcome: 'stored', staged: result.staged } + } + await requestStore(helia, peer.multiAddr, cid, callOptions()) - return 'stored' + return { outcome: 'stored', staged: false } } catch (err) { if (!(err as Error).message.includes('Not authorized')) { throw err @@ -266,6 +344,66 @@ export async function acceptReplica(cid: string): Promise { }) } +/** Prepare a remote copy that can still be withdrawn after a strict miss. */ +export async function acceptStagedReplica( + cid: string, + transactionId: string +): Promise<{ storedBytes: number; staged: boolean }> { + return storageOperationLock.withShared(async () => { + await pullUnderIntakeLimits(cid) + // A deliberately short ordinary-upload TTL must not expire a replica while + // the source can still be waiting for its other placements and settlement + // calls. Three request windows cover both phases and scheduling overhead. + const settlementTtlMs = Math.max( + config.storage.temporaryTtlMs, + config.replication.requestTimeoutMs * 3 + ) + const result = await stageReplica({ + ...lifecycleTarget(), + unixfs: ifs, + cid: CID.parse(cid), + transactionId, + temporaryTtlMs: settlementTtlMs, + pinTimeoutMs: config.replication.requestTimeoutMs + }) + + return { storedBytes: protectedStorageBytes(result.record), staged: result.staged } + }) +} + +/** A prepared transaction becomes a normal durable holder. */ +export async function commitStagedReplica(cid: string, transactionId: string): Promise { + await storageOperationLock.withShared(async () => { + await commitReplica({ ...lifecycleTarget(), cid: CID.parse(cid), transactionId }) + }) +} + +/** Withdraw one source upload's claim on a prepared copy. */ +export async function abortStagedReplica(cid: string, transactionId: string): Promise { + await storageOperationLock.withShared(async () => { + await abortReplica({ ...lifecycleTarget(), cid: CID.parse(cid), transactionId }) + }) +} + +/** True only for a permanent replica, not for a prepared strict-upload copy. */ +async function hasDurableReplica(cid: string): Promise { + const parsed = CID.parse(cid) + const record = await fileRegistry.get(cid) + + if (record === undefined) { + // A pin awaiting startup backfill predates transactional replication. + return isDirectlyPinned(helia, parsed) + } + + return ( + record.state === 'confirmed' && + record.pinned && + record.heldLocally && + record.replicaStage === undefined && + (await isDirectlyPinned(helia, parsed)) + ) +} + /** * Behaviour this node exposes on the replication protocol. * @@ -278,7 +416,10 @@ export function createReplicationHandlers(): ReplicationHandlers { return { isAuthorized: (peerId) => getReplicationPeers().some((peer) => peer.peerId === peerId), store: async (cid) => (await acceptReplica(cid)).storedBytes, - have: async (cid) => isDirectlyPinned(helia, CID.parse(cid)), + stage: acceptStagedReplica, + commit: commitStagedReplica, + abort: abortStagedReplica, + have: hasDurableReplica, willAccept: hasRoomForAnotherCopy, cacheCopy: cacheFileLocally, onError: (message) => logger.warn(message), @@ -520,7 +661,13 @@ async function rescueOrphanedFiles( const createdPin = await pinFile(helia, cid) try { - return await registry.save({ ...current, pinned: true, heldLocally: true }) + return await registry.save({ + ...current, + pinned: true, + heldLocally: true, + admissionId: undefined, + replicaStage: undefined + }) } catch (err) { if (createdPin && current.pinned !== true) { await unpinFile(helia, cid) @@ -719,7 +866,13 @@ export async function demoteReleasableCopies( const removedPin = await unpinFile(helia, parsed) try { - await registry.save({ ...current, pinned: false, heldLocally: false }) + await registry.save({ + ...current, + pinned: false, + heldLocally: false, + admissionId: undefined, + replicaStage: undefined + }) return true } catch (err) { if (removedPin && current.pinned) { diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 804e7de..2822b08 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -411,6 +411,22 @@ describe('FileRegistry', () => { assert.equal(await registry.get(CID_A), undefined) }) + it('preserves upload ownership across replica metadata refreshes', async () => { + const registry = createRegistry() + const written = await registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(newFile(), { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'upload-one' + }) + ) + + const refreshed = await registry.setReplicas(CID_A, ['n2']) + + assert.notEqual(refreshed?.revision, written.record.revision) + assert.equal(refreshed?.admissionId, 'upload-one') + }) + it('refuses a public mutator called from inside its own lock', async () => { // The two ways to change a record — the locking mutators and the view // handed to withExclusiveCids — cannot be mixed: the inner call queues diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts index a799858..3a1d648 100644 --- a/test/garbageCollectionPlan.test.ts +++ b/test/garbageCollectionPlan.test.ts @@ -183,4 +183,24 @@ describe('planGarbageCollection', () => { assert.deepEqual(plan.evicted, []) assert.equal(plan.estimatedBytesAfter, 500) }) + + it('does not evict a live replica stage before its source settles it', () => { + const staged = record({ + cid: 'prepared', + state: 'temporary', + expiresAt: 9000, + replicaStage: { transactionIds: ['upload-1'], previous: null } + }) + const plan = planGarbageCollection({ + blockstoreBytes: 5000, + watermarks, + records: [staged], + now: 1000 + }) + + assert.equal(plan.shouldCollect, true) + assert.deepEqual(plan.expired, []) + assert.deepEqual(plan.evicted, []) + assert.deepEqual(plan.retained, [staged]) + }) }) diff --git a/test/integration/replicationProtocol.test.ts b/test/integration/replicationProtocol.test.ts index 72e2f02..94a28c1 100644 --- a/test/integration/replicationProtocol.test.ts +++ b/test/integration/replicationProtocol.test.ts @@ -8,11 +8,14 @@ import { FsBlockstore } from 'blockstore-fs' import { FsDatastore } from 'datastore-fs' import { CID } from 'multiformats/cid' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' -import { isDirectlyPinned, pinFile } from '../../src/storage/pinning.js' +import { isDirectlyPinned, pinFile, unpinFile } from '../../src/storage/pinning.js' import { probeAccept, probeHave, registerReplicationProtocol, + requestAbort, + requestCommit, + requestStage, requestStore, type ReplicationHandlers } from '../../src/storage/replicationProtocol.js' @@ -55,6 +58,8 @@ let holderFs: UnixFS let authorized: Set let refusals: string[] let hasRoom = true +let committed: string[] +let aborted: string[] before(async () => { sender = await startNode() @@ -64,6 +69,8 @@ before(async () => { authorized = new Set([sender.node.libp2p.peerId.toString()]) refusals = [] + committed = [] + aborted = [] const handlers: ReplicationHandlers = { isAuthorized: (peerId) => authorized.has(peerId), @@ -72,6 +79,18 @@ before(async () => { const stats = await holderFs.stat(CID.parse(cid), { extended: true, offline: true }) return Number(stats.deduplicatedDagSize) }, + stage: async (cid) => { + await pinFile(holder.node, CID.parse(cid), AbortSignal.timeout(CALL.timeoutMs)) + const stats = await holderFs.stat(CID.parse(cid), { extended: true, offline: true }) + return { storedBytes: Number(stats.deduplicatedDagSize), staged: true } + }, + commit: async (cid, transactionId) => { + committed.push(`${cid}:${transactionId}`) + }, + abort: async (cid, transactionId) => { + aborted.push(`${cid}:${transactionId}`) + await unpinFile(holder.node, CID.parse(cid)) + }, have: async (cid) => isDirectlyPinned(holder.node, CID.parse(cid)), willAccept: async () => hasRoom, cacheCopy: async (cid) => { @@ -178,4 +197,28 @@ describe('replication over libp2p', () => { }) ) }) + + it('stages and settles a strict-upload copy with its transaction id', async () => { + const commitCid = await senderFs.addBytes(deterministicBytes(1200, 'protocol-commit')) + const abortCid = await senderFs.addBytes(deterministicBytes(1300, 'protocol-abort')) + const peer = holder.node.libp2p.getMultiaddrs()[0] + + const preparedCommit = await requestStage( + sender.node, + peer, + commitCid.toString(), + 'tx-commit', + CALL + ) + assert.equal(preparedCommit.staged, true) + assert.equal(await isDirectlyPinned(holder.node, commitCid), true) + + await requestCommit(sender.node, peer, commitCid.toString(), 'tx-commit', CALL) + assert.deepEqual(committed, [`${commitCid}:tx-commit`]) + + await requestStage(sender.node, peer, abortCid.toString(), 'tx-abort', CALL) + await requestAbort(sender.node, peer, abortCid.toString(), 'tx-abort', CALL) + assert.deepEqual(aborted, [`${abortCid}:tx-abort`]) + assert.equal(await isDirectlyPinned(holder.node, abortCid), false) + }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index b338d86..3f2bd62 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -33,6 +33,7 @@ import { ConcurrencyLimiter } from '../../src/storage/limits.js' import { StorageOperationLock } from '../../src/storage/operationLock.js' import { isDirectlyPinned, isProtected, pinFile, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry, type FileRecord } from '../../src/storage/registry.js' +import { abortReplica, commitReplica, stageReplica } from '../../src/storage/replicaStage.js' import { UploadSession } from '../../src/storage/uploadSession.js' import { deterministicBytes } from '../fixtures.js' @@ -1297,6 +1298,129 @@ describe('lifecycle transitions', () => { }) }) +describe('strict-upload replica staging', () => { + const stage = (registry: FileRegistry, cid: CID, transactionId: string, now = 1000) => + stageReplica({ + node, + unixfs: ifs, + registry, + cid, + transactionId, + temporaryTtlMs: 1000, + now + }) + + it('removes a new prepared copy when its transaction aborts', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(20_000, 'replica-stage-abort')) + + const prepared = await stage(registry, cid, 'tx-abort') + + assert.equal(prepared.staged, true) + assert.equal(prepared.record.state, 'temporary') + assert.deepEqual(prepared.record.replicaStage?.transactionIds, ['tx-abort']) + assert.equal(await isDirectlyPinned(node, cid), true) + + await abortReplica({ node, registry, cid, transactionId: 'tx-abort' }) + + assert.equal(await registry.get(cid.toString()), undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + + it('keeps a shared stage until one transaction commits it', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_000, 'replica-stage-shared')) + + await stage(registry, cid, 'tx-one') + await stage(registry, cid, 'tx-two') + await abortReplica({ node, registry, cid, transactionId: 'tx-one' }) + + assert.deepEqual((await registry.get(cid.toString()))?.replicaStage?.transactionIds, ['tx-two']) + assert.equal(await isDirectlyPinned(node, cid), true) + + await commitReplica({ node, registry, cid, transactionId: 'tx-two', now: 3000 }) + + const committed = await registry.get(cid.toString()) + assert.equal(committed?.state, 'confirmed') + assert.equal(committed?.replicaStage, undefined) + assert.equal(committed?.confirmedAt, 3000) + assert.equal(await isDirectlyPinned(node, cid), true) + }) + + it('restores an expired record after a prepared copy aborts', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(22_000, 'replica-stage-restore')) + const previous = await registry.save({ + cid: cid.toString(), + name: 'released.bin', + state: 'expired', + createdAt: 100, + expiresAt: 200, + confirmedAt: 150, + fileSize: 22_000, + storedBytes: 22_000, + protectedBytes: 22_000, + pinned: false, + heldLocally: false, + replicas: ['old-holder'] + }) + + await stage(registry, cid, 'tx-restore') + await abortReplica({ node, registry, cid, transactionId: 'tx-restore' }) + + const restored = await registry.get(cid.toString()) + assert.equal(restored?.state, previous.state) + assert.equal(restored?.expiresAt, previous.expiresAt) + assert.equal(restored?.confirmedAt, previous.confirmedAt) + assert.equal(restored?.name, previous.name) + assert.deepEqual(restored?.replicas, previous.replicas) + assert.equal(restored?.replicaStage, undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + + it('expires a stage left behind by a disappeared coordinator', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(23_000, 'replica-stage-expiry')) + + await stage(registry, cid, 'tx-lost', 1000) + await runGarbageCollection({ + node, + registry, + watermarks: WATERMARKS, + blockstoreBytes: 23_000, + now: 2001, + withCollectionLease: runWithoutOtherWriters + }) + + const expired = await registry.get(cid.toString()) + assert.equal(expired?.state, 'expired') + assert.equal(expired?.replicaStage, undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + + it('keeps a live stage pinned while collection runs under pressure', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(24_000, 'replica-stage-pressure')) + + await stage(registry, cid, 'tx-active', 1000) + const report = await runGarbageCollection({ + node, + registry, + watermarks: { highWatermarkBytes: 1, lowWatermarkBytes: 0 }, + blockstoreBytes: 24_000, + now: 1500, + withCollectionLease: runWithoutOtherWriters + }) + + const prepared = await registry.get(cid.toString()) + assert.equal(report.collected, true) + assert.deepEqual(report.releasedCids, []) + assert.equal(prepared?.state, 'temporary') + assert.deepEqual(prepared?.replicaStage?.transactionIds, ['tx-active']) + assert.equal(await isDirectlyPinned(node, cid), true) + }) +}) + describe('registry backfill', () => { it('records a pin that predates the registry, and does so only once', async () => { const registry = createRegistry() diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts index 3a8b7c5..02cb3a3 100644 --- a/test/integration/uploadRoute.test.ts +++ b/test/integration/uploadRoute.test.ts @@ -13,9 +13,10 @@ import { CID } from 'multiformats/cid' import { createUploadHandler } from '../../src/api/uploadRoute.js' import { createIpfsNode, type IpfsNode } from '../../src/ipfs-node.js' import { createUploadAdmission, getUploadSession } from '../../src/middleware/uploadAdmission.js' +import { releaseStoredFile } from '../../src/storage/lifecycle.js' import { ConcurrencyLimiter } from '../../src/storage/limits.js' import { StorageOperationLock } from '../../src/storage/operationLock.js' -import { isDirectlyPinned } from '../../src/storage/pinning.js' +import { isDirectlyPinned, unpinFile } from '../../src/storage/pinning.js' import { FileRegistry } from '../../src/storage/registry.js' import type { ReplicationReport } from '../../src/storage/replication.js' import { resetClaims } from '../../src/storage/reservation.js' @@ -64,8 +65,15 @@ interface Harness { */ async function serveUpload( overrides: { - replicate?: (cid: string) => Promise + replicate?: (cid: string, transactionId?: string) => Promise requireQuorumOnUpload?: boolean + commitReplicas?: ( + cid: string, + transactionId: string, + report: ReplicationReport + ) => Promise + abortReplicas?: (cid: string, transactionId: string, report: ReplicationReport) => Promise + unpin?: (cid: CID) => Promise } = {} ): Promise { prefix += 1 @@ -103,6 +111,9 @@ async function serveUpload( replicated.push(cid) return bestEffort() }), + commitReplicas: overrides.commitReplicas ?? (async () => undefined), + abortReplicas: overrides.abortReplicas ?? (async () => undefined), + unpin: overrides.unpin, log: { info: () => undefined, error: () => undefined } }) ) @@ -264,6 +275,241 @@ describe('the upload endpoint end to end', () => { } }) + it('rolls one CID back once when identical parts miss strict quorum', async () => { + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => ({ ...bestEffort(), mode: 'quorum' as const, satisfied: false }) + }) + const payload = deterministicBytes(71_000, 'route-duplicate-quorum') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const response = await post(harness.url, [ + { name: 'same-one.bin', bytes: payload }, + { name: 'same-two.bin', bytes: payload } + ]) + + assert.equal(response.status, 503) + assert.equal(await harness.registry.get(cid), undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + } finally { + harness.server.close() + } + }) + + it('rolls back through a concurrent replica metadata refresh', async () => { + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async (cid) => { + await harness.registry.setReplicas(cid, ['repair-observation']) + return { ...bestEffort(), mode: 'quorum' as const, satisfied: false } + } + }) + const payload = deterministicBytes(72_000, 'route-replica-race') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const response = await post(harness.url, [{ name: 'race.bin', bytes: payload }]) + + assert.equal(response.status, 503) + assert.equal(await harness.registry.get(cid), undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + } finally { + harness.server.close() + } + }) + + it('aborts every prepared peer before returning a strict rejection', async () => { + const aborted: Array<{ cid: string; transactionId: string }> = [] + const report: ReplicationReport = { + ...bestEffort(), + mode: 'quorum', + desiredCopies: 3, + copies: 3, + required: 3, + acknowledged: 2, + replicas: ['holder'], + satisfied: false, + networkTooSmall: true, + attempts: [ + { + node: 'holder', + peerId: 'holder-peer-id', + ok: true, + outcome: 'stored', + staged: true + } + ] + } + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => report, + abortReplicas: async (cid, transactionId) => { + aborted.push({ cid, transactionId }) + } + }) + const payload = deterministicBytes(73_000, 'route-remote-abort') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const response = await post(harness.url, [{ name: 'remote.bin', bytes: payload }]) + + assert.equal(response.status, 503) + assert.deepEqual( + aborted.map((item) => item.cid), + [cid] + ) + assert.ok(aborted[0]?.transactionId.length > 0) + assert.equal(await harness.registry.get(cid), undefined) + } finally { + harness.server.close() + } + }) + + it('does not report a clean rejection when remote compensation fails', async () => { + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => ({ ...bestEffort(), mode: 'quorum' as const, satisfied: false }), + abortReplicas: async () => { + throw new Error('remote abort unavailable') + } + }) + const payload = deterministicBytes(74_000, 'route-remote-abort-fails') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const response = await post(harness.url, [{ name: 'uncertain.bin', bytes: payload }]) + + assert.equal(response.status, 500) + assert.equal(await harness.registry.get(cid), undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + } finally { + harness.server.close() + } + }) + + it('does not report a clean rejection when local unpinning fails', async () => { + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => ({ ...bestEffort(), mode: 'quorum' as const, satisfied: false }), + unpin: async () => { + throw new Error('pin datastore unavailable') + } + }) + const payload = deterministicBytes(76_000, 'route-local-rollback-fails') + const parsed = await ifs.addBytes(payload) + const cid = parsed.toString() + + try { + const response = await post(harness.url, [{ name: 'protected.bin', bytes: payload }]) + + assert.equal(response.status, 500) + assert.equal((await harness.registry.get(cid))?.state, 'confirmed') + assert.equal(await isDirectlyPinned(node, parsed), true) + } finally { + await unpinFile(node, parsed) + await harness.registry.remove(cid) + harness.server.close() + } + }) + + it('does not accept a strict upload after another lifecycle releases its local copy', async () => { + const aborted: string[] = [] + const report: ReplicationReport = { + ...bestEffort(), + mode: 'quorum', + desiredCopies: 2, + copies: 2, + required: 2, + acknowledged: 2, + replicas: ['holder'], + satisfied: true, + networkTooSmall: true, + attempts: [ + { + node: 'holder', + peerId: 'holder-peer-id', + ok: true, + outcome: 'stored', + staged: true + } + ] + } + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async (cid) => { + await releaseStoredFile({ node, registry: harness.registry, cid: CID.parse(cid) }) + return report + }, + abortReplicas: async (cid) => { + aborted.push(cid) + } + }) + const payload = deterministicBytes(77_000, 'route-lifecycle-race') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const response = await post(harness.url, [{ name: 'released.bin', bytes: payload }]) + const record = await harness.registry.get(cid) + + assert.equal(response.status, 500) + assert.deepEqual(aborted, [cid]) + assert.equal(record?.state, 'expired') + assert.equal(record?.heldLocally, false) + assert.equal(record?.admissionId, undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + } finally { + harness.server.close() + } + }) + + it('keeps an accepted upload durable when a remote commit acknowledgement is lost', async () => { + let commitAttempts = 0 + const report: ReplicationReport = { + ...bestEffort(), + mode: 'quorum', + desiredCopies: 2, + copies: 2, + required: 2, + acknowledged: 2, + replicas: ['holder'], + satisfied: true, + networkTooSmall: true, + attempts: [ + { + node: 'holder', + peerId: 'holder-peer-id', + ok: true, + outcome: 'stored', + staged: true + } + ] + } + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => report, + commitReplicas: async () => { + commitAttempts += 1 + throw new Error('commit acknowledgement lost') + } + }) + const payload = deterministicBytes(78_000, 'route-commit-ack-lost') + + try { + const response = await post(harness.url, [{ name: 'accepted.bin', bytes: payload }]) + const body = (await response.json()) as { cids: string[] } + const record = await harness.registry.get(body.cids[0]) + + assert.equal(response.status, 200) + assert.equal(commitAttempts, 1) + assert.equal(record?.state, 'confirmed') + assert.equal(record?.admissionId, undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(body.cids[0])), true) + } finally { + harness.server.close() + } + }) + it('restores a pre-existing durable file when a re-upload misses strict quorum', async () => { let satisfied = true const harness = await serveUpload({ diff --git a/test/replication.test.ts b/test/replication.test.ts index 6503b9d..af3f3db 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -147,6 +147,28 @@ describe('replicate', () => { assert.ok(report.attempts.every((attempt) => !attempt.ok)) }) + it('reports which accepted copies still belong to the upload transaction', async () => { + let first = true + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: { ...baseConfig, ackQuorum: 3 }, + store: async () => { + if (first) { + first = false + return { outcome: 'stored', staged: true } + } + throw new Error('offline') + } + }) + + assert.equal(report.satisfied, false) + assert.equal(report.acknowledged, 2) + assert.equal(report.attempts.filter((attempt) => attempt.staged === true).length, 1) + }) + it('does not demand a quorum the network cannot reach', async () => { const single = [peer('ipfs2')] diff --git a/test/rollback.test.ts b/test/rollback.test.ts index 0b1fed4..30352d2 100644 --- a/test/rollback.test.ts +++ b/test/rollback.test.ts @@ -171,4 +171,62 @@ describe('rollbackUpload', () => { assert.equal(await registry.get(CID_A), undefined) assert.equal(pinned, false) }) + + it('rolls back after metadata changes but not after lifecycle adoption', async () => { + const registry = createRegistry() + let pinned = true + + await registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'mine' + }) + ) + await registry.setReplicas(CID_A, ['repair-observation']) + + await registry.withExclusiveCids([CID_A], (locked) => + rollbackUpload({ + registry: locked, + cid: CID_A, + admissionId: 'mine', + previous: undefined, + createdPin: true, + unpin: async () => { + pinned = false + } + }) + ) + + assert.equal(await registry.get(CID_A), undefined) + assert.equal(pinned, false) + + pinned = true + await registry.withExclusiveCids([CID_A], async (locked) => { + await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'mine' + }) + await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'theirs' + }) + + await rollbackUpload({ + registry: locked, + cid: CID_A, + admissionId: 'mine', + previous: undefined, + createdPin: true, + unpin: async () => { + pinned = false + } + }) + }) + + assert.equal((await registry.get(CID_A))?.admissionId, 'theirs') + assert.equal(pinned, true) + }) }) From 7af3463adf504625858882f1047d6272a4af8029 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 26 Aug 2026 13:26:45 +0300 Subject: [PATCH 127/167] fix(storage): enhance replication and upload handling with strict quorum requirements - Updated configuration to enforce that `ackQuorum` must be at least 2 when `requireQuorumOnUpload` is true, ensuring a remote copy is part of the upload decision. - Improved documentation for `ackQuorum` and `requireQuorumOnUpload` to clarify their interactions. - Enhanced storage metrics to track in-flight strict uploads and their states. - Updated tests to validate the new strict quorum requirements and ensure correct behavior during replication and upload processes. --- README.md | 2 +- config.default.json5 | 4 +- docs/storage-lifecycle.md | 36 ++++++--- src/config.ts | 7 ++ src/replication.cron.ts | 8 ++ src/storage/config.ts | 16 +++- src/storage/gc.ts | 5 +- src/storage/metrics.ts | 16 ++-- src/storage/registry.ts | 17 +++++ src/storage/replicaStage.ts | 8 +- src/storage/replication.ts | 21 +++++- src/storage/replicationProtocol.ts | 8 +- src/storage/service.ts | 65 ++++++++++------ test/config.test.ts | 12 +++ test/fileRegistry.test.ts | 72 +++++++++++++++++- test/integration/storageLifecycle.test.ts | 91 +++++++++++++++++++++++ test/replication.test.ts | 16 ++++ test/storageConfig.test.ts | 7 ++ 18 files changed, 354 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 9225645..f719b86 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Invalid CORS, proxy, API-key, or rate-limit configuration stops the process inst | Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | | Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | | Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | -| Peer replication | libp2p `/adamant/replication/1.0.0`, not an HTTP route | Authenticated by the libp2p handshake; accepted only from the peers listed in `nodes` | +| Peer replication | libp2p `/adamant/replication/1.1.0`, not an HTTP route | Authenticated by the libp2p handshake; accepted only from the peers listed in `nodes` | | Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | | Authenticated user | None | The service has no end-user identity or session layer | diff --git a/config.default.json5 b/config.default.json5 index ab4e813..c5d119f 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -90,8 +90,8 @@ { minAgeMs: 15552000000, copies: 3 }, // older than 180 days { minAgeMs: 31536000000, copies: 2 } // older than 365 days ], - ackQuorum: 1, // Acknowledgements required before an upload is durable - requireQuorumOnUpload: false, // true rejects an upload that cannot reach the quorum + ackQuorum: 1, // Including this node. Strict uploads need >= 2 so a remote copy is required. + requireQuorumOnUpload: false, // Incompatible with confirmationRequired; needs ackQuorum >= 2 requestTimeoutMs: 30000, repairEnabled: true, repairSchedule: '0 */30 * * * *' diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 1f081cd..b393d64 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -130,7 +130,7 @@ than being open, so a node cannot be filled with permanently pinned content by an anonymous caller. Replication between nodes is not an HTTP route and uses no key at all. It runs -on the libp2p protocol `/adamant/replication/1.0.0`, where the handshake already +on the libp2p protocol `/adamant/replication/1.1.0`, where the handshake already proves the calling peer id cryptographically. Nothing has to be distributed, and a peer needs no HTTP address to be reachable. @@ -239,9 +239,14 @@ libp2p and pins it before answering, so an acknowledgement means the copy exists and is protected there, not that a request was queued. When `replication.requireQuorumOnUpload` is enabled, the local and remote copies -form one rollback-capable upload decision. Each peer first records its copy as a -temporary, transaction-owned pin. If the required acknowledgements are not -reached, the source withdraws every prepared copy and restores its own exact +form one rollback-capable upload decision. `ackQuorum` counts the local copy, so +the flag requires `ackQuorum >= 2`: otherwise the upload succeeds before any +peer is asked. It cannot be combined with `storage.confirmationRequired`, which +would skip placement until a later confirm that uses permanent `store`. + +Each peer first records its copy as a temporary, transaction-owned pin. If the +required acknowledgements are not reached, the source withdraws every prepared +copy — including peers whose `stage` ack was lost — and restores its own exact pre-upload pin and registry state before returning `503`. If any local or remote compensation fails, the route returns `500` instead: it must not describe an incomplete rollback as a clean rejection. @@ -249,10 +254,12 @@ incomplete rollback as a clean rejection. After the local decision is durably recorded, the source commits the prepared copies. A lost commit acknowledgement does not turn an accepted request into a failure: the remote copy remains pinned and temporary, repair treats it as -missing until it is promoted, and its TTL eventually restores the prior expired -state or releases a newly created copy if the source never returns. Transaction -owners and their restoration baselines live in the datastore, so restarting a -peer does not turn a prepared copy into a permanent one. +missing until it is promoted, and its TTL (three replication request timeouts) +eventually restores the prior expired state or releases a newly created copy if +the source never returns. Transaction owners and their restoration baselines +live in the datastore, so restarting a peer does not turn a prepared copy into a +permanent one. Live stages are kept through watermark pressure for that short +window; `GET /api/storage/metrics` reports how many remain as `stagedReplicas`. Turning it off with `replication.enabled: false` makes the node store content **best effort** instead: one copy, on this node, pinned, with no promise that @@ -434,15 +441,17 @@ copies are withdrawn as described above. ### Protocol version -Copies are placed over the libp2p protocol `/adamant/replication/1.0.0`. The +Copies are placed over the libp2p protocol `/adamant/replication/1.1.0`. The version in that identifier belongs to the wire format, not to the release: it says what two nodes must agree on to talk to each other, and that changes far less often than the software. Taking it from `package.json` would break interoperability on every release for no reason. Raise it when an older node can no longer read what a newer one sends — a new -required field, different framing, or an operation whose meaning changed. Adding -an operation that older nodes never send does not require it. +required field, different framing, or an operation this node sends that an older +handler would reject. Adding an operation that older nodes never send does not +require it. `stage`, `commit`, and `abort` are sent by this node, which is why +the identifier is `1.1.0`. libp2p negotiates the newest version both ends offer, so listing an older one alongside the current one is what allows a network to be upgraded one node at a @@ -485,6 +494,11 @@ operational cost. - `availableBytes` — free space on the blockstore filesystem - `reservedBytes` and `usableBytes` — the disk reserve and what is left for uploads - `files` — how many files are in each lifecycle state +- `stagedReplicas` — how many registry entries still belong to an in-flight + strict upload + +`replication.lastRun` on the same payload is counts only: the CID lists from a +repair pass stay on `POST /api/storage/repair`, which is behind the admin key. A collection report also lists `demoted`: files whose local copy was handed over to their designated holders during that pass. diff --git a/src/config.ts b/src/config.ts index 027c5ab..fc885d8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -253,6 +253,13 @@ export function validateConfig(raw: unknown): Config { const storage = resolveStorageConfig(root.storage, root.uploadLimitSizeBytes as number) const replication = resolveReplicationConfig(root.replication) + if (storage.confirmationRequired && replication.requireQuorumOnUpload) { + fail( + 'replication.requireQuorumOnUpload', + 'cannot be true while storage.confirmationRequired is true' + ) + } + return { nodes: nodes.map(({ name, multiAddr }) => ({ name, multiAddr })), storeFolder, diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 278680d..a0ce706 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -62,5 +62,13 @@ export function getReplicationState() { repairSchedule: config.replication.repairSchedule, running, lastRun: lastReport + ? { + checked: lastReport.checked, + underReplicated: lastReport.underReplicated, + repaired: lastReport.repaired.length, + stillMissing: lastReport.stillMissing.length, + rescued: lastReport.rescued.length + } + : null } } diff --git a/src/storage/config.ts b/src/storage/config.ts index e3983c1..e5b2863 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -70,9 +70,14 @@ export interface ReplicationConfig { * which would record when users fetch their files. */ placement: PlacementTier[] - /** Acknowledgements required before an upload is reported as durable. */ + /** Acknowledgements required before an upload is reported as durable, including this node. */ ackQuorum: number - /** When true an upload fails unless the acknowledgement quorum is reached. */ + /** + * When true an upload fails unless the acknowledgement quorum is reached. + * + * Requires `ackQuorum >= 2` so a remote copy is part of the decision, and + * cannot be combined with `storage.confirmationRequired`. + */ requireQuorumOnUpload: boolean /** Timeout of a single replication request to a peer node. */ requestTimeoutMs: number @@ -313,5 +318,12 @@ export function resolveReplicationConfig(raw: unknown): ReplicationConfig { fail('replication.ackQuorum', 'must not exceed the largest copy count in replication.placement') } + if (replication.requireQuorumOnUpload && replication.ackQuorum < 2) { + fail( + 'replication.ackQuorum', + 'must be >= 2 when replication.requireQuorumOnUpload is true, so a strict upload cannot succeed with only the local copy' + ) + } + return replication } diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 500921a..8554ebb 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -62,8 +62,9 @@ export interface GcPlan { * * Confirmed files and live transaction-owned replica stages are never selected, * so durable content cannot be reclaimed and a strict acknowledgement cannot - * disappear before its source settles it. An abandoned stage becomes eligible - * normally once its TTL expires. + * disappear before its source settles it. Stage TTL is a few request timeouts, + * so an abandoned stage becomes eligible shortly afterwards rather than after + * the ordinary unconfirmed-upload lifetime. */ export function planGarbageCollection(input: GcPlanInput): GcPlan { const now = input.now ?? Date.now() diff --git a/src/storage/metrics.ts b/src/storage/metrics.ts index 76d9b3b..a764e55 100644 --- a/src/storage/metrics.ts +++ b/src/storage/metrics.ts @@ -31,6 +31,8 @@ export interface StorageMetrics { /** Free bytes uploads may still use. */ usableBytes: number files: Record & { total: number } + /** Registry entries that still belong to an in-flight strict upload. */ + stagedReplicas: number /** Unix epoch milliseconds of the last successful refresh. */ updatedAt: number | null } @@ -44,15 +46,10 @@ let cached: StorageMetrics = { reservedBytes: config.storage.diskReserveBytes, usableBytes: 0, files: { temporary: 0, confirmed: 0, expired: 0, total: 0 }, + stagedReplicas: 0, updatedAt: null } -/** - * Recompute the storage report. - * - * Directory scans and a full registry sweep are too expensive for a request - * path, so this runs on the disk usage schedule and the API serves the cache. - */ /** * Bytes the pins on this node protect. * @@ -71,6 +68,12 @@ export function pinnedStorageBytes( .reduce((total, record) => total + protectedStorageBytes(record), 0) } +/** + * Recompute the storage report. + * + * Directory scans and a full registry sweep are too expensive for a request + * path, so this runs on the disk usage schedule and the API serves the cache. + */ export async function refreshStorageMetrics(): Promise { const [blockstoreBytes, datastoreBytes, availableBytes, records] = await Promise.all([ dirSize(blockstorePath), @@ -92,6 +95,7 @@ export async function refreshStorageMetrics(): Promise { reservedBytes, usableBytes: Math.max(0, availableBytes - reservedBytes), files: { ...countByState(records), total: records.length }, + stagedReplicas: records.filter((record) => record.replicaStage !== undefined).length, updatedAt: Date.now() } diff --git a/src/storage/registry.ts b/src/storage/registry.ts index ea88745..236ef7f 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -531,6 +531,12 @@ export class FileRegistry { const now = options.now ?? Date.now() const existing = await this.get(file.cid) + // A permanent store or repair must not confirm a copy a strict upload can + // still abort. A later local upload passes `admissionId` and takes over. + if (existing?.replicaStage !== undefined && options.admissionId === undefined) { + return { record: existing, previous: existing } + } + if (existing?.state === 'confirmed') { // Re-uploading already durable content must not weaken its state, and it // puts the blocks back on this node whether or not they were released. @@ -615,6 +621,17 @@ export function isReclaimable(record: FileRecord, now: number = Date.now()): boo return record.state !== 'confirmed' && isExpired(record, now) } +/** + * Durable local copy that no in-flight upload still owns. + * + * Repair and handover skip records that still carry an admission token: those + * writes are mid-transaction, and treating them as settled would let `store` + * confirm a replica the upload can still abort. + */ +export function isSettledHeldFile(record: FileRecord): boolean { + return record.state === 'confirmed' && record.heldLocally && record.admissionId === undefined +} + export function countByState(records: FileRecord[]): Record { const counts: Record = { temporary: 0, confirmed: 0, expired: 0 } for (const record of records) { diff --git a/src/storage/replicaStage.ts b/src/storage/replicaStage.ts index d382d47..0540086 100644 --- a/src/storage/replicaStage.ts +++ b/src/storage/replicaStage.ts @@ -100,7 +100,7 @@ async function registerStableReplica( pinned: true, heldLocally: true, replicas: previous?.replicas ?? [], - admissionId: undefined, + admissionId: previous?.admissionId, replicaStage: undefined }) @@ -127,7 +127,7 @@ export async function stageReplica(options: ReplicaStageTarget): Promise [peer.peerId, peer])) return report.attempts - .filter((attempt) => attempt.ok && attempt.outcome === 'stored' && attempt.staged === true) + .filter( + (attempt) => + attempt.staged === true && + (options.includeFailed || (attempt.ok && attempt.outcome === 'stored')) + ) .map((attempt) => peers.get(attempt.peerId)) .filter((peer): peer is ReplicationPeer => peer !== undefined) } @@ -135,15 +142,16 @@ function stagedPeers(report: ReplicationReport): ReplicationPeer[] { * Make every prepared copy permanent after the local upload decision commits. * * A lost acknowledgement cannot turn an accepted upload into a rejection. The - * copy remains pinned as temporary, and replication repair probes it as missing - * and promotes it with the idempotent permanent `store` operation. + * copy remains pinned as temporary. Repair skips records that still carry an + * `admissionId`; once that token is cleared it treats the staged copy as + * missing and promotes it with the idempotent permanent `store` operation. */ export async function commitUploadedReplicas( cid: string, transactionId: string, report: ReplicationReport ): Promise { - const peers = stagedPeers(report) + const peers = stagedPeers(report, { includeFailed: false }) const results = await Promise.allSettled( peers.map((peer) => requestCommit(helia, peer.multiAddr, cid, transactionId, callOptions())) ) @@ -163,7 +171,7 @@ export async function abortUploadedReplicas( transactionId: string, report: ReplicationReport ): Promise { - const peers = stagedPeers(report) + const peers = stagedPeers(report, { includeFailed: true }) const results = await Promise.allSettled( peers.map((peer) => requestAbort(helia, peer.multiAddr, cid, transactionId, callOptions())) ) @@ -239,8 +247,18 @@ async function placeCopy( } if (transactionId !== undefined) { - const result = await requestStage(helia, peer.multiAddr, cid, transactionId, callOptions()) - return { outcome: 'stored', staged: result.staged } + try { + const result = await requestStage(helia, peer.multiAddr, cid, transactionId, callOptions()) + return { outcome: 'stored', staged: result.staged } + } catch (err) { + if ((err as Error).message.includes('Not authorized')) { + throw err + } + + // The peer may have pinned before the ack was lost. Abort must still + // reach it; the copy must not count towards the quorum. + return { outcome: 'failed', staged: true, error: (err as Error).message } + } } await requestStore(helia, peer.multiAddr, cid, callOptions()) @@ -330,6 +348,14 @@ export async function releaseFile(cid: string): Promise /** Store a copy requested by another ADAMANT node. */ export async function acceptReplica(cid: string): Promise { return storageOperationLock.withShared(async () => { + const existing = await fileRegistry.get(cid) + + if (existing?.replicaStage !== undefined) { + // A strict upload still owns this copy. Promoting it would make abort a + // no-op. + return existing + } + // Pulling the DAG first is what bounds this. Pinning would fetch it too, but // with no limit on how many transfers run at once, how much space they may // take together, or how large the content may be. The `accept` probe that @@ -351,13 +377,11 @@ export async function acceptStagedReplica( ): Promise<{ storedBytes: number; staged: boolean }> { return storageOperationLock.withShared(async () => { await pullUnderIntakeLimits(cid) - // A deliberately short ordinary-upload TTL must not expire a replica while - // the source can still be waiting for its other placements and settlement - // calls. Three request windows cover both phases and scheduling overhead. - const settlementTtlMs = Math.max( - config.storage.temporaryTtlMs, - config.replication.requestTimeoutMs * 3 - ) + // Three request windows cover stage, the peer's own work, and commit or + // abort. The ordinary unconfirmed-upload TTL is a day; inheriting it would + // pin an abandoned stage through watermark pressure for far longer than + // settlement can take. + const settlementTtlMs = config.replication.requestTimeoutMs * 3 const result = await stageReplica({ ...lifecycleTarget(), unixfs: ifs, @@ -715,10 +739,7 @@ export async function repairReplication(): Promise { const peers = getReplicationPeers() const self = selfPeerId() const records = await fileRegistry.all() - const candidates = nextSweepBatch( - 'repair', - records.filter((record) => record.state === 'confirmed' && record.heldLocally) - ) + const candidates = nextSweepBatch('repair', records.filter(isSettledHeldFile)) report.checked = candidates.length @@ -804,9 +825,7 @@ export async function demoteReleasableCopies( const candidates = nextSweepBatch( 'demote', - (await fileRegistry.all()).filter( - (record) => record.state === 'confirmed' && record.heldLocally - ), + (await fileRegistry.all()).filter(isSettledHeldFile), // A dry run must not take the batch: the real pass that follows would then // resume past the very files the operator was shown. { advance: options.dryRun !== true } diff --git a/test/config.test.ts b/test/config.test.ts index 8ef841d..0c1185d 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -154,6 +154,18 @@ describe('validateConfig', () => { /origin/i ) }) + + it('rejects strict quorum while confirmation is still required', () => { + assert.throws( + () => + validateConfig({ + ...validRaw(), + storage: { confirmationRequired: true }, + replication: { ackQuorum: 2, requireQuorumOnUpload: true } + }), + /requireQuorumOnUpload/ + ) + }) }) describe('the config loaded at startup', () => { diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 2822b08..865d70f 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -5,7 +5,13 @@ import { join } from 'node:path' import { after, before, describe, it } from 'node:test' import { FsDatastore } from 'datastore-fs' import { Key } from 'interface-datastore' -import { FileRegistry, isExpired, isReclaimable, type FileRecord } from '../src/storage/registry.js' +import { + FileRegistry, + isExpired, + isReclaimable, + isSettledHeldFile, + type FileRecord +} from '../src/storage/registry.js' const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' const CID_B = 'bafkreihfzyt2g6pbd4rhk67deek7xh33xams74spn72eqq5qhx2ypphvii' @@ -427,6 +433,62 @@ describe('FileRegistry', () => { assert.equal(refreshed?.admissionId, 'upload-one') }) + it('does not confirm a live replica stage through a permanent store', async () => { + const registry = createRegistry() + const staged = await registry.save({ + cid: CID_A, + name: CID_A, + state: 'temporary', + createdAt: 1, + expiresAt: 2, + confirmedAt: null, + fileSize: 100, + storedBytes: 120, + protectedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [], + replicaStage: { transactionIds: ['tx-live'], previous: null } + }) + + const stored = await registerFile(registry) + + assert.equal(stored.record.state, 'temporary') + assert.deepEqual(stored.record.replicaStage?.transactionIds, ['tx-live']) + assert.equal(stored.record.revision, staged.revision) + }) + + it('lets a later local upload take over a prepared replica', async () => { + const registry = createRegistry() + await registry.save({ + cid: CID_A, + name: CID_A, + state: 'temporary', + createdAt: 1, + expiresAt: 2, + confirmedAt: null, + fileSize: 100, + storedBytes: 120, + protectedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [], + replicaStage: { transactionIds: ['tx-live'], previous: null } + }) + + const stored = await registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(newFile(), { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'upload-two' + }) + ) + + assert.equal(stored.record.state, 'confirmed') + assert.equal(stored.record.replicaStage, undefined) + assert.equal(stored.record.admissionId, 'upload-two') + }) + it('refuses a public mutator called from inside its own lock', async () => { // The two ways to change a record — the locking mutators and the view // handed to withExclusiveCids — cannot be mixed: the inner call queues @@ -511,4 +573,12 @@ describe('expiry rules', () => { assert.equal(isReclaimable(released, 0), true) }) + + it('treats only settled local copies as repair candidates', () => { + const held: FileRecord = { ...record, state: 'confirmed', expiresAt: null } + + assert.equal(isSettledHeldFile(held), true) + assert.equal(isSettledHeldFile({ ...held, admissionId: 'upload-one' }), false) + assert.equal(isSettledHeldFile({ ...held, heldLocally: false }), false) + }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 3f2bd62..c03790e 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1347,6 +1347,97 @@ describe('strict-upload replica staging', () => { assert.equal(await isDirectlyPinned(node, cid), true) }) + it('does not unpin a shared stage when a second claim fails to persist', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_500, 'replica-stage-join-fail')) + + await stage(registry, cid, 'tx-one') + await unpinFile(node, cid) + + let fail = true + const failing = new Proxy(registry, { + get: (target, property) => { + if (property !== 'save') { + return Reflect.get(target, property, target) + } + + return async (value: FileRecord): Promise => { + const stored = await target.save(value) + + if (fail) { + fail = false + throw new Error('datastore acknowledgement failed') + } + + return stored + } + } + }) as FileRegistry + + await assert.rejects( + () => + stageReplica({ + node, + unixfs: ifs, + registry: failing, + cid, + transactionId: 'tx-two', + temporaryTtlMs: 1000, + now: 2000 + }), + /datastore acknowledgement failed/ + ) + + assert.equal(await isDirectlyPinned(node, cid), true) + assert.deepEqual((await registry.get(cid.toString()))?.replicaStage?.transactionIds, ['tx-one']) + }) + + it('does not confirm a prepared replica through a permanent store', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_600, 'replica-stage-store')) + + await stage(registry, cid, 'tx-live') + const stored = await registerPinnedFile({ + node, + registry, + unixfs: ifs, + cid, + temporaryTtlMs: 60_000 + }) + + assert.equal(stored.state, 'temporary') + assert.deepEqual(stored.replicaStage?.transactionIds, ['tx-live']) + await abortReplica({ node, registry, cid, transactionId: 'tx-live' }) + assert.equal(await registry.get(cid.toString()), undefined) + }) + + it('keeps a local upload token when the CID is already durable', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_700, 'replica-stage-admission')) + await pinFile(node, cid) + await registry.save({ + cid: cid.toString(), + name: cid.toString(), + state: 'confirmed', + createdAt: 1000, + expiresAt: null, + confirmedAt: 1000, + fileSize: 21_700, + storedBytes: 21_700, + protectedBytes: 21_700, + pinned: true, + heldLocally: true, + replicas: [], + admissionId: 'mine' + }) + + const result = await stage(registry, cid, 'tx-other') + + assert.equal(result.staged, false) + assert.equal((await registry.get(cid.toString()))?.admissionId, 'mine') + assert.equal((await registry.get(cid.toString()))?.replicaStage, undefined) + }) + it('restores an expired record after a prepared copy aborts', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(22_000, 'replica-stage-restore')) diff --git a/test/replication.test.ts b/test/replication.test.ts index af3f3db..e680627 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -169,6 +169,22 @@ describe('replicate', () => { assert.equal(report.attempts.filter((attempt) => attempt.staged === true).length, 1) }) + it('marks a lost stage acknowledgement so abort can still reach that peer', async () => { + const report = await replicate({ + cid: CID, + ageMs: 0, + selfPeerId: SELF, + peers: PEERS, + config: { ...baseConfig, ackQuorum: 3 }, + store: async () => ({ outcome: 'failed', staged: true, error: 'timed out' }) + }) + + assert.equal(report.satisfied, false) + assert.equal(report.acknowledged, 1) + assert.equal(report.replicas.length, 0) + assert.ok(report.attempts.every((attempt) => attempt.ok === false && attempt.staged === true)) + }) + it('does not demand a quorum the network cannot reach', async () => { const single = [peer('ipfs2')] diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 5252a70..9ff8bc0 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -136,4 +136,11 @@ describe('resolveReplicationConfig', () => { assert.equal(replication.ackQuorum, 2) assert.equal(replication.requireQuorumOnUpload, true) }) + + it('rejects a strict upload that could succeed with only the local copy', () => { + assert.throws( + () => resolveReplicationConfig({ ackQuorum: 1, requireQuorumOnUpload: true }), + /ackQuorum/ + ) + }) }) From 3b43462aa7ca996b3ea2d6dff643860bda18e1f8 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 26 Aug 2026 13:28:25 +0300 Subject: [PATCH 128/167] fix: update replication protocol version to 1.0.0 in documentation and code - Changed references of the replication protocol version from `1.1.0` to `1.0.0` in README, storage lifecycle documentation, and the replication protocol code. - Clarified documentation regarding the implications of protocol versioning and interoperability. --- README.md | 2 +- docs/storage-lifecycle.md | 10 ++++------ src/storage/replicationProtocol.ts | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f719b86..9225645 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Invalid CORS, proxy, API-key, or rate-limit configuration stops the process inst | Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | | Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | | Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | -| Peer replication | libp2p `/adamant/replication/1.1.0`, not an HTTP route | Authenticated by the libp2p handshake; accepted only from the peers listed in `nodes` | +| Peer replication | libp2p `/adamant/replication/1.0.0`, not an HTTP route | Authenticated by the libp2p handshake; accepted only from the peers listed in `nodes` | | Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | | Authenticated user | None | The service has no end-user identity or session layer | diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index b393d64..931bd4a 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -130,7 +130,7 @@ than being open, so a node cannot be filled with permanently pinned content by an anonymous caller. Replication between nodes is not an HTTP route and uses no key at all. It runs -on the libp2p protocol `/adamant/replication/1.1.0`, where the handshake already +on the libp2p protocol `/adamant/replication/1.0.0`, where the handshake already proves the calling peer id cryptographically. Nothing has to be distributed, and a peer needs no HTTP address to be reachable. @@ -441,17 +441,15 @@ copies are withdrawn as described above. ### Protocol version -Copies are placed over the libp2p protocol `/adamant/replication/1.1.0`. The +Copies are placed over the libp2p protocol `/adamant/replication/1.0.0`. The version in that identifier belongs to the wire format, not to the release: it says what two nodes must agree on to talk to each other, and that changes far less often than the software. Taking it from `package.json` would break interoperability on every release for no reason. Raise it when an older node can no longer read what a newer one sends — a new -required field, different framing, or an operation this node sends that an older -handler would reject. Adding an operation that older nodes never send does not -require it. `stage`, `commit`, and `abort` are sent by this node, which is why -the identifier is `1.1.0`. +required field, different framing, or an operation whose meaning changed. Adding +an operation that older nodes never send does not require it. libp2p negotiates the newest version both ends offer, so listing an older one alongside the current one is what allows a network to be upgraded one node at a diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index 6863ad0..8d530ac 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -12,11 +12,11 @@ import type { IpfsNode } from '../ipfs-node.js' * every release, for no reason. * * Raise it when the wire format changes in a way an older node cannot read — - * a new required field, a different framing, or an operation this node sends - * that an older handler would reject. Adding an operation an older node simply - * never sends does not require it. + * a new required field, a different framing, or an operation whose meaning + * changed. Adding an operation an older node simply never sends does not + * require it. */ -export const REPLICATION_PROTOCOL_VERSION = '1.1.0' +export const REPLICATION_PROTOCOL_VERSION = '1.0.0' /** * Protocol nodes use to place copies of a file. From 50fe296b472fd086ac4197dd578775e0cce4eeb0 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Wed, 26 Aug 2026 15:40:18 +0300 Subject: [PATCH 129/167] fix(after review): implement admission token management for upload lifecycle - Introduced `createAdmissionId` and `isCurrentAdmissionId` functions to manage upload tokens. - Added `admissionSettledAt` timestamp to `FileRecord` to indicate when a local upload decision becomes irreversible. - Implemented `recoverInterruptedAdmissions` to clear stale admission tokens left by previous processes. - Updated `finalizeAdmitted` and `clearSettledAdmissions` to handle admission tokens correctly during the upload lifecycle. - Enhanced error handling to prevent operations on files with active lifecycle transactions. - Added tests for admission recovery and lifecycle management to ensure stability and correctness. --- README.md | 2 +- docs/storage-lifecycle.md | 37 ++++-- src/api/uploadRoute.ts | 101 +++++++++++++---- src/index.ts | 34 ++++-- src/security/errors.ts | 5 + src/storage/admission.ts | 64 +++++++++++ src/storage/gc.ts | 1 + src/storage/lifecycle.ts | 17 ++- src/storage/registry.ts | 39 ++++++- src/storage/replicaStage.ts | 76 ++++++++++++- src/storage/service.ts | 130 ++++++++++++++++------ test/admission.test.ts | 56 ++++++++++ test/fileRegistry.test.ts | 4 + test/integration/storageLifecycle.test.ts | 90 ++++++++++++++- test/integration/uploadRoute.test.ts | 89 ++++++++++++--- test/rollback.test.ts | 21 ++-- test/security.test.ts | 8 ++ 17 files changed, 665 insertions(+), 109 deletions(-) create mode 100644 src/storage/admission.ts create mode 100644 test/admission.test.ts diff --git a/README.md b/README.md index 9225645..d1c90de 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ This is an explicit compatibility decision, not an authorization guarantee. A de The multipart contract accepts `files` parts only. Text fields are rejected with a controlled `400 Bad Request` response. -An interrupted upload no longer leaves blocks behind. Each request owns a session that records the blocks it created; a rejected, aborted, or partially failed request removes exactly those, skipping blocks that already existed, blocks a concurrent upload is still writing, and blocks a pin protects. Whatever survives cleanup is unpinned and reclaimable by garbage collection. Strict-quorum uploads prepare rollback-capable remote pins and commit them only after the local decision is durable. Blockstore growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the collection watermarks — see [docs/storage-lifecycle.md](docs/storage-lifecycle.md). +An interrupted upload no longer leaves blocks behind. Each request owns a session that records the blocks it created; a rejected, aborted, or partially failed request removes exactly those, skipping blocks that already existed, blocks a concurrent upload is still writing, and blocks a pin protects. Whatever survives cleanup is unpinned and reclaimable by garbage collection. Strict-quorum uploads prepare rollback-capable remote pins and commit them concurrently only after the local decision is durable; success is returned only after permanent copies still satisfy the quorum. Blockstore growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the collection watermarks — see [docs/storage-lifecycle.md](docs/storage-lifecycle.md). ### CORS diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 931bd4a..6c0f496 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -251,15 +251,30 @@ pre-upload pin and registry state before returning `503`. If any local or remote compensation fails, the route returns `500` instead: it must not describe an incomplete rollback as a clean rejection. -After the local decision is durably recorded, the source commits the prepared -copies. A lost commit acknowledgement does not turn an accepted request into a -failure: the remote copy remains pinned and temporary, repair treats it as -missing until it is promoted, and its TTL (three replication request timeouts) -eventually restores the prior expired state or releases a newly created copy if -the source never returns. Transaction owners and their restoration baselines -live in the datastore, so restarting a peer does not turn a prepared copy into a -permanent one. Live stages are kept through watermark pressure for that short -window; `GET /api/storage/metrics` reports how many remain as `stagedReplicas`. +After the local decision is durably recorded, the source commits every file's +prepared copies concurrently. A commit acknowledgement proves the permanent +write; when that acknowledgement is lost, the source asks `have` and accepts the +peer only if it reports the copy as durable. The HTTP request returns success +only while the verified permanent copies still satisfy `ackQuorum`. If they do +not, the route returns `500`, keeps the local file pinned, and lets the ordinary +repair sweep recreate the missing copies. It does not report `503`, because a +peer may already have committed and the outcome is no longer safe to roll back. + +Extra prepared copies that were not needed for the verified quorum stay +temporary. Their TTL (three replication request timeouts) eventually restores +the prior expired state or releases a newly created copy. An abort marker kept +for the same window prevents a slow `stage` from appearing after its coordinator +already aborted it. Transaction owners and restoration baselines live in the +datastore, so restarting a peer does not turn a prepared copy into a permanent +one. Live stages are kept through watermark pressure for that short window; +`GET /api/storage/metrics` reports how many remain as `stagedReplicas`. + +The local admission token remains until replica settlement finishes. A failed +cleanup write is harmless because a settled timestamp keeps the record visible +to `have`, repair, and handover. On startup, tokens belonging to a previous +process are cleared in the background; no stopped request can still roll them +back. Manual confirm, pin, and unpin operations return `409` while an upload or +replica transaction owns that CID. Turning it off with `replication.enabled: false` makes the node store content **best effort** instead: one copy, on this node, pinned, with no promise that @@ -546,6 +561,10 @@ serve. Reads, uploads and incoming copies do not need a complete registry; the collector and the repair sweep are started once the walk finishes, so neither acts on a half-built picture. +The same startup phase clears admission tokens left by the previous process. +Tokens created by the running process carry its own prefix and are skipped, so +this background recovery cannot settle an upload that is still in flight. + Two consequences are worth planning for: - The original upload time is not recoverable, so a backfilled record is dated diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index 424bd8d..24ae0f9 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -1,4 +1,3 @@ -import { randomUUID } from 'node:crypto' import type { NextFunction, Request, RequestHandler, Response } from 'express' import type { IpfsNode } from '../ipfs-node.js' import { pinFile, unpinFile } from '../storage/pinning.js' @@ -6,6 +5,7 @@ import type { FileRecord, FileRegistry } from '../storage/registry.js' import type { ReplicationReport } from '../storage/replication.js' import { rollbackUpload } from '../storage/rollback.js' import type { UploadSession } from '../storage/uploadSession.js' +import { createAdmissionId } from '../storage/admission.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' @@ -90,11 +90,11 @@ async function rollbackAdmitted( /** * Commit every local record as one guarded decision after network placement. * - * Replica metadata is written first while the admission token is retained on - * every CID. If any write fails, every record can still be compensated under - * the same set of locks. Clearing the internal tokens afterwards is best effort: - * the durable upload decision has already been made, and a cleanup write must - * not turn an accepted upload into an ambiguous failure. + * Replica metadata is written while the admission token is retained on every + * CID. If any write fails, every record can still be compensated under the + * same set of locks. `admissionSettledAt` makes the successful local decision + * irreversible before remote commit starts; clearing the internal token is a + * separate best-effort cleanup. */ async function finalizeAdmitted( registry: FileRegistry, @@ -153,11 +153,19 @@ async function finalizeAdmitted( }) } + // All local records are now durable and no later path rolls them back. + // Publish that fact only after the whole request crossed the boundary, so a + // concurrent `have` cannot observe one file as settled while another write + // still has the power to compensate the complete request. + const settledAt = Date.now() for (const [cid, stored] of finalized) { try { - finalized.set(cid, await locked.save({ ...stored, admissionId: undefined })) + finalized.set(cid, await locked.save({ ...stored, admissionSettledAt: settledAt })) } catch (err) { - log.error(`Could not clear settled upload ownership for ${cid}: ${(err as Error).message}`) + // Token cleanup below makes a second independent write attempt. If the + // datastore remains unavailable, restart recovery clears this process's + // now-stale token before lifecycle jobs resume. + log.error(`Could not mark local upload settlement for ${cid}: ${(err as Error).message}`) } } @@ -165,6 +173,49 @@ async function finalizeAdmitted( }) } +/** + * Remove admission tokens after remote settlement and persist the verified + * replica set. A failed cleanup does not change durability: + * `admissionSettledAt` keeps the record visible to `have`, repair and handover. + */ +async function clearSettledAdmissions( + registry: FileRegistry, + finalized: Map, + reports: Map, + admissionId: string, + log: UploadRouteLog +): Promise { + await registry.withExclusiveCids(finalized.keys(), async (locked) => { + for (const [cid] of finalized) { + let current: FileRecord | undefined + + try { + current = await locked.get(cid) + } catch (err) { + log.error(`Could not read settled upload ownership for ${cid}: ${(err as Error).message}`) + continue + } + + if (current?.admissionId !== admissionId) { + throw new Error(`Upload lifecycle changed during replica commit for ${cid}`) + } + + try { + const report = reports.get(cid) + const stored = await locked.save({ + ...current, + replicas: report?.mode === 'quorum' ? report.replicas : current.replicas, + admissionId: undefined, + admissionSettledAt: undefined + }) + finalized.set(cid, stored) + } catch (err) { + log.error(`Could not clear settled upload ownership for ${cid}: ${(err as Error).message}`) + } + } + }) +} + /** * Build the handler that turns imported parts into durable, registered files. * @@ -185,7 +236,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } const session = dependencies.getSession(req) - const admissionId = randomUUID() + const admissionId = createAdmissionId() try { const files = flatFiles(req.files as UnixFsMulterFile[]) @@ -415,17 +466,29 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } if (dependencies.requireQuorumOnUpload) { - for (const item of replication) { - try { - await dependencies.commitReplicas(item.cid, admissionId, item.report) - } catch (err) { - // The local decision is already durable. A prepared remote copy is - // pinned until its TTL, and repair promotes it with permanent store. - log.error( - `Could not commit prepared replicas for ${item.cid}: ${(err as Error).message}` - ) - } + const commits = await Promise.allSettled( + replication.map((item) => dependencies.commitReplicas(item.cid, admissionId, item.report)) + ) + const failures = commits.flatMap((result, index) => + result.status === 'rejected' + ? [ + new Error( + `Could not commit a durable quorum for ${replication[index]?.cid}: ${result.reason}` + ) + ] + : [] + ) + + await clearSettledAdmissions(registry, finalized, reportByCid, admissionId, log) + + if (failures.length > 0) { + // The local decision is durable and must not be rolled back after a + // peer may have committed. A 500 reports the uncertain network + // outcome; the normal repair sweep restores any missing copies. + throw new AggregateError(failures, 'Could not commit every required replica') } + } else { + await clearSettledAdmissions(registry, finalized, reportByCid, admissionId, log) } const records = outcome.records.map((record) => finalized.get(record.cid) ?? record) diff --git a/src/index.ts b/src/index.ts index 8142385..d623030 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { diskUsageCron } from './disk-usage.cron.js' import { peeringCron, peerWithKnownNodes } from './peering.cron.js' import { helia, ifs } from './helia.js' import { backfillRegistryFromPins, snapshotPins } from './storage/backfill.js' +import { recoverInterruptedAdmissions } from './storage/admission.js' import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' import { fileRegistry } from './storage/state.js' @@ -73,21 +74,30 @@ const legacyPins = await snapshotPins(helia) // the entire migration on exactly the nodes that have the most to serve. Reads, // uploads and incoming copies do not need the registry to be complete; the jobs // that do are started once it is. -void backfillRegistryFromPins({ - cids: legacyPins, - unixfs: ifs, - registry: fileRegistry, - log: (message) => logger.info(message) -}) - .then((report) => { - for (const error of report.errors) { +void Promise.all([ + backfillRegistryFromPins({ + cids: legacyPins, + unixfs: ifs, + registry: fileRegistry, + log: (message) => logger.info(message) + }), + recoverInterruptedAdmissions(fileRegistry) +]) + .then(([backfill, admissions]) => { + for (const error of backfill.errors) { logger.warn(`Registry backfill: ${error}`) } + for (const error of admissions.errors) { + logger.warn(`Admission recovery: ${error}`) + } + if (admissions.recovered > 0) { + logger.info(`Admission recovery: cleared ${admissions.recovered} interrupted uploads`) + } }) - .catch((err: Error) => logger.error(`Registry backfill failed: ${err.message}`)) - // Even a failed backfill must not leave the node without a collector: it - // reclaims nothing that is unregistered, so an incomplete registry makes it - // do less, never more. + .catch((err: Error) => logger.error(`Startup storage reconciliation failed: ${err.message}`)) + // Even a failed reconciliation must not leave the node without a collector: + // it reclaims nothing that is unregistered, so an incomplete registry makes + // it do less, never more. .finally(startLifecycleJobs) const PORT = config.serverPort diff --git a/src/security/errors.ts b/src/security/errors.ts index a7a1121..9d059a4 100644 --- a/src/security/errors.ts +++ b/src/security/errors.ts @@ -1,5 +1,6 @@ import multer from 'multer' import { RequestSizeLimitError } from '../storage/limits.js' +import { FileLifecycleBusyError } from '../storage/registry.js' import { FileNotFoundError } from '../utils/fileErrors.js' export type PublicError = { @@ -35,6 +36,10 @@ export function getPublicError(error: unknown): PublicError { return { status: 408, body: { error: 'File request timed out' } } } + if (error instanceof FileLifecycleBusyError) { + return { status: 409, body: { error: 'File lifecycle is busy' } } + } + // Raised while streaming when the parts of one request exceed the aggregate // limit. The configured limit is not echoed back to the client. if (error instanceof RequestSizeLimitError) { diff --git a/src/storage/admission.ts b/src/storage/admission.ts new file mode 100644 index 0000000..752ba37 --- /dev/null +++ b/src/storage/admission.ts @@ -0,0 +1,64 @@ +import { randomUUID } from 'node:crypto' +import type { FileRegistry } from './registry.js' + +const processAdmissionPrefix = `${randomUUID()}:` + +/** Create an upload token that can be recognized as belonging to this process. */ +export function createAdmissionId(): string { + return `${processAdmissionPrefix}${randomUUID()}` +} + +/** Whether a request that can still roll this token back exists in this process. */ +export function isCurrentAdmissionId(admissionId: string): boolean { + return admissionId.startsWith(processAdmissionPrefix) +} + +export interface AdmissionRecoveryReport { + checked: number + recovered: number + errors: string[] +} + +/** + * Clear upload ownership left by a process that stopped before cleanup. + * + * A current-process token is skipped because its request can still compensate + * the record. A previous process cannot resume that request; retaining its token + * would hide a confirmed pin from repair and handover forever. The file itself + * stays exactly as it was — only stale transaction ownership is removed. + */ +export async function recoverInterruptedAdmissions( + registry: FileRegistry +): Promise { + const candidates = (await registry.all()).filter( + (record) => record.admissionId !== undefined && !isCurrentAdmissionId(record.admissionId) + ) + const report: AdmissionRecoveryReport = { checked: candidates.length, recovered: 0, errors: [] } + + for (const candidate of candidates) { + try { + const recovered = await registry.withExclusiveCids([candidate.cid], async (locked) => { + const current = await locked.get(candidate.cid) + + if (current?.admissionId === undefined || isCurrentAdmissionId(current.admissionId)) { + return false + } + + await locked.save({ + ...current, + admissionId: undefined, + admissionSettledAt: undefined + }) + return true + }) + + if (recovered) { + report.recovered += 1 + } + } catch (err) { + report.errors.push(`${candidate.cid}: ${(err as Error).message}`) + } + } + + return report +} diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 8554ebb..8536525 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -389,6 +389,7 @@ export async function runGarbageCollection(options: GcRunOptions): Promise 0) + const admissionSettlementIsValid = + record.admissionSettledAt === undefined || + (record.admissionId !== undefined && isFiniteNonNegative(record.admissionSettledAt)) const replicaStageIsValid = record.replicaStage === undefined || (record.state === 'temporary' && @@ -166,6 +177,7 @@ function isFileRecord(value: unknown): value is FileRecord { record.replicas.every((peer) => typeof peer === 'string') && revisionIsValid && admissionIsValid && + admissionSettlementIsValid && replicaStageIsValid ) } @@ -200,6 +212,14 @@ export interface RegisterFileOptions { admissionId?: string } +/** A second lifecycle must not replace ownership held by an in-flight operation. */ +export class FileLifecycleBusyError extends Error { + constructor(cid: string) { + super(`CID ${cid} has an active lifecycle transaction`) + this.name = 'FileLifecycleBusyError' + } +} + /** * Registry operations that are safe to compose while their CID locks are held. * @@ -537,6 +557,10 @@ export class FileRegistry { return { record: existing, previous: existing } } + if (existing?.admissionId !== undefined && existing.admissionId !== options.admissionId) { + throw new FileLifecycleBusyError(file.cid) + } + if (existing?.state === 'confirmed') { // Re-uploading already durable content must not weaken its state, and it // puts the blocks back on this node whether or not they were released. @@ -551,6 +575,7 @@ export class FileRegistry { pinned: true, heldLocally: true, admissionId: options.admissionId, + admissionSettledAt: undefined, replicaStage: undefined }) @@ -573,6 +598,7 @@ export class FileRegistry { heldLocally: true, replicas: existing?.replicas ?? [], admissionId: options.admissionId, + admissionSettledAt: undefined, replicaStage: undefined }) @@ -624,12 +650,17 @@ export function isReclaimable(record: FileRecord, now: number = Date.now()): boo /** * Durable local copy that no in-flight upload still owns. * - * Repair and handover skip records that still carry an admission token: those - * writes are mid-transaction, and treating them as settled would let `store` - * confirm a replica the upload can still abort. + * Repair and handover skip an admission token only until the request publishes + * `admissionSettledAt`. The token may remain after a failed cleanup write; once + * settled, no path can roll the local lifecycle back. */ export function isSettledHeldFile(record: FileRecord): boolean { - return record.state === 'confirmed' && record.heldLocally && record.admissionId === undefined + return record.state === 'confirmed' && record.heldLocally && isAdmissionSettled(record) +} + +/** Whether an upload can no longer roll this local lifecycle back. */ +export function isAdmissionSettled(record: FileRecord): boolean { + return record.admissionId === undefined || record.admissionSettledAt !== undefined } export function countByState(records: FileRecord[]): Record { diff --git a/src/storage/replicaStage.ts b/src/storage/replicaStage.ts index 0540086..efc2239 100644 --- a/src/storage/replicaStage.ts +++ b/src/storage/replicaStage.ts @@ -8,7 +8,48 @@ import type { LockedFileRegistry, ReplicaStageBaseline } from './registry.js' -import { protectedStorageBytes } from './registry.js' +import { isAdmissionSettled, protectedStorageBytes } from './registry.js' + +/** + * Abort markers close the race where an abort reaches the peer while its stage + * request is still pulling blocks and has not written transaction ownership. + * They only need to survive that in-process request; a restart cancels both + * sides of the race. + */ +const abortedTransactions = new Map() +const DEFAULT_ABORT_TOMBSTONE_TTL_MS = 5 * 60 * 1000 +const MAX_ABORT_TOMBSTONES = 10_000 + +function transactionKey(cid: string, transactionId: string): string { + return `${cid}\0${transactionId}` +} + +function pruneAbortTombstones(now: number): void { + for (const [key, expiresAt] of abortedTransactions) { + if (expiresAt <= now) { + abortedTransactions.delete(key) + } + } + + while (abortedTransactions.size > MAX_ABORT_TOMBSTONES) { + const oldest = abortedTransactions.keys().next().value as string | undefined + if (oldest === undefined) { + break + } + abortedTransactions.delete(oldest) + } +} + +function rememberAbort(cid: string, transactionId: string, expiresAt: number, now: number): void { + pruneAbortTombstones(now) + abortedTransactions.set(transactionKey(cid, transactionId), expiresAt) + pruneAbortTombstones(now) +} + +function wasAborted(cid: string, transactionId: string, now: number): boolean { + pruneAbortTombstones(now) + return (abortedTransactions.get(transactionKey(cid, transactionId)) ?? 0) > now +} export interface ReplicaStageTarget { node: IpfsNode @@ -101,6 +142,7 @@ async function registerStableReplica( heldLocally: true, replicas: previous?.replicas ?? [], admissionId: previous?.admissionId, + admissionSettledAt: previous?.admissionSettledAt, replicaStage: undefined }) @@ -124,6 +166,10 @@ export async function stageReplica(options: ReplicaStageTarget): Promise { + if (wasAborted(key, options.transactionId, now)) { + throw new Error(`Replica transaction ${options.transactionId} for ${key} was already aborted`) + } + const previous = await registry.get(key) if (previous?.replicaStage !== undefined) { @@ -156,6 +202,10 @@ export async function stageReplica(options: ReplicaStageTarget): Promise { const key = options.cid.toString() + const now = options.now ?? Date.now() + rememberAbort( + key, + options.transactionId, + now + (options.tombstoneTtlMs ?? DEFAULT_ABORT_TOMBSTONE_TTL_MS), + now + ) return options.registry.withExclusiveCids([key], async (registry) => { const current = await registry.get(key) diff --git a/src/storage/service.ts b/src/storage/service.ts index d31cab5..3ea1f4f 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -14,7 +14,12 @@ import { releaseStoredFile, type LifecycleTarget } from './lifecycle.js' -import { isSettledHeldFile, protectedStorageBytes, type FileRecord } from './registry.js' +import { + isAdmissionSettled, + isSettledHeldFile, + protectedStorageBytes, + type FileRecord +} from './registry.js' import { isUnderReplicated, replicate, @@ -52,6 +57,9 @@ const callOptions = (): ReplicationCallOptions => ({ timeoutMs: config.replication.requestTimeoutMs }) +/** Lifetime of a prepared copy and of an abort marker for its request. */ +const settlementTtlMs = (): number => config.replication.requestTimeoutMs * 3 + function selfPeerId(): string { return helia.libp2p.peerId.toString() } @@ -139,12 +147,11 @@ function stagedPeers( } /** - * Make every prepared copy permanent after the local upload decision commits. + * Make enough prepared copies permanent to preserve the strict upload quorum. * - * A lost acknowledgement cannot turn an accepted upload into a rejection. The - * copy remains pinned as temporary. Repair skips records that still carry an - * `admissionId`; once that token is cleared it treats the staged copy as - * missing and promotes it with the idempotent permanent `store` operation. + * A lost commit acknowledgement is followed by `have`: if the peer applied the + * decision, its durable state is authoritative. The upload response is withheld + * when fewer than `required` copies can be proved permanent. */ export async function commitUploadedReplicas( cid: string, @@ -153,15 +160,49 @@ export async function commitUploadedReplicas( ): Promise { const peers = stagedPeers(report, { includeFailed: false }) const results = await Promise.allSettled( - peers.map((peer) => requestCommit(helia, peer.multiAddr, cid, transactionId, callOptions())) + peers.map(async (peer) => { + try { + await requestCommit(helia, peer.multiAddr, cid, transactionId, callOptions()) + } catch (commitError) { + try { + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return peer.name + } + } catch (probeError) { + throw new AggregateError( + [commitError, probeError], + `Replica commit and verification failed on ${peer.name}`, + { cause: probeError } + ) + } + + throw commitError + } + + return peer.name + }) ) - for (const [index, result] of results.entries()) { - if (result.status === 'rejected') { - logger.warn( - `Could not commit prepared replica ${cid} on ${peers[index]?.name}: ${result.reason}` - ) - } + const stable = report.attempts + .filter((attempt) => attempt.ok && attempt.outcome === 'stored' && attempt.staged !== true) + .map((attempt) => attempt.node) + const committed = results.flatMap((result) => + result.status === 'fulfilled' ? [result.value] : [] + ) + report.replicas = [...new Set([...stable, ...committed])] + report.acknowledged = report.replicas.length + 1 + report.satisfied = report.acknowledged >= report.required + + if (!report.satisfied) { + const failures = results.flatMap((result, index) => + result.status === 'rejected' + ? [new Error(`Could not commit replica on ${peers[index]?.name}: ${result.reason}`)] + : [] + ) + throw new AggregateError( + failures, + `Committed replica quorum not reached for ${cid}: ${report.acknowledged}/${report.required}` + ) } } @@ -352,8 +393,9 @@ export async function acceptReplica(cid: string): Promise { if (existing?.replicaStage !== undefined) { // A strict upload still owns this copy. Promoting it would make abort a - // no-op. - return existing + // no-op, while returning success would let repair count a temporary pin + // as durable. + throw new Error(`CID ${cid} has an active replica transaction`) } // Pulling the DAG first is what bounds this. Pinning would fetch it too, but @@ -366,7 +408,15 @@ export async function acceptReplica(cid: string): Promise { // Every block is local now, so pinning walks the blockstore rather than the // network. The shared lease prevents collection between these two steps. - return registerPinned(CID.parse(cid), cid) + const stored = await registerPinned(CID.parse(cid), cid) + + if (stored.replicaStage !== undefined) { + // A stage may have appeared while this request was pulling the DAG. The + // registry correctly kept it; do not turn that no-op into a durable ack. + throw new Error(`CID ${cid} has an active replica transaction`) + } + + return stored }) } @@ -381,13 +431,12 @@ export async function acceptStagedReplica( // abort. The ordinary unconfirmed-upload TTL is a day; inheriting it would // pin an abandoned stage through watermark pressure for far longer than // settlement can take. - const settlementTtlMs = config.replication.requestTimeoutMs * 3 const result = await stageReplica({ ...lifecycleTarget(), unixfs: ifs, cid: CID.parse(cid), transactionId, - temporaryTtlMs: settlementTtlMs, + temporaryTtlMs: settlementTtlMs(), pinTimeoutMs: config.replication.requestTimeoutMs }) @@ -405,7 +454,12 @@ export async function commitStagedReplica(cid: string, transactionId: string): P /** Withdraw one source upload's claim on a prepared copy. */ export async function abortStagedReplica(cid: string, transactionId: string): Promise { await storageOperationLock.withShared(async () => { - await abortReplica({ ...lifecycleTarget(), cid: CID.parse(cid), transactionId }) + await abortReplica({ + ...lifecycleTarget(), + cid: CID.parse(cid), + transactionId, + tombstoneTtlMs: settlementTtlMs() + }) }) } @@ -423,6 +477,7 @@ async function hasDurableReplica(cid: string): Promise { record.state === 'confirmed' && record.pinned && record.heldLocally && + isAdmissionSettled(record) && record.replicaStage === undefined && (await isDirectlyPinned(helia, parsed)) ) @@ -608,6 +663,21 @@ export interface RepairReport { rescued: string[] } +/** Ask the designated peers which durable copies exist right now. */ +async function liveHolderNames(holders: ReplicationPeer[], cid: string): Promise { + const answers = await Promise.all( + holders.map(async (peer) => { + try { + return (await probeHave(helia, peer.multiAddr, cid, callOptions())) ? peer.name : undefined + } catch { + return undefined + } + }) + ) + + return answers.filter((name): name is string => name !== undefined) +} + /** * Take a released file back when no other node is holding it. * @@ -690,6 +760,7 @@ async function rescueOrphanedFiles( pinned: true, heldLocally: true, admissionId: undefined, + admissionSettledAt: undefined, replicaStage: undefined }) } catch (err) { @@ -755,19 +826,7 @@ export async function repairReplication(): Promise { .map((peerId) => byPeerId.get(peerId)) .filter((peer): peer is ReplicationPeer => peer !== undefined) - const answers = await Promise.all( - holders.map(async (peer) => { - try { - return (await probeHave(helia, peer.multiAddr, record.cid, callOptions())) - ? peer.name - : undefined - } catch { - return undefined - } - }) - ) - - const live = answers.filter((name): name is string => name !== undefined) + const live = await liveHolderNames(holders, record.cid) await fileRegistry.setReplicas(record.cid, live) if (!isUnderReplicated(live.length, placement, self)) { @@ -775,9 +834,11 @@ export async function repairReplication(): Promise { } report.underReplicated += 1 - const result = await replicateFile(record.cid) + await replicateFile(record.cid) + const verified = await liveHolderNames(holders, record.cid) + await fileRegistry.setReplicas(record.cid, verified) - if (isUnderReplicated(result.replicas.length, placement, self)) { + if (isUnderReplicated(verified.length, placement, self)) { report.stillMissing.push(record.cid) } else { report.repaired.push(record.cid) @@ -890,6 +951,7 @@ export async function demoteReleasableCopies( pinned: false, heldLocally: false, admissionId: undefined, + admissionSettledAt: undefined, replicaStage: undefined }) return true diff --git a/test/admission.test.ts b/test/admission.test.ts new file mode 100644 index 0000000..a951c34 --- /dev/null +++ b/test/admission.test.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { FsDatastore } from 'datastore-fs' +import { createAdmissionId, recoverInterruptedAdmissions } from '../src/storage/admission.js' +import { FileRegistry, type FileRecord } from '../src/storage/registry.js' + +const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' +const CID_B = 'bafkreihfzyt2g6pbd4rhk67deek7xh33xams74spn72eqq5qhx2ypphvii' + +let directory: string +let datastore: FsDatastore + +const record = (cid: string, admissionId: string): FileRecord => ({ + cid, + name: cid, + state: 'confirmed', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + fileSize: 100, + storedBytes: 120, + protectedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [], + admissionId +}) + +before(async () => { + directory = await mkdtemp(join(tmpdir(), 'ipfs-node-admission-')) + datastore = new FsDatastore(join(directory, 'datastore')) + await datastore.open() +}) + +after(async () => { + await datastore.close() + await rm(directory, { recursive: true, force: true }) +}) + +describe('upload admission recovery', () => { + it('clears only tokens left by an earlier process', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-recovery') + const currentId = createAdmissionId() + await registry.save(record(CID_A, 'previous-process:request')) + await registry.save(record(CID_B, currentId)) + + const report = await recoverInterruptedAdmissions(registry) + + assert.deepEqual(report, { checked: 1, recovered: 1, errors: [] }) + assert.equal((await registry.get(CID_A))?.admissionId, undefined) + assert.equal((await registry.get(CID_B))?.admissionId, currentId) + }) +}) diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 865d70f..d0f7bd6 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -579,6 +579,10 @@ describe('expiry rules', () => { assert.equal(isSettledHeldFile(held), true) assert.equal(isSettledHeldFile({ ...held, admissionId: 'upload-one' }), false) + assert.equal( + isSettledHeldFile({ ...held, admissionId: 'upload-one', admissionSettledAt: 1234 }), + true + ) assert.equal(isSettledHeldFile({ ...held, heldLocally: false }), false) }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index c03790e..9e23893 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1411,7 +1411,37 @@ describe('strict-upload replica staging', () => { assert.equal(await registry.get(cid.toString()), undefined) }) - it('keeps a local upload token when the CID is already durable', async () => { + it('rejects manual confirm and release while a replica transaction is live', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_650, 'replica-stage-admin')) + + await stage(registry, cid, 'tx-admin') + + await assert.rejects( + () => + confirmStoredFile({ + node, + registry, + unixfs: ifs, + cid, + temporaryTtlMs: 60_000 + }), + /active lifecycle transaction/ + ) + await assert.rejects( + () => releaseStoredFile({ node, registry, cid }), + /active lifecycle transaction/ + ) + + assert.deepEqual((await registry.get(cid.toString()))?.replicaStage?.transactionIds, [ + 'tx-admin' + ]) + assert.equal(await isDirectlyPinned(node, cid), true) + + await abortReplica({ node, registry, cid, transactionId: 'tx-admin' }) + }) + + it('does not count an unsettled local upload as a stable replica', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(21_700, 'replica-stage-admission')) await pinFile(node, cid) @@ -1431,13 +1461,67 @@ describe('strict-upload replica staging', () => { admissionId: 'mine' }) - const result = await stage(registry, cid, 'tx-other') + await assert.rejects(() => stage(registry, cid, 'tx-other'), /unsettled local upload/) - assert.equal(result.staged, false) assert.equal((await registry.get(cid.toString()))?.admissionId, 'mine') assert.equal((await registry.get(cid.toString()))?.replicaStage, undefined) }) + it('accepts a settled upload whose admission token cleanup is pending', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_800, 'replica-stage-settled-admission')) + await pinFile(node, cid) + await registry.save({ + cid: cid.toString(), + name: cid.toString(), + state: 'confirmed', + createdAt: 1000, + expiresAt: null, + confirmedAt: 1000, + fileSize: 21_800, + storedBytes: 21_800, + protectedBytes: 21_800, + pinned: true, + heldLocally: true, + replicas: [], + admissionId: 'settled', + admissionSettledAt: 2000 + }) + + const result = await stage(registry, cid, 'tx-other') + + assert.equal(result.staged, false) + assert.equal((await registry.get(cid.toString()))?.admissionId, 'settled') + }) + + it('rejects a stage that arrives after its abort', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_900, 'replica-stage-late')) + + await abortReplica({ + node, + registry, + cid, + transactionId: 'tx-late', + tombstoneTtlMs: 1000, + now: 1000 + }) + + await assert.rejects(() => stage(registry, cid, 'tx-late', 1500), /already aborted/) + assert.equal(await registry.get(cid.toString()), undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + + it('does not acknowledge commit after a stage disappeared', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_950, 'replica-stage-missing-commit')) + + await assert.rejects( + () => commitReplica({ node, registry, cid, transactionId: 'tx-missing' }), + /is not staged/ + ) + }) + it('restores an expired record after a prepared copy aborts', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(22_000, 'replica-stage-restore')) diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts index 02cb3a3..ebf8e25 100644 --- a/test/integration/uploadRoute.test.ts +++ b/test/integration/uploadRoute.test.ts @@ -413,8 +413,8 @@ describe('the upload endpoint end to end', () => { } }) - it('does not accept a strict upload after another lifecycle releases its local copy', async () => { - const aborted: string[] = [] + it('keeps the local lifecycle stable until strict settlement finishes', async () => { + let releaseRejected = false const report: ReplicationReport = { ...bestEffort(), mode: 'quorum', @@ -438,11 +438,12 @@ describe('the upload endpoint end to end', () => { const harness = await serveUpload({ requireQuorumOnUpload: true, replicate: async (cid) => { - await releaseStoredFile({ node, registry: harness.registry, cid: CID.parse(cid) }) + await assert.rejects( + () => releaseStoredFile({ node, registry: harness.registry, cid: CID.parse(cid) }), + /active lifecycle transaction/ + ) + releaseRejected = true return report - }, - abortReplicas: async (cid) => { - aborted.push(cid) } }) const payload = deterministicBytes(77_000, 'route-lifecycle-race') @@ -452,18 +453,18 @@ describe('the upload endpoint end to end', () => { const response = await post(harness.url, [{ name: 'released.bin', bytes: payload }]) const record = await harness.registry.get(cid) - assert.equal(response.status, 500) - assert.deepEqual(aborted, [cid]) - assert.equal(record?.state, 'expired') - assert.equal(record?.heldLocally, false) + assert.equal(response.status, 200) + assert.equal(releaseRejected, true) + assert.equal(record?.state, 'confirmed') + assert.equal(record?.heldLocally, true) assert.equal(record?.admissionId, undefined) - assert.equal(await isDirectlyPinned(node, CID.parse(cid)), false) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), true) } finally { harness.server.close() } }) - it('keeps an accepted upload durable when a remote commit acknowledgement is lost', async () => { + it('does not report strict success when remote commit cannot be verified', async () => { let commitAttempts = 0 const report: ReplicationReport = { ...bestEffort(), @@ -494,17 +495,73 @@ describe('the upload endpoint end to end', () => { } }) const payload = deterministicBytes(78_000, 'route-commit-ack-lost') + const cid = (await ifs.addBytes(payload)).toString() try { const response = await post(harness.url, [{ name: 'accepted.bin', bytes: payload }]) - const body = (await response.json()) as { cids: string[] } - const record = await harness.registry.get(body.cids[0]) + const record = await harness.registry.get(cid) - assert.equal(response.status, 200) + assert.equal(response.status, 500) assert.equal(commitAttempts, 1) assert.equal(record?.state, 'confirmed') assert.equal(record?.admissionId, undefined) - assert.equal(await isDirectlyPinned(node, CID.parse(body.cids[0])), true) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), true) + } finally { + harness.server.close() + } + }) + + it('commits every file in a strict request concurrently', async () => { + const started: string[] = [] + let release!: () => void + const mayFinish = new Promise((resolve) => { + release = resolve + }) + const report = (): ReplicationReport => ({ + ...bestEffort(), + mode: 'quorum', + desiredCopies: 2, + copies: 2, + required: 2, + acknowledged: 2, + replicas: ['holder'], + satisfied: true, + networkTooSmall: true, + attempts: [ + { + node: 'holder', + peerId: 'holder-peer-id', + ok: true, + outcome: 'stored', + staged: true + } + ] + }) + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => report(), + commitReplicas: async (cid) => { + started.push(cid) + if (started.length === 2) { + release() + } + await Promise.race([ + mayFinish, + new Promise((_, reject) => + setTimeout(() => reject(new Error('commits were serialized')), 1000) + ) + ]) + } + }) + + try { + const response = await post(harness.url, [ + { name: 'one.bin', bytes: deterministicBytes(79_000, 'route-concurrent-commit-one') }, + { name: 'two.bin', bytes: deterministicBytes(80_000, 'route-concurrent-commit-two') } + ]) + + assert.equal(response.status, 200) + assert.equal(started.length, 2) } finally { harness.server.close() } diff --git a/test/rollback.test.ts b/test/rollback.test.ts index 30352d2..295e2c9 100644 --- a/test/rollback.test.ts +++ b/test/rollback.test.ts @@ -172,7 +172,7 @@ describe('rollbackUpload', () => { assert.equal(pinned, false) }) - it('rolls back after metadata changes but not after lifecycle adoption', async () => { + it('rolls back after metadata changes and blocks competing lifecycle adoption', async () => { const registry = createRegistry() let pinned = true @@ -208,11 +208,16 @@ describe('rollbackUpload', () => { temporaryTtlMs: TTL, admissionId: 'mine' }) - await locked.registerReplacing(file, { - confirmationRequired: false, - temporaryTtlMs: TTL, - admissionId: 'theirs' - }) + + await assert.rejects( + () => + locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'theirs' + }), + /active lifecycle transaction/ + ) await rollbackUpload({ registry: locked, @@ -226,7 +231,7 @@ describe('rollbackUpload', () => { }) }) - assert.equal((await registry.get(CID_A))?.admissionId, 'theirs') - assert.equal(pinned, true) + assert.equal(await registry.get(CID_A), undefined) + assert.equal(pinned, false) }) }) diff --git a/test/security.test.ts b/test/security.test.ts index e47ee28..91921e6 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -12,6 +12,7 @@ import { getPublicError, InvalidRequestError } from '../src/security/errors.js' import { createRateLimiter } from '../src/security/rateLimit.js' import { parseTrustProxy } from '../src/security/trustProxy.js' import { createMultipartLimits } from '../src/security/uploadLimits.js' +import { FileLifecycleBusyError } from '../src/storage/registry.js' import { FileNotFoundError } from '../src/utils/fileErrors.js' describe('CORS origin policy', () => { @@ -123,6 +124,13 @@ describe('public error mapping', () => { } ) }) + + it('reports an active lifecycle without exposing transaction details', () => { + assert.deepEqual(getPublicError(new FileLifecycleBusyError('secret-cid')), { + status: 409, + body: { error: 'File lifecycle is busy' } + }) + }) }) describe('streaming multipart limits', () => { From 25701b9e0c1d6b1baec1e693f8c8833659c8b1d8 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 26 Aug 2026 16:34:21 +0300 Subject: [PATCH 130/167] fix(storage): improve admission token management and lifecycle handling - Enhanced the handling of admission tokens during the upload lifecycle, ensuring that settled tokens are retained for commit retries while clearing unsettled tokens. - Updated `commitReplicas` to return a `ReplicationReport`, allowing better tracking of committed replicas. - Introduced `isLifecycleBusy` to determine if a file is in a busy state based on replica stages and unsettled admissions. - Improved error handling to prevent operations on files with active lifecycle transactions. - Added tests to validate the behavior of settled tokens and lifecycle management, ensuring robustness in upload processes. --- docs/storage-lifecycle.md | 33 ++++--- src/api/uploadRoute.ts | 57 +++++++++-- src/storage/admission.ts | 23 +++-- src/storage/lifecycle.ts | 5 +- src/storage/registry.ts | 16 +++- src/storage/replicaStage.ts | 6 +- src/storage/service.ts | 111 +++++++++++++++++++--- test/admission.test.ts | 13 +++ test/fileRegistry.test.ts | 12 +++ test/integration/storageLifecycle.test.ts | 36 +++++++ test/integration/uploadRoute.test.ts | 8 +- test/rollback.test.ts | 27 ++++++ 12 files changed, 300 insertions(+), 47 deletions(-) diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 6c0f496..bbecf3d 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -256,25 +256,34 @@ prepared copies concurrently. A commit acknowledgement proves the permanent write; when that acknowledgement is lost, the source asks `have` and accepts the peer only if it reports the copy as durable. The HTTP request returns success only while the verified permanent copies still satisfy `ackQuorum`. If they do -not, the route returns `500`, keeps the local file pinned, and lets the ordinary -repair sweep recreate the missing copies. It does not report `503`, because a -peer may already have committed and the outcome is no longer safe to roll back. +not, the route returns `500` and keeps the local file pinned. The admission +token stays on that CID so the ordinary repair sweep can retry `commit` on +peers that still hold the prepared pin, then `store` onto empty peers. It does +not report `503`, because a peer may already have committed and the outcome is +no longer safe to roll back. Clients that treat `500` as "not saved" may retry: +content-addressed dedup absorbs a second upload of the same bytes. Extra prepared copies that were not needed for the verified quorum stay temporary. Their TTL (three replication request timeouts) eventually restores the prior expired state or releases a newly created copy. An abort marker kept -for the same window prevents a slow `stage` from appearing after its coordinator -already aborted it. Transaction owners and restoration baselines live in the -datastore, so restarting a peer does not turn a prepared copy into a permanent -one. Live stages are kept through watermark pressure for that short window; +in memory for the same window prevents a slow `stage` from appearing after its +coordinator already aborted it. The marker does not survive a restart of this +node: if a late `stage` is still in flight then, the copy can appear until TTL, +which is the same bound used for any abandoned stage. A coordinator that already +aborted will not reuse that transaction id, so a source restart is a new +request. Transaction owners and restoration baselines live in the datastore, so +restarting a peer does not turn a prepared copy into a permanent one. Live +stages are kept through watermark pressure for that short window; `GET /api/storage/metrics` reports how many remain as `stagedReplicas`. The local admission token remains until replica settlement finishes. A failed -cleanup write is harmless because a settled timestamp keeps the record visible -to `have`, repair, and handover. On startup, tokens belonging to a previous -process are cleared in the background; no stopped request can still roll them -back. Manual confirm, pin, and unpin operations return `409` while an upload or -replica transaction owns that CID. +cleanup write, or a `500` after an unverified commit, leaves the token in place. +`admissionSettledAt` still makes the record visible to `have`, repair, and +handover, and repair clears the token once the durable copies are confirmed. +Manual confirm, pin, and unpin return `409` while a replica stage or an +_unsettled_ local upload owns that CID; a leftover settled token does not block +them. On startup, _unsettled_ tokens belonging to a previous process are cleared +in the background; settled tokens are left for repair. Turning it off with `replication.enabled: false` makes the node store content **best effort** instead: one copy, on this node, pinned, with no promise that diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index 24ae0f9..b4139a0 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -28,7 +28,11 @@ export interface UploadRouteDependencies { /** Prepare remote copies when `transactionId` is present. */ replicate: (cid: string, transactionId?: string) => Promise /** Make prepared copies permanent after the local decision commits. */ - commitReplicas: (cid: string, transactionId: string, report: ReplicationReport) => Promise + commitReplicas: ( + cid: string, + transactionId: string, + report: ReplicationReport + ) => Promise /** Withdraw prepared copies before reporting a strict rejection. */ abortReplicas: (cid: string, transactionId: string, report: ReplicationReport) => Promise /** Test seam for pin-datastore failure paths. */ @@ -175,18 +179,27 @@ async function finalizeAdmitted( /** * Remove admission tokens after remote settlement and persist the verified - * replica set. A failed cleanup does not change durability: - * `admissionSettledAt` keeps the record visible to `have`, repair and handover. + * replica set. Only CIDs whose remote commit was verified are passed in; a + * failed commit keeps the token so repair can retry it. A failed cleanup does + * not change durability: `admissionSettledAt` keeps the record visible to + * `have`, repair and handover. */ async function clearSettledAdmissions( registry: FileRegistry, finalized: Map, reports: Map, admissionId: string, - log: UploadRouteLog + log: UploadRouteLog, + cids: Iterable ): Promise { - await registry.withExclusiveCids(finalized.keys(), async (locked) => { - for (const [cid] of finalized) { + const keys = [...cids] + + if (keys.length === 0) { + return + } + + await registry.withExclusiveCids(keys, async (locked) => { + for (const cid of keys) { let current: FileRecord | undefined try { @@ -467,8 +480,17 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ if (dependencies.requireQuorumOnUpload) { const commits = await Promise.allSettled( - replication.map((item) => dependencies.commitReplicas(item.cid, admissionId, item.report)) + replication.map(async (item) => { + const committed = await dependencies.commitReplicas(item.cid, admissionId, item.report) + item.report = committed + reportByCid.set(item.cid, committed) + return item.cid + }) ) + const committedCids = commits.flatMap((result, index) => { + const cid = replication[index]?.cid + return result.status === 'fulfilled' && cid !== undefined ? [cid] : [] + }) const failures = commits.flatMap((result, index) => result.status === 'rejected' ? [ @@ -479,7 +501,17 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ : [] ) - await clearSettledAdmissions(registry, finalized, reportByCid, admissionId, log) + // Keep the token on files whose commit was not verified so repair can + // retry `commit` with the original transaction id. Files that did + // commit drop the token here. + await clearSettledAdmissions( + registry, + finalized, + reportByCid, + admissionId, + log, + committedCids + ) if (failures.length > 0) { // The local decision is durable and must not be rolled back after a @@ -488,7 +520,14 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ throw new AggregateError(failures, 'Could not commit every required replica') } } else { - await clearSettledAdmissions(registry, finalized, reportByCid, admissionId, log) + await clearSettledAdmissions( + registry, + finalized, + reportByCid, + admissionId, + log, + finalized.keys() + ) } const records = outcome.records.map((record) => finalized.get(record.cid) ?? record) diff --git a/src/storage/admission.ts b/src/storage/admission.ts index 752ba37..b83d742 100644 --- a/src/storage/admission.ts +++ b/src/storage/admission.ts @@ -1,5 +1,5 @@ import { randomUUID } from 'node:crypto' -import type { FileRegistry } from './registry.js' +import { isAdmissionSettled, type FileRegistry } from './registry.js' const processAdmissionPrefix = `${randomUUID()}:` @@ -23,15 +23,22 @@ export interface AdmissionRecoveryReport { * Clear upload ownership left by a process that stopped before cleanup. * * A current-process token is skipped because its request can still compensate - * the record. A previous process cannot resume that request; retaining its token - * would hide a confirmed pin from repair and handover forever. The file itself - * stays exactly as it was — only stale transaction ownership is removed. + * the record. A previous process cannot resume an unsettled request; retaining + * that token would hide a confirmed pin from repair and handover forever. The + * file itself stays exactly as it was — only stale transaction ownership is + * removed. + * + * A settled token is kept. Repair uses it to retry `commit` on peers that still + * hold a prepared copy, then clears it once those copies are durable. */ export async function recoverInterruptedAdmissions( registry: FileRegistry ): Promise { const candidates = (await registry.all()).filter( - (record) => record.admissionId !== undefined && !isCurrentAdmissionId(record.admissionId) + (record) => + record.admissionId !== undefined && + !isCurrentAdmissionId(record.admissionId) && + !isAdmissionSettled(record) ) const report: AdmissionRecoveryReport = { checked: candidates.length, recovered: 0, errors: [] } @@ -40,7 +47,11 @@ export async function recoverInterruptedAdmissions( const recovered = await registry.withExclusiveCids([candidate.cid], async (locked) => { const current = await locked.get(candidate.cid) - if (current?.admissionId === undefined || isCurrentAdmissionId(current.admissionId)) { + if ( + current?.admissionId === undefined || + isCurrentAdmissionId(current.admissionId) || + isAdmissionSettled(current) + ) { return false } diff --git a/src/storage/lifecycle.ts b/src/storage/lifecycle.ts index 5da6500..d0e576a 100644 --- a/src/storage/lifecycle.ts +++ b/src/storage/lifecycle.ts @@ -4,6 +4,7 @@ import type { IpfsNode } from '../ipfs-node.js' import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' import { FileLifecycleBusyError, + isLifecycleBusy, type FileRecord, type FileRegistry, type LockedFileRegistry @@ -186,7 +187,7 @@ export async function confirmStoredFile(options: ConfirmOptions): Promise { const counts: Record = { temporary: 0, confirmed: 0, expired: 0 } for (const record of records) { diff --git a/src/storage/replicaStage.ts b/src/storage/replicaStage.ts index efc2239..39d41ee 100644 --- a/src/storage/replicaStage.ts +++ b/src/storage/replicaStage.ts @@ -13,8 +13,10 @@ import { isAdmissionSettled, protectedStorageBytes } from './registry.js' /** * Abort markers close the race where an abort reaches the peer while its stage * request is still pulling blocks and has not written transaction ownership. - * They only need to survive that in-process request; a restart cancels both - * sides of the race. + * They live in this process. A coordinator that already aborted will not reuse + * the same transaction id, so a source restart is a new request. If this node + * restarts while a late `stage` is still in flight, that copy can appear until + * settlement TTL — the same bound already used for abandoned stages. */ const abortedTransactions = new Map() const DEFAULT_ABORT_TOMBSTONE_TTL_MS = 5 * 60 * 1000 diff --git a/src/storage/service.ts b/src/storage/service.ts index 3ea1f4f..9039c0a 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -15,6 +15,7 @@ import { type LifecycleTarget } from './lifecycle.js' import { + FileLifecycleBusyError, isAdmissionSettled, isSettledHeldFile, protectedStorageBytes, @@ -99,7 +100,7 @@ function placementFor(cid: string, createdAt: number, peers: ReplicationPeer[]): async function placeReplicas( cid: string, createdAt: number, - transactionId?: string + store: (peer: ReplicationPeer) => Promise ): Promise { return replicate({ cid, @@ -107,7 +108,7 @@ async function placeReplicas( selfPeerId: selfPeerId(), peers: getReplicationPeers(), config: config.replication, - store: (peer) => placeCopy(peer, cid, transactionId), + store, cacheOnly: async (peer) => { await requestCache(helia, peer.multiAddr, cid, callOptions()) } @@ -126,7 +127,9 @@ export async function replicateUploadedFile( transactionId?: string ): Promise { const record = await fileRegistry.get(cid) - return placeReplicas(cid, record?.createdAt ?? Date.now(), transactionId) + return placeReplicas(cid, record?.createdAt ?? Date.now(), (peer) => + placeCopy(peer, cid, transactionId) + ) } /** Peers whose copies still belong to this upload transaction. */ @@ -152,12 +155,16 @@ function stagedPeers( * A lost commit acknowledgement is followed by `have`: if the peer applied the * decision, its durable state is authoritative. The upload response is withheld * when fewer than `required` copies can be proved permanent. + * + * Returns a new report. The placement object passed in is left unchanged so the + * caller can keep the original `attempts` for abort without sharing a mutated + * replica list. */ export async function commitUploadedReplicas( cid: string, transactionId: string, report: ReplicationReport -): Promise { +): Promise { const peers = stagedPeers(report, { includeFailed: false }) const results = await Promise.allSettled( peers.map(async (peer) => { @@ -189,11 +196,16 @@ export async function commitUploadedReplicas( const committed = results.flatMap((result) => result.status === 'fulfilled' ? [result.value] : [] ) - report.replicas = [...new Set([...stable, ...committed])] - report.acknowledged = report.replicas.length + 1 - report.satisfied = report.acknowledged >= report.required + const verified: ReplicationReport = { + ...report, + replicas: [...new Set([...stable, ...committed])], + acknowledged: 0, + satisfied: false + } + verified.acknowledged = verified.replicas.length + 1 + verified.satisfied = verified.acknowledged >= verified.required - if (!report.satisfied) { + if (!verified.satisfied) { const failures = results.flatMap((result, index) => result.status === 'rejected' ? [new Error(`Could not commit replica on ${peers[index]?.name}: ${result.reason}`)] @@ -201,9 +213,11 @@ export async function commitUploadedReplicas( ) throw new AggregateError( failures, - `Committed replica quorum not reached for ${cid}: ${report.acknowledged}/${report.required}` + `Committed replica quorum not reached for ${cid}: ${verified.acknowledged}/${verified.required}` ) } + + return verified } /** Withdraw every prepared remote copy before reporting a strict rejection. */ @@ -230,7 +244,9 @@ export async function abortUploadedReplicas( /** Place copies of a known file and persist the peers that acknowledged it. */ export async function replicateFile(cid: string): Promise { const record = await fileRegistry.get(cid) - const report = await placeReplicas(cid, record?.createdAt ?? Date.now()) + const report = await placeReplicas(cid, record?.createdAt ?? Date.now(), (peer) => + repairCopy(peer, cid, record?.admissionId) + ) if (report.mode === 'quorum') { await fileRegistry.setReplicas(cid, report.replicas) @@ -314,6 +330,51 @@ async function placeCopy( } } +/** + * Restore a missing durable copy without creating a new upload transaction. + * + * When the origin still has a settled admission token, `commit` is tried first: + * a peer that kept the prepared pin can promote it instead of waiting for TTL + * and a later `store`. `store` remains the fallback for an empty peer, and it + * still refuses a live stage it does not own. + */ +async function repairCopy( + peer: ReplicationPeer, + cid: string, + commitTransactionId?: string +): Promise { + try { + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return { outcome: 'stored', staged: false } + } + + if (commitTransactionId !== undefined) { + try { + await requestCommit(helia, peer.multiAddr, cid, commitTransactionId, callOptions()) + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return { outcome: 'stored', staged: false } + } + } catch { + // No matching stage, or the peer is still temporary; `store` is next. + } + } + + if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { + throw new Error('peer has no room for another copy') + } + + await requestStore(helia, peer.multiAddr, cid, callOptions()) + return { outcome: 'stored', staged: false } + } catch (err) { + if (!(err as Error).message.includes('Not authorized')) { + throw err + } + + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return 'cached' + } +} + /** The node and registry every lifecycle transition on this node works against. */ const lifecycleTarget = (): LifecycleTarget => ({ node: helia, registry: fileRegistry }) @@ -395,7 +456,7 @@ export async function acceptReplica(cid: string): Promise { // A strict upload still owns this copy. Promoting it would make abort a // no-op, while returning success would let repair count a temporary pin // as durable. - throw new Error(`CID ${cid} has an active replica transaction`) + throw new FileLifecycleBusyError(cid) } // Pulling the DAG first is what bounds this. Pinning would fetch it too, but @@ -413,7 +474,7 @@ export async function acceptReplica(cid: string): Promise { if (stored.replicaStage !== undefined) { // A stage may have appeared while this request was pulling the DAG. The // registry correctly kept it; do not turn that no-op into a durable ack. - throw new Error(`CID ${cid} has an active replica transaction`) + throw new FileLifecycleBusyError(cid) } return stored @@ -663,6 +724,30 @@ export interface RepairReport { rescued: string[] } +/** + * Drop a leftover admission token once this node no longer needs it to retry + * `commit`. Unsettled tokens are left alone: their request can still roll back. + */ +async function clearSettledAdmission(record: FileRecord): Promise { + if (record.admissionId === undefined) { + return + } + + await fileRegistry.withExclusiveCids([record.cid], async (locked) => { + const current = await locked.get(record.cid) + + if ( + current === undefined || + current.admissionId === undefined || + !isAdmissionSettled(current) + ) { + return + } + + await locked.save({ ...current, admissionId: undefined, admissionSettledAt: undefined }) + }) +} + /** Ask the designated peers which durable copies exist right now. */ async function liveHolderNames(holders: ReplicationPeer[], cid: string): Promise { const answers = await Promise.all( @@ -830,6 +915,7 @@ export async function repairReplication(): Promise { await fileRegistry.setReplicas(record.cid, live) if (!isUnderReplicated(live.length, placement, self)) { + await clearSettledAdmission(record) continue } @@ -842,6 +928,7 @@ export async function repairReplication(): Promise { report.stillMissing.push(record.cid) } else { report.repaired.push(record.cid) + await clearSettledAdmission(record) } } diff --git a/test/admission.test.ts b/test/admission.test.ts index a951c34..c75c5e0 100644 --- a/test/admission.test.ts +++ b/test/admission.test.ts @@ -53,4 +53,17 @@ describe('upload admission recovery', () => { assert.equal((await registry.get(CID_A))?.admissionId, undefined) assert.equal((await registry.get(CID_B))?.admissionId, currentId) }) + + it('keeps a settled token from an earlier process for commit retry', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-recovery-settled') + await registry.save({ + ...record(CID_A, 'previous-process:settled'), + admissionSettledAt: 1 + }) + + const report = await recoverInterruptedAdmissions(registry) + + assert.deepEqual(report, { checked: 0, recovered: 0, errors: [] }) + assert.equal((await registry.get(CID_A))?.admissionId, 'previous-process:settled') + }) }) diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index d0f7bd6..6b597a5 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -8,6 +8,7 @@ import { Key } from 'interface-datastore' import { FileRegistry, isExpired, + isLifecycleBusy, isReclaimable, isSettledHeldFile, type FileRecord @@ -585,4 +586,15 @@ describe('expiry rules', () => { ) assert.equal(isSettledHeldFile({ ...held, heldLocally: false }), false) }) + + it('treats replica stages and unsettled admissions as busy', () => { + const held: FileRecord = { ...record, state: 'confirmed', expiresAt: null } + + assert.equal(isLifecycleBusy(held), false) + assert.equal(isLifecycleBusy({ ...held, admissionId: 'upload-one' }), true) + assert.equal( + isLifecycleBusy({ ...held, admissionId: 'upload-one', admissionSettledAt: 1234 }), + false + ) + }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 9e23893..f0c2ff4 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1441,6 +1441,42 @@ describe('strict-upload replica staging', () => { await abortReplica({ node, registry, cid, transactionId: 'tx-admin' }) }) + it('allows confirm and release when only a settled admission residue remains', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_660, 'replica-settled-residue')) + await pinFile(node, cid) + await registry.save({ + cid: cid.toString(), + name: cid.toString(), + state: 'confirmed', + createdAt: 1000, + expiresAt: null, + confirmedAt: 1000, + fileSize: 21_660, + storedBytes: 21_660, + protectedBytes: 21_660, + pinned: true, + heldLocally: true, + replicas: [], + admissionId: 'leftover', + admissionSettledAt: 2000 + }) + + const confirmed = await confirmStoredFile({ + node, + registry, + unixfs: ifs, + cid, + temporaryTtlMs: 60_000 + }) + + assert.equal(confirmed?.admissionId, undefined) + assert.equal(confirmed?.state, 'confirmed') + + const released = await releaseStoredFile({ node, registry, cid }) + assert.equal(released?.state, 'expired') + }) + it('does not count an unsettled local upload as a stable replica', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(21_700, 'replica-stage-admission')) diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts index ebf8e25..dac7b3b 100644 --- a/test/integration/uploadRoute.test.ts +++ b/test/integration/uploadRoute.test.ts @@ -71,7 +71,7 @@ async function serveUpload( cid: string, transactionId: string, report: ReplicationReport - ) => Promise + ) => Promise abortReplicas?: (cid: string, transactionId: string, report: ReplicationReport) => Promise unpin?: (cid: CID) => Promise } = {} @@ -111,7 +111,7 @@ async function serveUpload( replicated.push(cid) return bestEffort() }), - commitReplicas: overrides.commitReplicas ?? (async () => undefined), + commitReplicas: overrides.commitReplicas ?? (async (_cid, _transactionId, report) => report), abortReplicas: overrides.abortReplicas ?? (async () => undefined), unpin: overrides.unpin, log: { info: () => undefined, error: () => undefined } @@ -504,7 +504,8 @@ describe('the upload endpoint end to end', () => { assert.equal(response.status, 500) assert.equal(commitAttempts, 1) assert.equal(record?.state, 'confirmed') - assert.equal(record?.admissionId, undefined) + assert.ok(record?.admissionId !== undefined) + assert.ok(record?.admissionSettledAt !== undefined) assert.equal(await isDirectlyPinned(node, CID.parse(cid)), true) } finally { harness.server.close() @@ -551,6 +552,7 @@ describe('the upload endpoint end to end', () => { setTimeout(() => reject(new Error('commits were serialized')), 1000) ) ]) + return report() } }) diff --git a/test/rollback.test.ts b/test/rollback.test.ts index 295e2c9..ee956ec 100644 --- a/test/rollback.test.ts +++ b/test/rollback.test.ts @@ -234,4 +234,31 @@ describe('rollbackUpload', () => { assert.equal(await registry.get(CID_A), undefined) assert.equal(pinned, false) }) + + it('lets a later upload take over a settled leftover admission token', async () => { + const registry = createRegistry() + await registry.withExclusiveCids([CID_A], async (locked) => { + await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'mine' + }) + const current = await locked.get(CID_A) + + if (current === undefined) { + throw new Error('expected a registered file') + } + + await locked.save({ ...current, admissionSettledAt: 1 }) + + const next = await locked.registerReplacing(file, { + confirmationRequired: false, + temporaryTtlMs: TTL, + admissionId: 'theirs' + }) + + assert.equal(next.record.admissionId, 'theirs') + assert.equal(next.record.admissionSettledAt, undefined) + }) + }) }) From 94aac8500ffb9eaeccb458ccefb992220f3b0088 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 26 Aug 2026 17:26:11 +0300 Subject: [PATCH 131/167] fix(storage): refine admission token lifecycle management and enhance upload handling - Improved management of admission tokens during the upload process, ensuring that active tokens are retained until the request completes. - Introduced `beginAdmission` and `endAdmission` functions to track in-process upload tokens, preventing operations on files with ongoing requests. - Enhanced `clearSettledAdmission` to correctly handle the cleanup of settled tokens, ensuring they are only cleared when no longer needed. - Updated tests to validate the behavior of admission tokens and their impact on file lifecycle states, ensuring robustness in upload operations. --- docs/storage-lifecycle.md | 20 ++++--- src/api/uploadRoute.ts | 5 +- src/storage/admission.ts | 38 ++++++++++++- src/storage/admissionState.ts | 29 ++++++++++ src/storage/registry.ts | 33 +++++++---- src/storage/service.ts | 29 +--------- test/admission.test.ts | 72 +++++++++++++++++++++++- test/fileRegistry.test.ts | 19 +++++++ test/integration/uploadRoute.test.ts | 82 +++++++++++++++++++++++++++- 9 files changed, 279 insertions(+), 48 deletions(-) create mode 100644 src/storage/admissionState.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index bbecf3d..ffead0a 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -276,14 +276,18 @@ restarting a peer does not turn a prepared copy into a permanent one. Live stages are kept through watermark pressure for that short window; `GET /api/storage/metrics` reports how many remain as `stagedReplicas`. -The local admission token remains until replica settlement finishes. A failed -cleanup write, or a `500` after an unverified commit, leaves the token in place. -`admissionSettledAt` still makes the record visible to `have`, repair, and -handover, and repair clears the token once the durable copies are confirmed. -Manual confirm, pin, and unpin return `409` while a replica stage or an -_unsettled_ local upload owns that CID; a leftover settled token does not block -them. On startup, _unsettled_ tokens belonging to a previous process are cleared -in the background; settled tokens are left for repair. +The local admission token remains until replica settlement finishes. The HTTP +handler keeps that token _active_ in process until it returns, even after +`admissionSettledAt` is written: confirm, pin, unpin, a second upload of the +same CID, and repair must not take it while remote commit is still running. A +failed cleanup write, or a `500` after an unverified commit, leaves the token +in place once the request has ended. `admissionSettledAt` still makes the +record visible to `have`, and repair then retries `commit` and clears the token +once the durable copies are confirmed. Manual confirm, pin, and unpin return +`409` while a replica stage, an unsettled upload, or an in-flight request owns +that CID; a leftover settled token from a finished request does not block them. +On startup, _unsettled_ tokens belonging to a previous process are cleared in +the background; settled tokens are left for repair. Turning it off with `replication.enabled: false` makes the node store content **best effort** instead: one copy, on this node, pinned, with no promise that diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index b4139a0..997a265 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -5,7 +5,7 @@ import type { FileRecord, FileRegistry } from '../storage/registry.js' import type { ReplicationReport } from '../storage/replication.js' import { rollbackUpload } from '../storage/rollback.js' import type { UploadSession } from '../storage/uploadSession.js' -import { createAdmissionId } from '../storage/admission.js' +import { createAdmissionId, beginAdmission, endAdmission } from '../storage/admission.js' import { UnixFsMulterFile } from '../utils/types.js' import { flatFiles } from '../utils/utils.js' @@ -250,6 +250,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ const session = dependencies.getSession(req) const admissionId = createAdmissionId() + beginAdmission(admissionId) try { const files = flatFiles(req.files as UnixFsMulterFile[]) @@ -551,6 +552,8 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ }) } catch (err) { next(err) + } finally { + endAdmission(admissionId) } } } diff --git a/src/storage/admission.ts b/src/storage/admission.ts index b83d742..c5b9f6e 100644 --- a/src/storage/admission.ts +++ b/src/storage/admission.ts @@ -1,5 +1,8 @@ import { randomUUID } from 'node:crypto' -import { isAdmissionSettled, type FileRegistry } from './registry.js' +import { isAdmissionSettled, type FileRecord, type FileRegistry } from './registry.js' +import { isActiveAdmission } from './admissionState.js' + +export { beginAdmission, endAdmission, isActiveAdmission } from './admissionState.js' const processAdmissionPrefix = `${randomUUID()}:` @@ -73,3 +76,36 @@ export async function recoverInterruptedAdmissions( return report } + +/** + * Drop a leftover admission token once this node no longer needs it to retry + * `commit`. Unsettled tokens and tokens whose upload request is still running + * are left alone. + * + * Repair reads the record before network probes. A later upload can replace + * `admissionId` while those probes run; clearing that newer token would strand + * its own `commit` retry. Only the id this pass selected may be removed. + */ +export async function clearSettledAdmission( + registry: FileRegistry, + record: FileRecord +): Promise { + if (record.admissionId === undefined || isActiveAdmission(record.admissionId)) { + return + } + + await registry.withExclusiveCids([record.cid], async (locked) => { + const current = await locked.get(record.cid) + + if ( + current === undefined || + current.admissionId !== record.admissionId || + !isAdmissionSettled(current) || + isActiveAdmission(current.admissionId) + ) { + return + } + + await locked.save({ ...current, admissionId: undefined, admissionSettledAt: undefined }) + }) +} diff --git a/src/storage/admissionState.ts b/src/storage/admissionState.ts new file mode 100644 index 0000000..e41c2a6 --- /dev/null +++ b/src/storage/admissionState.ts @@ -0,0 +1,29 @@ +/** + * In-process set of upload tokens whose HTTP request has not finished yet. + * + * `admissionSettledAt` is written before remote commit. Until the handler + * returns, that token still belongs to the request: confirm, release, a second + * upload, and repair must not take it. A leftover after the request ends is a + * different object — the id is no longer here. + */ + +const activeAdmissions = new Set() + +/** Record that this process still owns the upload request for `admissionId`. */ +export function beginAdmission(admissionId: string): void { + activeAdmissions.add(admissionId) +} + +/** Drop ownership when the upload request returns, succeeds or fails. */ +export function endAdmission(admissionId: string): void { + activeAdmissions.delete(admissionId) +} + +/** + * Whether the upload handler that created this token is still running. + * + * @param admissionId Token from a file record, or `undefined` when none + */ +export function isActiveAdmission(admissionId: string | undefined): boolean { + return admissionId !== undefined && activeAdmissions.has(admissionId) +} diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 80c9be1..bde9424 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto' import { AsyncLocalStorage } from 'node:async_hooks' import { Key } from 'interface-datastore' import type { Datastore, Pair } from 'interface-datastore' +import { isActiveAdmission } from './admissionState.js' /** * Datastore namespace of the lifecycle registry. @@ -559,8 +560,9 @@ export class FileRegistry { if ( existing !== undefined && - !isAdmissionSettled(existing) && - existing.admissionId !== options.admissionId + existing.admissionId !== undefined && + existing.admissionId !== options.admissionId && + (!isAdmissionSettled(existing) || isActiveAdmission(existing.admissionId)) ) { throw new FileLifecycleBusyError(file.cid) } @@ -654,12 +656,17 @@ export function isReclaimable(record: FileRecord, now: number = Date.now()): boo /** * Durable local copy that no in-flight upload still owns. * - * Repair and handover skip an admission token only until the request publishes - * `admissionSettledAt`. The token may remain after a failed cleanup write; once - * settled, no path can roll the local lifecycle back. + * Repair and handover skip an admission token until the request that created it + * has finished. `admissionSettledAt` makes the copy visible to `have` earlier; + * an active token still excludes it from sweeps that would clear or replace it. */ export function isSettledHeldFile(record: FileRecord): boolean { - return record.state === 'confirmed' && record.heldLocally && isAdmissionSettled(record) + return ( + record.state === 'confirmed' && + record.heldLocally && + isAdmissionSettled(record) && + !isActiveAdmission(record.admissionId) + ) } /** Whether an upload can no longer roll this local lifecycle back. */ @@ -668,13 +675,19 @@ export function isAdmissionSettled(record: FileRecord): boolean { } /** - * True while a replica stage or an unsettled local upload still owns this CID. + * True while a replica stage, an unsettled upload, or an in-flight request still + * owns this CID. * - * A leftover admission token after settlement is not busy: nothing can roll the - * local decision back, and repair uses that token to retry `commit`. + * `admissionSettledAt` is not enough: the HTTP handler writes it before remote + * commit. A token still listed as active belongs to that request. A leftover + * after the handler returns is not busy — repair uses it to retry `commit`. */ export function isLifecycleBusy(record: FileRecord): boolean { - return record.replicaStage !== undefined || !isAdmissionSettled(record) + return ( + record.replicaStage !== undefined || + !isAdmissionSettled(record) || + isActiveAdmission(record.admissionId) + ) } export function countByState(records: FileRecord[]): Record { diff --git a/src/storage/service.ts b/src/storage/service.ts index 9039c0a..311d5ab 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -21,6 +21,7 @@ import { protectedStorageBytes, type FileRecord } from './registry.js' +import { clearSettledAdmission } from './admission.js' import { isUnderReplicated, replicate, @@ -724,30 +725,6 @@ export interface RepairReport { rescued: string[] } -/** - * Drop a leftover admission token once this node no longer needs it to retry - * `commit`. Unsettled tokens are left alone: their request can still roll back. - */ -async function clearSettledAdmission(record: FileRecord): Promise { - if (record.admissionId === undefined) { - return - } - - await fileRegistry.withExclusiveCids([record.cid], async (locked) => { - const current = await locked.get(record.cid) - - if ( - current === undefined || - current.admissionId === undefined || - !isAdmissionSettled(current) - ) { - return - } - - await locked.save({ ...current, admissionId: undefined, admissionSettledAt: undefined }) - }) -} - /** Ask the designated peers which durable copies exist right now. */ async function liveHolderNames(holders: ReplicationPeer[], cid: string): Promise { const answers = await Promise.all( @@ -915,7 +892,7 @@ export async function repairReplication(): Promise { await fileRegistry.setReplicas(record.cid, live) if (!isUnderReplicated(live.length, placement, self)) { - await clearSettledAdmission(record) + await clearSettledAdmission(fileRegistry, record) continue } @@ -928,7 +905,7 @@ export async function repairReplication(): Promise { report.stillMissing.push(record.cid) } else { report.repaired.push(record.cid) - await clearSettledAdmission(record) + await clearSettledAdmission(fileRegistry, record) } } diff --git a/test/admission.test.ts b/test/admission.test.ts index c75c5e0..75e865a 100644 --- a/test/admission.test.ts +++ b/test/admission.test.ts @@ -4,7 +4,13 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { after, before, describe, it } from 'node:test' import { FsDatastore } from 'datastore-fs' -import { createAdmissionId, recoverInterruptedAdmissions } from '../src/storage/admission.js' +import { + beginAdmission, + clearSettledAdmission, + createAdmissionId, + endAdmission, + recoverInterruptedAdmissions +} from '../src/storage/admission.js' import { FileRegistry, type FileRecord } from '../src/storage/registry.js' const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' @@ -67,3 +73,67 @@ describe('upload admission recovery', () => { assert.equal((await registry.get(CID_A))?.admissionId, 'previous-process:settled') }) }) + +describe('settled admission cleanup', () => { + it('clears the leftover token this repair pass selected', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-clear-match') + const selected = await registry.save({ + ...record(CID_A, 'repair-pass'), + admissionSettledAt: 1 + }) + + await clearSettledAdmission(registry, selected) + + const current = await registry.get(CID_A) + assert.equal(current?.admissionId, undefined) + assert.equal(current?.admissionSettledAt, undefined) + }) + + it('does not clear an admission a later upload replaced', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-clear-replaced') + const selected = await registry.save({ + ...record(CID_A, 'repair-pass'), + admissionSettledAt: 1 + }) + await registry.save({ + ...selected, + admissionId: 'later-upload', + admissionSettledAt: 2 + }) + + await clearSettledAdmission(registry, selected) + + const current = await registry.get(CID_A) + assert.equal(current?.admissionId, 'later-upload') + assert.equal(current?.admissionSettledAt, 2) + }) + + it('does not clear a token whose upload request is still running', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-clear-active') + beginAdmission('live-request') + + try { + const selected = await registry.save({ + ...record(CID_A, 'live-request'), + admissionSettledAt: 1 + }) + + await clearSettledAdmission(registry, selected) + + const current = await registry.get(CID_A) + assert.equal(current?.admissionId, 'live-request') + assert.equal(current?.admissionSettledAt, 1) + } finally { + endAdmission('live-request') + } + }) + + it('does not clear an unsettled admission', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-clear-unsettled') + const selected = await registry.save(record(CID_A, 'still-open')) + + await clearSettledAdmission(registry, selected) + + assert.equal((await registry.get(CID_A))?.admissionId, 'still-open') + }) +}) diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 6b597a5..2f5de4d 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -13,6 +13,7 @@ import { isSettledHeldFile, type FileRecord } from '../src/storage/registry.js' +import { beginAdmission, endAdmission } from '../src/storage/admission.js' const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' const CID_B = 'bafkreihfzyt2g6pbd4rhk67deek7xh33xams74spn72eqq5qhx2ypphvii' @@ -584,6 +585,15 @@ describe('expiry rules', () => { isSettledHeldFile({ ...held, admissionId: 'upload-one', admissionSettledAt: 1234 }), true ) + beginAdmission('upload-one') + try { + assert.equal( + isSettledHeldFile({ ...held, admissionId: 'upload-one', admissionSettledAt: 1234 }), + false + ) + } finally { + endAdmission('upload-one') + } assert.equal(isSettledHeldFile({ ...held, heldLocally: false }), false) }) @@ -596,5 +606,14 @@ describe('expiry rules', () => { isLifecycleBusy({ ...held, admissionId: 'upload-one', admissionSettledAt: 1234 }), false ) + beginAdmission('upload-one') + try { + assert.equal( + isLifecycleBusy({ ...held, admissionId: 'upload-one', admissionSettledAt: 1234 }), + true + ) + } finally { + endAdmission('upload-one') + } }) }) diff --git a/test/integration/uploadRoute.test.ts b/test/integration/uploadRoute.test.ts index dac7b3b..10d514e 100644 --- a/test/integration/uploadRoute.test.ts +++ b/test/integration/uploadRoute.test.ts @@ -17,7 +17,7 @@ import { releaseStoredFile } from '../../src/storage/lifecycle.js' import { ConcurrencyLimiter } from '../../src/storage/limits.js' import { StorageOperationLock } from '../../src/storage/operationLock.js' import { isDirectlyPinned, unpinFile } from '../../src/storage/pinning.js' -import { FileRegistry } from '../../src/storage/registry.js' +import { FileRegistry, isLifecycleBusy, isSettledHeldFile } from '../../src/storage/registry.js' import type { ReplicationReport } from '../../src/storage/replication.js' import { resetClaims } from '../../src/storage/reservation.js' import { UnixfsMulterStorage } from '../../src/utils/unixfs-multer.storage.js' @@ -464,6 +464,86 @@ describe('the upload endpoint end to end', () => { } }) + it('keeps a settled admission busy until remote commit finishes', async () => { + let releaseCommit!: () => void + const hold = new Promise((resolve) => { + releaseCommit = resolve + }) + let reachedCommit!: () => void + const inCommit = new Promise((resolve) => { + reachedCommit = resolve + }) + const report: ReplicationReport = { + ...bestEffort(), + mode: 'quorum', + desiredCopies: 2, + copies: 2, + required: 2, + acknowledged: 2, + replicas: ['holder'], + satisfied: true, + networkTooSmall: true, + attempts: [ + { + node: 'holder', + peerId: 'holder-peer-id', + ok: true, + outcome: 'stored', + staged: true + } + ] + } + const harness = await serveUpload({ + requireQuorumOnUpload: true, + replicate: async () => report, + commitReplicas: async (cid, _transactionId, placement) => { + const record = await harness.registry.get(cid) + assert.ok(record) + assert.ok(record.admissionSettledAt !== undefined) + assert.equal(isLifecycleBusy(record), true) + assert.equal(isSettledHeldFile(record), false) + await assert.rejects( + () => releaseStoredFile({ node, registry: harness.registry, cid: CID.parse(cid) }), + /active lifecycle transaction/ + ) + await assert.rejects( + () => + harness.registry.withExclusiveCids([cid], (locked) => + locked.registerReplacing( + { cid, name: 'other.bin', fileSize: 1, storedBytes: 1 }, + { + confirmationRequired: false, + temporaryTtlMs: 60_000, + admissionId: 'other-upload' + } + ) + ), + /active lifecycle transaction/ + ) + reachedCommit() + await hold + return placement + } + }) + const payload = deterministicBytes(77_500, 'route-settled-commit-window') + const cid = (await ifs.addBytes(payload)).toString() + + try { + const pending = post(harness.url, [{ name: 'held.bin', bytes: payload }]) + await inCommit + releaseCommit() + const response = await pending + const record = await harness.registry.get(cid) + + assert.equal(response.status, 200) + assert.equal(record?.state, 'confirmed') + assert.equal(record?.admissionId, undefined) + assert.equal(await isDirectlyPinned(node, CID.parse(cid)), true) + } finally { + harness.server.close() + } + }) + it('does not report strict success when remote commit cannot be verified', async () => { let commitAttempts = 0 const report: ReplicationReport = { From 652e9be4fa71c955693a8185d20496242b0b8c5e Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 26 Aug 2026 18:26:04 +0300 Subject: [PATCH 132/167] fix(storage): improve upload admission and lifecycle management - Enhanced the management of admission tokens to ensure that only unsettled tokens are cleared, preventing interference with ongoing uploads. - Introduced mechanisms to mark and clear pin-intent markers, ensuring that temporary pins are correctly identified and managed during the upload lifecycle. - Updated the garbage collection process to respect the state of in-flight uploads, preventing premature eviction of temporary files. - Improved error handling and validation in the upload process to ensure robustness and prevent operations on files with active requests. - Added tests to validate the behavior of admission recovery and the handling of temporary pins, ensuring stability in upload operations. --- docs/storage-lifecycle.md | 28 ++++--- src/api/file.ts | 1 + src/api/storage.ts | 4 + src/api/uploadRoute.ts | 37 +++++++-- src/config.ts | 13 +++- src/gc.cron.ts | 6 ++ src/index.ts | 36 +++++++-- src/middleware/uploadAdmission.ts | 4 +- src/storage/admission.ts | 19 ++--- src/storage/backfill.ts | 52 +++++++++---- src/storage/config.ts | 13 +++- src/storage/gc.ts | 29 ++++--- src/storage/limits.ts | 8 +- src/storage/placement.ts | 2 +- src/storage/registry.ts | 70 +++++++++++++++-- src/storage/replicationProtocol.ts | 95 ++++++++++++++--------- src/storage/reservation.ts | 13 +++- src/storage/service.ts | 22 ++++-- src/storage/sweep.ts | 25 +++++- src/storage/uploadSession.ts | 51 ++++++++++++ src/utils/unixfs-multer.storage.ts | 10 ++- test/admission.test.ts | 20 ++++- test/garbageCollectionPlan.test.ts | 18 +++++ test/integration/storageLifecycle.test.ts | 4 +- test/placement.test.ts | 13 ++++ test/storageConfig.test.ts | 13 ++++ test/sweepBatch.test.ts | 13 ++++ test/uploadSession.test.ts | 95 +++++++++++++++++++++++ 28 files changed, 587 insertions(+), 127 deletions(-) create mode 100644 test/uploadSession.test.ts diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index ffead0a..8d23db6 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -523,8 +523,12 @@ operational cost. - `stagedReplicas` — how many registry entries still belong to an in-flight strict upload -`replication.lastRun` on the same payload is counts only: the CID lists from a -repair pass stay on `POST /api/storage/repair`, which is behind the admin key. +The route is public so a client can see capacity before uploading. It includes +watermarks and job schedules; it does not include filenames, CID lists, or peer +identity. Those stay on `POST /api/storage/gc` and `POST /api/storage/repair`, +which are behind the admin key. + +`replication.lastRun` on the same payload is counts only. A collection report also lists `demoted`: files whose local copy was handed over to their designated holders during that pass. @@ -556,10 +560,14 @@ Startup walks the pinset once and records anything the registry does not know as is not fully local is skipped rather than recorded — calling it confirmed would claim more than the node can serve. -Which pins count as legacy is decided before the API accepts anything, by -listing the pinset once. Anything pinned after that belongs to a request with a -lifecycle of its own. Listing is cheap; reconciling each pin is not, which is -why only the first part is ordered against the listener. +Which pins count as legacy is decided before the replication protocol is +registered and before the API accepts anything, by listing the pinset once. +Anything pinned after that belongs to a request with a lifecycle of its own. + +A pin created after the snapshot but never registered (a crash between pin and +registry write) carries a pin-intent marker. Backfill records that pin as +`temporary` when `storage.confirmationRequired` is on, so it expires instead of +becoming durable without a confirmation. Records are then created only while the CID is absent, and every read-modify-write on the registry is serialised per CID. Both are needed: @@ -574,9 +582,11 @@ serve. Reads, uploads and incoming copies do not need a complete registry; the collector and the repair sweep are started once the walk finishes, so neither acts on a half-built picture. -The same startup phase clears admission tokens left by the previous process. -Tokens created by the running process carry its own prefix and are skipped, so -this background recovery cannot settle an upload that is still in flight. +The same startup phase clears admission tokens left by a request that can no +longer resume. Tokens whose HTTP handler is still running in this process are +skipped; unsettled tokens from a previous process, or from a handler in this +process that has already returned, are removed so they cannot hide a pin from +repair. Two consequences are worth planning for: diff --git a/src/api/file.ts b/src/api/file.ts index a297d12..3b69915 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -52,6 +52,7 @@ router.post( replicate: replicateUploadedFile, commitReplicas: commitUploadedReplicas, abortReplicas: abortUploadedReplicas, + pinTimeoutMs: config.findFileTimeout, log: logger }) ) diff --git a/src/api/storage.ts b/src/api/storage.ts index 107c9b8..3d9e22d 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -24,6 +24,10 @@ export const storageAdminRouter = Router() * * Values come from the disk usage schedule, so they are as fresh as * `diskUsageScanPeriod`. + * + * This route is public: it includes free space, watermarks and job schedules + * so a client can see capacity. It does not include filenames, CID lists, or + * peer identity. Those stay on the admin collection and repair endpoints. */ router.get('/metrics', readLimiter, (req, res) => { res.send({ diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index 997a265..5e59fe3 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -37,6 +37,11 @@ export interface UploadRouteDependencies { abortReplicas: (cid: string, transactionId: string, report: ReplicationReport) => Promise /** Test seam for pin-datastore failure paths. */ unpin?: (cid: UnixFsMulterFile['cid']) => Promise + /** + * Bounds the pin DAG walk. Uploaded blocks are already local; the deadline + * is a backstop if disconnect cleanup won the race and the walk would fetch. + */ + pinTimeoutMs?: number log: UploadRouteLog } @@ -243,16 +248,20 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ const { node, registry, log } = dependencies return async (req: Request, res: Response, next: NextFunction) => { - if (!Array.isArray(req.files) || req.files.length === 0) { - res.statusCode = 400 - return res.send({ error: 'No file uploaded' }) - } - - const session = dependencies.getSession(req) const admissionId = createAdmissionId() beginAdmission(admissionId) try { + if (!Array.isArray(req.files) || req.files.length === 0) { + res.statusCode = 400 + return res.send({ error: 'No file uploaded' }) + } + + const session = dependencies.getSession(req) + if (!session.claim()) { + throw new Error('Upload was aborted before it could be committed') + } + const files = flatFiles(req.files as UnixFsMulterFile[]) log.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) @@ -303,10 +312,16 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } } const previous = await locked.get(cid) - const createdPin = await pinFile(node, file.cid) + await locked.markPinIntent(cid) + const pinSignal = + dependencies.pinTimeoutMs === undefined + ? undefined + : AbortSignal.timeout(dependencies.pinTimeoutMs) + let createdPin = false let registration try { + createdPin = await pinFile(node, file.cid, pinSignal) registration = await locked.registerReplacing( { cid, @@ -326,9 +341,12 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // key. Restore both sides from the baseline captured under the // same CID lock, so neither outcome leaks a pin or a record. await rollbackUpload({ registry: locked, cid, previous, createdPin, unpin }) + await locked.clearPinIntent(cid) throw err } + await locked.clearPinIntent(cid) + if (!baselines.has(cid)) { baselines.set(cid, { cid, previous, createdPin, unpin }) } @@ -364,6 +382,11 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // One request may contain the same content more than once. Place each CID // once, using the registration that stored it last. + // + // `requireQuorumOnUpload` cannot be paired with `confirmationRequired`, so + // every admitted record here is `confirmed`. Temporary records would have + // nothing to commit and would keep a settled admission token; that pairing + // is rejected at startup rather than handled as a second branch. const durableByCid = new Map( admitted.records .filter((record) => record.state === 'confirmed') diff --git a/src/config.ts b/src/config.ts index fc885d8..6dcfedd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -250,8 +250,17 @@ export function validateConfig(raw: unknown): Config { // Owns the storage lifecycle and replication policy. Both sections are // optional: every option falls back to a documented default so that config // files written before this feature keep working. - const storage = resolveStorageConfig(root.storage, root.uploadLimitSizeBytes as number) - const replication = resolveReplicationConfig(root.replication) + let storage: StorageConfig + let replication: ReplicationConfig + try { + storage = resolveStorageConfig(root.storage, root.uploadLimitSizeBytes as number) + replication = resolveReplicationConfig(root.replication) + } catch (err) { + if (err instanceof ConfigError) { + throw err + } + throw new ConfigError((err as Error).message) + } if (storage.confirmationRequired && replication.requireQuorumOnUpload) { fail( diff --git a/src/gc.cron.ts b/src/gc.cron.ts index b2ca50e..a298a08 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -1,6 +1,7 @@ import { CronJob } from 'cron' import { config } from './config.js' import { helia } from './helia.js' +import { recoverInterruptedAdmissions } from './storage/admission.js' import { collectStorage, type CollectionReport } from './storage/collection.js' import { refreshStorageMetrics } from './storage/metrics.js' import { demoteReleasableCopies } from './storage/service.js' @@ -35,6 +36,11 @@ export async function collectGarbage( running = true try { + const recovery = await recoverInterruptedAdmissions(fileRegistry) + for (const error of recovery.errors) { + logger.warn(`Admission recovery: ${error}`) + } + const collection = await collectStorage({ lock: storageOperationLock, node: helia, diff --git a/src/index.ts b/src/index.ts index d623030..e747803 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,11 +28,19 @@ diskUsageCron.start() peeringCron.start() await peerWithKnownNodes() +// Which pins are legacy is decided here, before this node accepts new pins +// over the replication protocol or the upload API. A replica staged after +// this snapshot is a request with a lifecycle of its own; if the backfill +// saw it after an abort, it would record the leftover pin as confirmed. +const legacyPins = await snapshotPins(helia) + // Answering the protocol is what lets peers place copies here, and it is // registered whether or not this node places copies of its own. A node that // only accepted copies when it was also sending them could not be added to a // network without every other node being reconfigured first. -await registerReplicationProtocol(helia, createReplicationHandlers()) +await registerReplicationProtocol(helia, createReplicationHandlers(), { + requestTimeoutMs: config.replication.requestTimeoutMs +}) /** The scheduled work that reads the file registry and acts on what it finds. */ function startLifecycleJobs(): void { @@ -62,12 +70,8 @@ function startLifecycleJobs(): void { // Content pinned before this release has no registry entry, which would leave // it out of the storage report, out of dry runs, and out of replication repair. -// Recording it is idempotent. -// -// Which pins are legacy is decided here, before the API can accept an upload: -// anything pinned after this line belongs to a request with a lifecycle of its -// own. Listing them is cheap; reconciling them is not. -const legacyPins = await snapshotPins(helia) +// Recording it is idempotent. Listing is cheap; reconciling is not, which is +// why only the snapshot is ordered against protocol registration and the listener. // The reconciliation is deliberately not awaited. It is as long as the pinset, // and blocking here would keep the whole API — reads included — unavailable for @@ -79,6 +83,8 @@ void Promise.all([ cids: legacyPins, unixfs: ifs, registry: fileRegistry, + confirmationRequired: config.storage.confirmationRequired, + temporaryTtlMs: config.storage.temporaryTtlMs, log: (message) => logger.info(message) }), recoverInterruptedAdmissions(fileRegistry) @@ -134,10 +140,24 @@ mountApiRoutes(app, routers, createApiKeyAuth(config.adminApiKey), config.enable app.use(notFoundHandler) app.use(errorHandler) -app.listen(PORT, () => { +const server = app.listen(PORT, () => { logger.info(`Server is running on http://localhost:${PORT}`) logger.warn( 'TLS is not handled at the application level. ' + 'Ensure this service is deployed behind a TLS-terminating reverse proxy (e.g. nginx, Caddy).' ) }) + +function shutdown(signal: string): void { + logger.info(`Received ${signal}, shutting down`) + diskUsageCron.stop() + peeringCron.stop() + garbageCollectionCron.stop() + replicationRepairCron.stop() + server.close(() => { + void helia.stop().finally(() => process.exit(0)) + }) +} + +process.once('SIGINT', () => shutdown('SIGINT')) +process.once('SIGTERM', () => shutdown('SIGTERM')) diff --git a/src/middleware/uploadAdmission.ts b/src/middleware/uploadAdmission.ts index 9ce0539..ddfd2ac 100644 --- a/src/middleware/uploadAdmission.ts +++ b/src/middleware/uploadAdmission.ts @@ -99,12 +99,12 @@ export function createUploadAdmission(dependencies: UploadAdmissionDependencies) res.on('close', () => { release() - if (session === undefined || session.isSettled) { + if (session === undefined || session.isSettled || session.isClaimed) { return } session - .cleanup() + .cleanupIfUnclaimed() .then((removed) => { if (removed > 0) { dependencies.log.info(`Removed ${removed} blocks left by an unfinished upload`) diff --git a/src/storage/admission.ts b/src/storage/admission.ts index c5b9f6e..562261a 100644 --- a/src/storage/admission.ts +++ b/src/storage/admission.ts @@ -23,13 +23,14 @@ export interface AdmissionRecoveryReport { } /** - * Clear upload ownership left by a process that stopped before cleanup. + * Clear upload ownership left by a request that can no longer resume. * - * A current-process token is skipped because its request can still compensate - * the record. A previous process cannot resume an unsettled request; retaining - * that token would hide a confirmed pin from repair and handover forever. The - * file itself stays exactly as it was — only stale transaction ownership is - * removed. + * A token whose HTTP handler is still running is skipped. An earlier process + * cannot resume an unsettled request; retaining that token would hide a + * confirmed pin from repair and handover forever. The same is true of an + * unsettled token this process created after its handler returned — a failed + * rollback or a failed settlement write. The file itself stays exactly as it + * was; only stale transaction ownership is removed. * * A settled token is kept. Repair uses it to retry `commit` on peers that still * hold a prepared copy, then clears it once those copies are durable. @@ -40,8 +41,8 @@ export async function recoverInterruptedAdmissions( const candidates = (await registry.all()).filter( (record) => record.admissionId !== undefined && - !isCurrentAdmissionId(record.admissionId) && - !isAdmissionSettled(record) + !isAdmissionSettled(record) && + !isActiveAdmission(record.admissionId) ) const report: AdmissionRecoveryReport = { checked: candidates.length, recovered: 0, errors: [] } @@ -52,7 +53,7 @@ export async function recoverInterruptedAdmissions( if ( current?.admissionId === undefined || - isCurrentAdmissionId(current.admissionId) || + isActiveAdmission(current.admissionId) || isAdmissionSettled(current) ) { return false diff --git a/src/storage/backfill.ts b/src/storage/backfill.ts index c980199..aa7cdad 100644 --- a/src/storage/backfill.ts +++ b/src/storage/backfill.ts @@ -34,19 +34,25 @@ export interface BackfillOptions { cids: string[] unixfs: UnixFS registry: FileRegistry + /** + * When true, a pin that still carries a pin-intent marker is recorded as + * `temporary` instead of `confirmed`. That marker means an upload crashed + * between pinning and registration; promoting it would bypass confirmation. + */ + confirmationRequired?: boolean + /** TTL applied to an interrupted upload when {@link confirmationRequired} is set. */ + temporaryTtlMs?: number log?: (message: string) => void } /** * List the pins that exist right now. * - * This has to happen before the node accepts its first upload, and it is why - * enumeration is separate from the work that follows. A pin created after this - * point belongs to an upload with a lifecycle of its own; if the backfill saw - * it, it would record it as legacy content — `confirmed`, with no expiry, named - * by its CID — in the window between the upload pinning the file and - * registering it. An operator who required confirmation would find the file - * durable without ever confirming it. + * This has to happen before the node accepts pins over the replication + * protocol or the upload API, and it is why enumeration is separate from the + * work that follows. A pin created after this point belongs to a request with + * a lifecycle of its own; if the backfill saw a replica that had already been + * aborted, it would record the leftover pin as confirmed. * * Listing pins is cheap next to what the backfill then does with them, so the * part that must be ordered is also the part that costs almost nothing. @@ -90,6 +96,7 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis try { if (await options.registry.get(cid)) { report.known += 1 + await options.registry.clearPinIntent(cid) continue } } catch (err) { @@ -97,20 +104,35 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis continue } + let interrupted: boolean + try { + interrupted = await options.registry.hasPinIntent(cid) + } catch (err) { + report.errors.push(`Pin-intent read failed for ${cid}: ${(err as Error).message}`) + continue + } + let stats try { // Offline on purpose: a pin whose blocks are not all here must not send // the node looking for them on the network at startup. stats = await options.unixfs.stat(CID.parse(cid), { extended: true, offline: true }) - } catch { - // The only failure this catch may claim. A datastore that is unavailable - // says nothing about whether the DAG is complete, and reporting it as - // incomplete content would send an operator after the wrong problem. + } catch (err) { + const code = (err as { code?: string }).code + if (code === 'EIO' || code === 'EACCES') { + report.errors.push(`Blockstore read failed for ${cid}: ${(err as Error).message}`) + continue + } + // Missing blocks, non-UnixFS DAGs, timeouts: the pin stays, but calling + // the file confirmed would claim more than the node can serve. report.incomplete += 1 continue } + const treatAsTemporary = interrupted && options.confirmationRequired === true + const ttl = options.temporaryTtlMs ?? 0 + try { // Create-if-absent rather than a plain write: the API is serving while // this runs, so an upload can register the same CID between the check @@ -122,12 +144,12 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis // Nothing recorded the original name, and inventing one would be worse // than admitting it is unknown. name: cid, - state: 'confirmed', + state: treatAsTemporary ? 'temporary' : 'confirmed', // The upload happened before this release; its time is not recoverable, // so the file counts as new and is placed as widely as a fresh one. createdAt: now, - expiresAt: null, - confirmedAt: now, + expiresAt: treatAsTemporary ? now + ttl : null, + confirmedAt: treatAsTemporary ? null : now, fileSize: Number(stats.size), storedBytes: Number(stats.deduplicatedDagSize), protectedBytes: Number(stats.deduplicatedDagSize), @@ -136,6 +158,8 @@ export async function backfillRegistryFromPins(options: BackfillOptions): Promis replicas: [] }) + await options.registry.clearPinIntent(cid) + if (created === undefined) { report.known += 1 } else { diff --git a/src/storage/config.ts b/src/storage/config.ts index e5b2863..aedcded 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -271,6 +271,12 @@ function resolvePlacement(raw: unknown, defaults: PlacementTier[]): PlacementTie if (tiers[index].minAgeMs <= tiers[index - 1].minAgeMs) { fail(`replication.placement[${index}].minAgeMs`, 'must be greater than the previous tier') } + if (tiers[index].copies > tiers[index - 1].copies) { + fail( + `replication.placement[${index}].copies`, + 'must not exceed the previous tier so copies shrink as a file ages' + ) + } } return tiers @@ -313,9 +319,12 @@ export function resolveReplicationConfig(raw: unknown): ReplicationConfig { ) } - const mostCopies = Math.max(...placement.map((tier) => tier.copies)) + const mostCopies = placement[0]?.copies ?? 0 if (replication.ackQuorum > mostCopies) { - fail('replication.ackQuorum', 'must not exceed the largest copy count in replication.placement') + fail( + 'replication.ackQuorum', + 'must not exceed the fresh-tier copy count in replication.placement' + ) } if (replication.requireQuorumOnUpload && replication.ackQuorum < 2) { diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 8536525..05ca21f 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -3,7 +3,13 @@ import type { IpfsNode } from '../ipfs-node.js' import type { GarbageCollectionConfig } from './config.js' import { isProtected, pinFile, unpinFile } from './pinning.js' import { restoreReplicaStage } from './replicaStage.js' -import { FileRegistry, isExpired, protectedStorageBytes, type FileRecord } from './registry.js' +import { + FileRegistry, + isAdmissionInFlight, + isExpired, + protectedStorageBytes, + type FileRecord +} from './registry.js' export type Watermarks = Pick @@ -70,15 +76,14 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { const now = input.now ?? Date.now() const { highWatermarkBytes, lowWatermarkBytes } = input.watermarks - const confirmed = input.records.filter((record) => record.state === 'confirmed') const unconfirmed = input.records.filter((record) => record.state !== 'confirmed') - const expired = unconfirmed.filter((record) => isExpired(record, now)) - const prepared = unconfirmed.filter( - (record) => record.replicaStage !== undefined && !isExpired(record, now) + const expired = unconfirmed.filter( + (record) => isExpired(record, now) && !isAdmissionInFlight(record) ) const alive = unconfirmed.filter( - (record) => record.replicaStage === undefined && !isExpired(record, now) + (record) => + record.replicaStage === undefined && !isExpired(record, now) && !isAdmissionInFlight(record) ) // Helia GC removes every unpinned block, including read cache and files a @@ -137,6 +142,9 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { } evicted.push(record) + // Shared DAG blocks are counted once per file, and an already-unpinned + // residue frees nothing on unpin. The next run re-measures; this is an + // estimate so the planner stops, not an accounting of bytes on disk. estimatedBytesAfter -= protectedStorageBytes(record) } } @@ -148,11 +156,7 @@ export function planGarbageCollection(input: GcPlanInput): GcPlan { trigger, expired, evicted, - retained: [ - ...confirmed, - ...prepared, - ...alive.filter((record) => !releasedCids.has(record.cid)) - ], + retained: input.records.filter((record) => !releasedCids.has(record.cid)), estimatedBytesAfter: Math.max(0, estimatedBytesAfter) } } @@ -366,7 +370,8 @@ export async function runGarbageCollection(options: GcRunOptions): Promise save(record: FileRecord): Promise remove(cid: string): Promise + markPinIntent(cid: string): Promise + clearPinIntent(cid: string): Promise + hasPinIntent(cid: string): Promise registerReplacing( file: NewFileRecord, options: RegisterFileOptions @@ -352,6 +355,9 @@ export class FileRegistry { get: (cid) => this.get(cid), save: (record) => this.save(record), remove: (cid) => this.removeRecord(cid), + markPinIntent: (cid) => this.markPinIntent(cid), + clearPinIntent: (cid) => this.clearPinIntent(cid), + hasPinIntent: (cid) => this.hasPinIntent(cid), registerReplacing: (file, options) => this.registerExclusively(file, options) } @@ -480,6 +486,46 @@ export class FileRegistry { } } + /** + * Record that a pin is about to be created for `cid` before its registry + * entry exists. + * + * Startup backfill would otherwise treat a crash between `pinFile` and + * `registerReplacing` as legacy content and make it forever-confirmed. + * Stored under a sibling prefix so {@link list} does not see it as a record. + */ + async markPinIntent(cid: string): Promise { + await this.datastore.put(this.intentKey(cid), encoder.encode('1')) + } + + /** Drop the pin-intent marker once the registry entry is written or undone. */ + async clearPinIntent(cid: string): Promise { + try { + await this.datastore.delete(this.intentKey(cid)) + } catch (err) { + if (!isNotFound(err)) { + throw err + } + } + } + + /** Whether an in-flight pin for `cid` was interrupted before registration. */ + async hasPinIntent(cid: string): Promise { + try { + await this.datastore.get(this.intentKey(cid)) + return true + } catch (err) { + if (isNotFound(err) || isMissingStore(err)) { + return false + } + throw err + } + } + + private intentKey(cid: string): Key { + return new Key(`/adm/pin-intent/${this.prefix.slice(1)}/${cid}`) + } + async *list(): AsyncGenerator { // `query` may return a sync iterable (memory) or an async one (file system). const source = this.datastore.query({ prefix: this.prefix }) @@ -519,7 +565,15 @@ export class FileRegistry { } } - /** Every record, materialised for callers that need more than one pass. */ + /** + * Every record, materialised for callers that need more than one pass. + * + * Repair, collection, metrics, and admission recovery all call this on their + * tick. Records are small, so the cost is acceptable at the current scale; + * it is the growth ceiling. Bounded sweeps cover repair/demote/rescue, not + * this listing. The GC pin guard rail must still walk every confirmed held + * file before deleting anything — do not cap that walk. + */ async all(): Promise { const records: FileRecord[] = [] for await (const record of this.list()) { @@ -674,6 +728,14 @@ export function isAdmissionSettled(record: FileRecord): boolean { return record.admissionId === undefined || record.admissionSettledAt !== undefined } +/** + * True while the upload that owns this record can still roll it back, or its + * HTTP handler is still running after settlement. + */ +export function isAdmissionInFlight(record: FileRecord): boolean { + return !isAdmissionSettled(record) || isActiveAdmission(record.admissionId) +} + /** * True while a replica stage, an unsettled upload, or an in-flight request still * owns this CID. @@ -683,11 +745,7 @@ export function isAdmissionSettled(record: FileRecord): boolean { * after the handler returns is not busy — repair uses it to retry `commit`. */ export function isLifecycleBusy(record: FileRecord): boolean { - return ( - record.replicaStage !== undefined || - !isAdmissionSettled(record) || - isActiveAdmission(record.admissionId) - ) + return record.replicaStage !== undefined || isAdmissionInFlight(record) } export function countByState(records: FileRecord[]): Record { diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index 8d530ac..227e923 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -1,6 +1,7 @@ import type { Connection, Stream } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' import type { PeerId } from '@libp2p/interface' +import { CID } from 'multiformats/cid' import type { IpfsNode } from '../ipfs-node.js' /** @@ -58,7 +59,9 @@ export type ReplicationRequest = * * Accepted from any peer, because it grants nothing a reader does not already * have: the blocks are unpinned, so they are reclaimed as soon as space is - * short, exactly like content cached while answering a read. + * short, exactly like content cached while answering a read. The DAG is + * fetched through Bitswap from whichever connected peer has the blocks, not + * only from the requester — the same path a public read uses. */ | { op: 'cache'; cid: string } @@ -106,8 +109,9 @@ export interface ReplicationHandlers { /** * Pull `cid` into the local blockstore without pinning or registering it. * - * The node can serve the file from now on, and gives up nothing: the blocks - * live in the same tier as read cache and go when space is needed. + * Fetches via Bitswap, so blocks may come from any connected peer that has + * them, not only from the caller. Bounded by the intake budget and disk + * reserve. The copy sits in the same tier as read cache. */ cacheCopy(cid: string, peerId: string): Promise onError?(message: string): void @@ -148,38 +152,49 @@ function encodeMessage(message: unknown): Uint8Array { } /** Read exactly one framed message, without waiting for the stream to end. */ -async function readMessage(stream: Stream): Promise { - const chunks: Uint8Array[] = [] - let received = 0 - - for await (const chunk of stream) { - const bytes = toBytes(chunk) - chunks.push(bytes) - received += bytes.byteLength - - if (received > MAX_MESSAGE_BYTES + LENGTH_PREFIX_BYTES) { - throw new Error('Replication message is too large') - } - - if (received < LENGTH_PREFIX_BYTES) { - continue - } +async function readMessage(stream: Stream, signal?: AbortSignal): Promise { + if (signal?.aborted === true) { + throw new Error('Replication message timed out') + } - const buffer = Buffer.concat(chunks) - const length = buffer.readUInt32BE(0) + const abort = (): void => stream.abort(new Error('Replication message timed out')) + signal?.addEventListener('abort', abort, { once: true }) - if (length > MAX_MESSAGE_BYTES) { - throw new Error('Replication message is too large') + try { + const chunks: Uint8Array[] = [] + let received = 0 + + for await (const chunk of stream) { + const bytes = toBytes(chunk) + chunks.push(bytes) + received += bytes.byteLength + + if (received > MAX_MESSAGE_BYTES + LENGTH_PREFIX_BYTES) { + throw new Error('Replication message is too large') + } + + if (received < LENGTH_PREFIX_BYTES) { + continue + } + + const buffer = Buffer.concat(chunks) + const length = buffer.readUInt32BE(0) + + if (length > MAX_MESSAGE_BYTES) { + throw new Error('Replication message is too large') + } + + if (buffer.byteLength >= LENGTH_PREFIX_BYTES + length) { + return JSON.parse( + buffer.subarray(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + length).toString('utf8') + ) as unknown + } } - if (buffer.byteLength >= LENGTH_PREFIX_BYTES + length) { - return JSON.parse( - buffer.subarray(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + length).toString('utf8') - ) as unknown - } + throw new Error('Replication stream ended before a complete message arrived') + } finally { + signal?.removeEventListener('abort', abort) } - - throw new Error('Replication stream ended before a complete message arrived') } /** Send one framed message. The stream stays open for the reply. */ @@ -194,6 +209,12 @@ function parseRequest(value: unknown): ReplicationRequest { throw new Error('Replication request is missing a CID') } + try { + CID.parse(message.cid) + } catch { + throw new Error('Replication request has an invalid CID') + } + const operations: ReplicationRequest['op'][] = [ 'store', 'stage', @@ -225,9 +246,10 @@ function parseRequest(value: unknown): ReplicationRequest { async function respond( stream: Stream, connection: Connection, - handlers: ReplicationHandlers + handlers: ReplicationHandlers, + requestTimeoutMs: number ): Promise { - const request = parseRequest(await readMessage(stream)) + const request = parseRequest(await readMessage(stream, AbortSignal.timeout(requestTimeoutMs))) const peerId = connection.remotePeer.toString() // Holding an extra copy is open to anyone, because it costs no more than a @@ -286,10 +308,13 @@ async function respond( /** Start answering replication requests from other ADAMANT nodes. */ export async function registerReplicationProtocol( node: IpfsNode, - handlers: ReplicationHandlers + handlers: ReplicationHandlers, + options: { requestTimeoutMs?: number } = {} ): Promise { + const requestTimeoutMs = options.requestTimeoutMs ?? 15_000 + await node.libp2p.handle(SUPPORTED_REPLICATION_PROTOCOLS, (stream, connection) => { - respond(stream, connection, handlers) + respond(stream, connection, handlers, requestTimeoutMs) .then(async () => stream.close()) .catch((err: Error) => { handlers.onError?.(`Replication request failed: ${err.message}`) @@ -317,7 +342,7 @@ async function call( try { sendMessage(stream, request) - const response = (await readMessage(stream)) as ReplicationResponse + const response = (await readMessage(stream, signal)) as ReplicationResponse if (response?.ok !== true) { throw new Error(response?.error ?? 'Replication request was refused') diff --git a/src/storage/reservation.ts b/src/storage/reservation.ts index 882658a..6ac22ed 100644 --- a/src/storage/reservation.ts +++ b/src/storage/reservation.ts @@ -1,3 +1,5 @@ +import { checkDiskReserve } from './limits.js' + /** * Free space promised to work that is already running. * @@ -8,7 +10,7 @@ * may write. * * Every admission therefore claims the bytes it might write, and the claim is - * counted against free space until the work finishes. + * counted against free space until the HTTP response finishes. */ let claimed = 0 @@ -39,7 +41,14 @@ export interface ClaimRequest { * @returns The claim, or `undefined` when granting it would break the reserve */ export function claimSpace(request: ClaimRequest): Claim | undefined { - if (request.availableBytes - claimed - request.bytes < request.reserveBytes) { + const remaining = request.availableBytes - claimed + if ( + !checkDiskReserve({ + availableBytes: remaining, + reserveBytes: request.reserveBytes, + requestedBytes: request.bytes + }).allowed + ) { return undefined } diff --git a/src/storage/service.ts b/src/storage/service.ts index 311d5ab..bba235a 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -74,11 +74,19 @@ function selfPeerId(): string { * libp2p handshake proves the peer id. */ export function getReplicationPeers(): ReplicationPeer[] { - return getNodesList([selfPeerId()]).map((node) => ({ - name: node.name, - peerId: node.peerId.toString(), - multiAddr: node.multiAddr - })) + const seen = new Set() + const peers: ReplicationPeer[] = [] + + for (const node of getNodesList([selfPeerId()])) { + const peerId = node.peerId.toString() + if (seen.has(peerId)) { + continue + } + seen.add(peerId) + peers.push({ name: node.name, peerId, multiAddr: node.multiAddr }) + } + + return peers } function placementFor(cid: string, createdAt: number, peers: ReplicationPeer[]): Placement { @@ -649,6 +657,10 @@ async function pullUnderIntakeLimits( // as a value rather than inline. Without it the exporter requests the whole // DAG before the meter can count a single byte, and the limit bounds // nothing at all. + // + // Bitswap may fetch from any connected peer that has the blocks, not only + // from the requester. That is the same path a public read uses; the intake + // budget and disk reserve are what bound it. const readOptions = { signal, blockReadConcurrency: INTAKE_READ_CONCURRENCY } for await (const chunk of metered.cat(CID.parse(cid), readOptions)) { diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index 88ebdac..edbe25c 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -17,7 +17,9 @@ const cursors = new Map() * * Slicing from the front would examine the same files on every pass and never * reach the rest, so the batch wraps around the end and the cursor moves with - * it. A cursor pointing at a record that has since gone simply starts over. + * A cursor pointing at a record that has since gone resumes at the first + * remaining CID greater than it, so a successful demote/rescue of the last + * batch element does not rewind to the head. * * @param sweep Which sweep is asking; each keeps its own position * @param records Every candidate, in a stable order @@ -41,9 +43,24 @@ export function nextSweepBatch( } const previous = cursors.get(sweep) - const resumeAt = - previous === undefined ? 0 : records.findIndex((item) => item.cid === previous) + 1 - const start = resumeAt > 0 && resumeAt < records.length ? resumeAt : 0 + let start = 0 + + if (previous !== undefined) { + const exact = records.findIndex((item) => item.cid === previous) + if (exact >= 0) { + start = exact + 1 + } else { + // The cursor record left the candidate set (the usual success path for + // demote/rescue). Resume at the first CID strictly greater so the pass + // does not rewind to the head. + const next = records.findIndex((item) => item.cid > previous) + start = next >= 0 ? next : 0 + } + } + + if (start >= records.length) { + start = 0 + } const batch = [...records.slice(start), ...records.slice(0, start)].slice(0, size) diff --git a/src/storage/uploadSession.ts b/src/storage/uploadSession.ts index 69ca48c..924dea5 100644 --- a/src/storage/uploadSession.ts +++ b/src/storage/uploadSession.ts @@ -34,6 +34,11 @@ export function inFlightBlockCount(): number { return inFlightBlocks.size } +/** True when another in-flight upload still references this block. */ +export function isBlockInFlight(cid: string): boolean { + return inFlightBlocks.has(cid) +} + type PutOptions = Parameters[2] type GetOptions = Parameters[1] type HasOptions = Parameters[1] @@ -179,6 +184,13 @@ export class UploadSession { readonly budget: RequestSizeBudget readonly blockstore: RecordingBlockstore private settled = false + /** + * True after the route handler has taken the session over. + * + * A client disconnect must not delete blocks the handler is pinning. The + * handler's own `catch` still calls {@link cleanup} if the request fails. + */ + private claimed = false constructor(private readonly options: UploadSessionOptions) { this.budget = new RequestSizeBudget(options.maxRequestSizeBytes) @@ -193,6 +205,24 @@ export class UploadSession { return this.settled } + get isClaimed(): boolean { + return this.claimed + } + + /** + * Take ownership so a disconnect cannot race the pin/register path. + * + * @returns False when cleanup or commit already ran + */ + claim(): boolean { + if (this.settled) { + return false + } + + this.claimed = true + return true + } + /** * Start accounting for a single part. * @@ -214,6 +244,7 @@ export class UploadSession { } this.settled = true + this.claimed = false try { this.blockstore.releaseAll() } finally { @@ -221,6 +252,19 @@ export class UploadSession { } } + /** + * Disconnect-path cleanup. No-ops once the handler has claimed the session. + * + * The checks are synchronous so they cannot interleave with {@link claim}. + */ + async cleanupIfUnclaimed(): Promise { + if (this.settled || this.claimed) { + return 0 + } + + return this.cleanup() + } + /** * Remove the blocks this session created. * @@ -237,6 +281,7 @@ export class UploadSession { } this.settled = true + this.claimed = false try { const releasable = new Set(this.blockstore.releaseAll()) let removed = 0 @@ -253,6 +298,12 @@ export class UploadSession { continue } + // Re-check at delete time: a concurrent upload may have retained the + // block after `releaseAll` took the snapshot. + if (isBlockInFlight(key)) { + continue + } + await this.options.deleteBlock(cid) removed += 1 } catch (err) { diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index b27a8c0..86cb8eb 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -1,7 +1,7 @@ import { StorageEngine } from 'multer' import * as e from 'express' import { unixfs } from '@helia/unixfs' -import { Readable, Transform } from 'node:stream' +import { Readable, Transform, pipeline } from 'node:stream' import { logger } from './logger.js' import { UnixFsMulterFile } from './types.js' import { sanitizeFilename } from './sanitizeFilename.js' @@ -46,8 +46,14 @@ function budgeted(stream: Readable, budget: RequestSizeBudget): MeteredStream { } }) + pipeline(stream, meter, (err) => { + if (err !== null && err !== undefined && !meter.destroyed) { + meter.destroy(err) + } + }) + return { - stream: stream.pipe(meter), + stream: meter, get bytes() { return bytes } diff --git a/test/admission.test.ts b/test/admission.test.ts index 75e865a..8921a5e 100644 --- a/test/admission.test.ts +++ b/test/admission.test.ts @@ -47,17 +47,33 @@ after(async () => { }) describe('upload admission recovery', () => { - it('clears only tokens left by an earlier process', async () => { + it('clears only tokens whose upload request is no longer running', async () => { const registry = new FileRegistry(datastore, '/adm/admission-recovery') const currentId = createAdmissionId() + beginAdmission(currentId) await registry.save(record(CID_A, 'previous-process:request')) await registry.save(record(CID_B, currentId)) + try { + const report = await recoverInterruptedAdmissions(registry) + + assert.deepEqual(report, { checked: 1, recovered: 1, errors: [] }) + assert.equal((await registry.get(CID_A))?.admissionId, undefined) + assert.equal((await registry.get(CID_B))?.admissionId, currentId) + } finally { + endAdmission(currentId) + } + }) + + it('clears an unsettled token this process left after its handler returned', async () => { + const registry = new FileRegistry(datastore, '/adm/admission-recovery-stale') + const leftover = createAdmissionId() + await registry.save(record(CID_A, leftover)) + const report = await recoverInterruptedAdmissions(registry) assert.deepEqual(report, { checked: 1, recovered: 1, errors: [] }) assert.equal((await registry.get(CID_A))?.admissionId, undefined) - assert.equal((await registry.get(CID_B))?.admissionId, currentId) }) it('keeps a settled token from an earlier process for commit retry', async () => { diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts index 3a1d648..732764d 100644 --- a/test/garbageCollectionPlan.test.ts +++ b/test/garbageCollectionPlan.test.ts @@ -203,4 +203,22 @@ describe('planGarbageCollection', () => { assert.deepEqual(plan.evicted, []) assert.deepEqual(plan.retained, [staged]) }) + + it('does not evict a temporary file whose upload is still in flight', () => { + const uploading = record({ + cid: 'uploading', + state: 'temporary', + expiresAt: 9000, + admissionId: 'req-1' + }) + const plan = planGarbageCollection({ + blockstoreBytes: 5000, + watermarks, + records: [uploading], + now: 1000 + }) + + assert.deepEqual(plan.evicted, []) + assert.ok(plan.retained.some((item) => item.cid === 'uploading')) + }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index f0c2ff4..082d7bf 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1687,7 +1687,9 @@ describe('registry backfill', () => { // problem entirely. const failing = { get: (value: string) => registry.get(value), - save: () => Promise.reject(new Error('datastore is unavailable')) + save: () => Promise.reject(new Error('datastore is unavailable')), + hasPinIntent: async () => false, + clearPinIntent: async () => undefined } as unknown as FileRegistry const report = await backfillRegistryFromPins({ diff --git a/test/placement.test.ts b/test/placement.test.ts index 816e68d..b7e936d 100644 --- a/test/placement.test.ts +++ b/test/placement.test.ts @@ -141,6 +141,19 @@ describe('placeFile', () => { placement.selfIsHolder ? placement.copies - 1 : placement.copies ) }) + + it('counts a peer once even when the same id is listed twice', () => { + const placement = placeFile({ + cid: CID, + ageMs: 0, + tiers: TIERS, + selfPeerId: 'self', + peerIds: ['peer-a', 'peer-a', 'peer-b'] + }) + + assert.equal(new Set(placement.holders).size, placement.holders.length) + assert.equal(placement.holders.includes('peer-a'), true) + }) }) describe('mayDemote', () => { diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 9ff8bc0..4259b0a 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -93,6 +93,19 @@ describe('resolveReplicationConfig', () => { ) }) + it('rejects tiers that increase copies as a file ages', () => { + assert.throws( + () => + resolveReplicationConfig({ + placement: [ + { minAgeMs: 0, copies: 2 }, + { minAgeMs: 1000, copies: 4 } + ] + }), + /placement\[1\].copies/ + ) + }) + it('rejects tiers that go backwards', () => { assert.throws( () => diff --git a/test/sweepBatch.test.ts b/test/sweepBatch.test.ts index 752d791..4aa417a 100644 --- a/test/sweepBatch.test.ts +++ b/test/sweepBatch.test.ts @@ -63,4 +63,17 @@ describe('nextSweepBatch', () => { assert.equal(nextSweepBatch('rescue', replaced).length, SWEEP_BATCHES.rescue) }) + + it('does not rewind when the last batch CID left the candidate set', () => { + const size = SWEEP_BATCHES.demote + const all = Array.from({ length: size + 8 }, (unused, index) => ({ + cid: `z-${String(index).padStart(3, '0')}` + })) + const first = nextSweepBatch('demote', all) + const cursor = first[first.length - 1].cid + const remaining = all.filter((record) => record.cid !== cursor) + const second = nextSweepBatch('demote', remaining) + + assert.ok(second[0].cid > cursor) + }) }) diff --git a/test/uploadSession.test.ts b/test/uploadSession.test.ts new file mode 100644 index 0000000..b431827 --- /dev/null +++ b/test/uploadSession.test.ts @@ -0,0 +1,95 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { CID } from 'multiformats/cid' +import { UploadSession } from '../src/storage/uploadSession.js' + +const CID_A = CID.parse('bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku') + +function memoryStore(): { + blockstore: { + has: (cid: CID) => Promise + put: (cid: CID, block: Uint8Array) => Promise + get: (cid: CID) => AsyncGenerator + } + deleted: string[] +} { + const blocks = new Map() + const deleted: string[] = [] + + return { + deleted, + blockstore: { + async has(cid) { + return blocks.has(cid.toString()) + }, + async put(cid, block) { + blocks.set(cid.toString(), block) + return cid + }, + async *get(cid) { + const block = blocks.get(cid.toString()) + if (block !== undefined) { + yield block + } + } + } + } +} + +describe('UploadSession claim', () => { + it('lets the handler clean up a claimed session, but not a disconnect', async () => { + const { blockstore } = memoryStore() + const session = new UploadSession({ + blockstore: blockstore as never, + isPinned: async () => false, + deleteBlock: async () => undefined, + maxRequestSizeBytes: 1024, + parseCid: (value) => CID.parse(value) + }) + + assert.equal(session.claim(), true) + assert.equal(await session.cleanupIfUnclaimed(), 0) + assert.equal(session.isSettled, false) + assert.equal(await session.cleanup(), 0) + assert.equal(session.isSettled, true) + assert.equal(session.claim(), false) + }) +}) + +describe('UploadSession cleanup', () => { + it('does not delete a block another upload retained after the snapshot', async () => { + const { blockstore, deleted } = memoryStore() + let releasePinned: () => void = () => undefined + const pinnedGate = new Promise((resolve) => { + releasePinned = resolve + }) + + const sessionA = new UploadSession({ + blockstore: blockstore as never, + isPinned: async () => { + await pinnedGate + return false + }, + deleteBlock: async (cid) => { + deleted.push(cid.toString()) + }, + maxRequestSizeBytes: 1024, + parseCid: (value) => CID.parse(value) + }) + const sessionB = new UploadSession({ + blockstore: blockstore as never, + isPinned: async () => false, + deleteBlock: async () => undefined, + maxRequestSizeBytes: 1024, + parseCid: (value) => CID.parse(value) + }) + + await sessionA.blockstore.put(CID_A, new Uint8Array([1, 2, 3])) + const cleanup = sessionA.cleanup() + await sessionB.blockstore.put(CID_A, new Uint8Array([1, 2, 3])) + releasePinned() + + assert.equal(await cleanup, 0) + assert.deepEqual(deleted, []) + }) +}) From 28833b6a00abb87718f8233deb841a2ae9a59f46 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Wed, 26 Aug 2026 20:47:23 +0300 Subject: [PATCH 133/167] fix(storage): implement repair lifecycle management and enhance file registry handling --- src/storage/registry.ts | 16 +++++-- src/storage/repairClaim.ts | 33 +++++++++++++ src/storage/repairState.ts | 29 ++++++++++++ src/storage/service.ts | 57 ++++++++++++++--------- test/fileRegistry.test.ts | 52 +++++++++++++++++++++ test/integration/storageLifecycle.test.ts | 54 ++++++++++++++++++++- 6 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 src/storage/repairClaim.ts create mode 100644 src/storage/repairState.ts diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 3d8cf98..548ecc8 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -3,6 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks' import { Key } from 'interface-datastore' import type { Datastore, Pair } from 'interface-datastore' import { isActiveAdmission } from './admissionState.js' +import { isActiveRepair } from './repairState.js' /** * Datastore namespace of the lifecycle registry. @@ -606,6 +607,10 @@ export class FileRegistry { const now = options.now ?? Date.now() const existing = await this.get(file.cid) + if (isActiveRepair(file.cid)) { + throw new FileLifecycleBusyError(file.cid) + } + // A permanent store or repair must not confirm a copy a strict upload can // still abort. A later local upload passes `admissionId` and takes over. if (existing?.replicaStage !== undefined && options.admissionId === undefined) { @@ -719,7 +724,8 @@ export function isSettledHeldFile(record: FileRecord): boolean { record.state === 'confirmed' && record.heldLocally && isAdmissionSettled(record) && - !isActiveAdmission(record.admissionId) + !isActiveAdmission(record.admissionId) && + !isActiveRepair(record.cid) ) } @@ -737,15 +743,17 @@ export function isAdmissionInFlight(record: FileRecord): boolean { } /** - * True while a replica stage, an unsettled upload, or an in-flight request still - * owns this CID. + * True while a replica stage, upload request, or replication repair still owns + * this CID. * * `admissionSettledAt` is not enough: the HTTP handler writes it before remote * commit. A token still listed as active belongs to that request. A leftover * after the handler returns is not busy — repair uses it to retry `commit`. */ export function isLifecycleBusy(record: FileRecord): boolean { - return record.replicaStage !== undefined || isAdmissionInFlight(record) + return ( + record.replicaStage !== undefined || isAdmissionInFlight(record) || isActiveRepair(record.cid) + ) } export function countByState(records: FileRecord[]): Record { diff --git a/src/storage/repairClaim.ts b/src/storage/repairClaim.ts new file mode 100644 index 0000000..606f008 --- /dev/null +++ b/src/storage/repairClaim.ts @@ -0,0 +1,33 @@ +import { isSettledHeldFile, type FileRecord, type FileRegistry } from './registry.js' +import { beginRepair, endRepair } from './repairState.js' + +/** + * Claim the exact registry revision selected for replication repair. + * + * Candidate selection happens before network probes. Taking the claim under + * the CID lock prevents a newer upload or release from replacing that + * lifecycle while the repair operates without holding the datastore lock. + */ +export async function claimRepairRecord( + registry: FileRegistry, + selected: FileRecord +): Promise { + return registry.withExclusiveCids([selected.cid], async (locked) => { + const current = await locked.get(selected.cid) + + if ( + current === undefined || + current.revision !== selected.revision || + !isSettledHeldFile(current) + ) { + return false + } + + return beginRepair(selected.cid) + }) +} + +/** Release a claim previously returned by {@link claimRepairRecord}. */ +export function releaseRepairRecord(cid: string): void { + endRepair(cid) +} diff --git a/src/storage/repairState.ts b/src/storage/repairState.ts new file mode 100644 index 0000000..4187f43 --- /dev/null +++ b/src/storage/repairState.ts @@ -0,0 +1,29 @@ +/** + * CIDs whose replication repair is currently doing network work. + * + * A repair cannot hold the registry lock across peer requests, but another + * lifecycle must not adopt or release the CID between candidate selection and + * placement. The marker is process-local because an interrupted repair has no + * local state to recover after restart. + */ +const activeRepairs = new Set() + +/** Claim a CID for one repair pass. */ +export function beginRepair(cid: string): boolean { + if (activeRepairs.has(cid)) { + return false + } + + activeRepairs.add(cid) + return true +} + +/** Release repair ownership after success or failure. */ +export function endRepair(cid: string): void { + activeRepairs.delete(cid) +} + +/** Whether this process is currently repairing the CID. */ +export function isActiveRepair(cid: string): boolean { + return activeRepairs.has(cid) +} diff --git a/src/storage/service.ts b/src/storage/service.ts index bba235a..3e2847d 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -17,6 +17,7 @@ import { import { FileLifecycleBusyError, isAdmissionSettled, + isLifecycleBusy, isSettledHeldFile, protectedStorageBytes, type FileRecord @@ -54,6 +55,7 @@ import { import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter, storageOperationLock } from './state.js' import { nextSweepBatch } from './sweep.js' +import { claimRepairRecord, releaseRepairRecord } from './repairClaim.js' const callOptions = (): ReplicationCallOptions => ({ timeoutMs: config.replication.requestTimeoutMs @@ -891,33 +893,41 @@ export async function repairReplication(): Promise { const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) for (const record of candidates) { - const placement = placementFor(record.cid, record.createdAt, peers) + if (!(await claimRepairRecord(fileRegistry, record))) { + continue + } - // `replicas` is a record of what peers once said. A holder that lost its - // blockstore, restarted empty, or left the configuration never changes that - // number, and the file would be considered healthy forever. Ask instead. - const holders = storageTargets(placement, self) - .map((peerId) => byPeerId.get(peerId)) - .filter((peer): peer is ReplicationPeer => peer !== undefined) + try { + const placement = placementFor(record.cid, record.createdAt, peers) - const live = await liveHolderNames(holders, record.cid) - await fileRegistry.setReplicas(record.cid, live) + // `replicas` is a record of what peers once said. A holder that lost its + // blockstore, restarted empty, or left the configuration never changes that + // number, and the file would be considered healthy forever. Ask instead. + const holders = storageTargets(placement, self) + .map((peerId) => byPeerId.get(peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) - if (!isUnderReplicated(live.length, placement, self)) { - await clearSettledAdmission(fileRegistry, record) - continue - } + const live = await liveHolderNames(holders, record.cid) + await fileRegistry.setReplicas(record.cid, live) + + if (!isUnderReplicated(live.length, placement, self)) { + await clearSettledAdmission(fileRegistry, record) + continue + } - report.underReplicated += 1 - await replicateFile(record.cid) - const verified = await liveHolderNames(holders, record.cid) - await fileRegistry.setReplicas(record.cid, verified) + report.underReplicated += 1 + await replicateFile(record.cid) + const verified = await liveHolderNames(holders, record.cid) + await fileRegistry.setReplicas(record.cid, verified) - if (isUnderReplicated(verified.length, placement, self)) { - report.stillMissing.push(record.cid) - } else { - report.repaired.push(record.cid) - await clearSettledAdmission(fileRegistry, record) + if (isUnderReplicated(verified.length, placement, self)) { + report.stillMissing.push(record.cid) + } else { + report.repaired.push(record.cid) + await clearSettledAdmission(fileRegistry, record) + } + } finally { + releaseRepairRecord(record.cid) } } @@ -1013,7 +1023,8 @@ export async function demoteReleasableCopies( current === undefined || current.revision !== record.revision || current.state !== 'confirmed' || - !current.heldLocally + !current.heldLocally || + isLifecycleBusy(current) ) { return false } diff --git a/test/fileRegistry.test.ts b/test/fileRegistry.test.ts index 2f5de4d..3df9c8a 100644 --- a/test/fileRegistry.test.ts +++ b/test/fileRegistry.test.ts @@ -14,6 +14,7 @@ import { type FileRecord } from '../src/storage/registry.js' import { beginAdmission, endAdmission } from '../src/storage/admission.js' +import { claimRepairRecord, releaseRepairRecord } from '../src/storage/repairClaim.js' const CID_A = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku' const CID_B = 'bafkreihfzyt2g6pbd4rhk67deek7xh33xams74spn72eqq5qhx2ypphvii' @@ -435,6 +436,57 @@ describe('FileRegistry', () => { assert.equal(refreshed?.admissionId, 'upload-one') }) + it('keeps a selected repair lifecycle exclusive until the repair releases it', async () => { + const registry = createRegistry() + const selected = ( + await registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(newFile(), { + confirmationRequired: false, + temporaryTtlMs: TTL + }) + ) + ).record + + assert.equal(await claimRepairRecord(registry, selected), true) + + try { + await assert.rejects( + () => + registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL, + admissionId: 'later-upload' + }) + ), + /active lifecycle transaction/ + ) + assert.equal(isLifecycleBusy(selected), true) + assert.equal(isSettledHeldFile(selected), false) + } finally { + releaseRepairRecord(CID_A) + } + + const adopted = await registry.withExclusiveCids([CID_A], (locked) => + locked.registerReplacing(newFile(), { + confirmationRequired: true, + temporaryTtlMs: TTL, + admissionId: 'later-upload' + }) + ) + + assert.equal(adopted.record.admissionId, 'later-upload') + }) + + it('does not claim a repair candidate after its registry revision changed', async () => { + const registry = createRegistry() + const selected = (await registerFile(registry)).record + await registry.setReplicas(CID_A, ['n2']) + + assert.equal(await claimRepairRecord(registry, selected), false) + assert.equal(isLifecycleBusy((await registry.get(CID_A)) as FileRecord), false) + }) + it('does not confirm a live replica stage through a permanent store', async () => { const registry = createRegistry() const staged = await registry.save({ diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 082d7bf..b176500 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1633,6 +1633,52 @@ describe('strict-upload replica staging', () => { }) describe('registry backfill', () => { + it('restores an interrupted pin as temporary after the registry reopens', async () => { + const cid = await ifs.addBytes(deterministicBytes(2048, 'interrupted-pin-intent')) + const restartDir = await mkdtemp(join(tmpdir(), 'ipfs-node-pin-intent-')) + const datastorePath = join(restartDir, 'datastore') + const prefix = '/adm/restart-pin-intent' + const ttl = 60_000 + const initialStore = new FsDatastore(datastorePath) + + try { + await initialStore.open() + const initialRegistry = new FileRegistry(initialStore, prefix) + await initialRegistry.markPinIntent(cid.toString()) + await pinFile(node, cid) + await initialStore.close() + + const restartedStore = new FsDatastore(datastorePath) + + try { + await restartedStore.open() + const restartedRegistry = new FileRegistry(restartedStore, prefix) + const before = Date.now() + const report = await backfillRegistryFromPins({ + cids: [cid.toString()], + unixfs: ifs, + registry: restartedRegistry, + confirmationRequired: true, + temporaryTtlMs: ttl + }) + const after = Date.now() + const restored = await restartedRegistry.get(cid.toString()) + + assert.equal(report.registered, 1) + assert.equal(restored?.state, 'temporary') + assert.equal(restored?.confirmedAt, null) + assert.ok((restored?.expiresAt ?? 0) >= before + ttl) + assert.ok((restored?.expiresAt ?? 0) <= after + ttl) + assert.equal(await restartedRegistry.hasPinIntent(cid.toString()), false) + } finally { + await restartedStore.close() + } + } finally { + await unpinFile(node, cid) + await rm(restartDir, { recursive: true, force: true }) + } + }) + it('records a pin that predates the registry, and does so only once', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'legacy-pin')) @@ -1707,10 +1753,14 @@ describe('registry backfill', () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'already-known')) await pinFile(node, cid) - await registry.save(record({ cid: cid.toString(), state: 'confirmed', name: 'mine.bin' })) + const existing = await registry.save( + record({ cid: cid.toString(), state: 'confirmed', name: 'mine.bin' }) + ) + await registry.markPinIntent(cid.toString()) await backfillRegistryFromPins({ cids: await snapshotPins(node), unixfs: ifs, registry }) - assert.equal((await registry.get(cid.toString()))?.name, 'mine.bin') + assert.deepEqual(await registry.get(cid.toString()), existing) + assert.equal(await registry.hasPinIntent(cid.toString()), false) }) }) From d4724f6b211fb458df1c3502a30472b1242f80f1 Mon Sep 17 00:00:00 2001 From: metalisk Date: Wed, 26 Aug 2026 21:38:59 +0300 Subject: [PATCH 134/167] fix(storage): enhance upload and replication handling with peer origin tracking - Introduced origin tracking for replica transactions, ensuring that commit and abort operations are validated against the originating peer. - Updated the replication protocol to include structured error responses, allowing clients to distinguish between different failure reasons. - Enhanced the upload handler to utilize public replication reports, stripping sensitive peer information from responses. - Improved lifecycle management by refining admission recovery processes and ensuring that temporary pins are correctly managed. - Updated tests to validate the new origin tracking and error handling mechanisms, ensuring robustness in upload and replication operations. --- README.md | 31 +++-- docs/storage-lifecycle.md | 14 +- src/api/file.ts | 1 - src/api/uploadRoute.ts | 39 +++--- src/gc.cron.ts | 28 +++- src/index.ts | 30 ++++- src/storage/collection.ts | 3 - src/storage/gc.ts | 14 +- src/storage/lifecycle.ts | 6 +- src/storage/pinning.ts | 36 ++++- src/storage/registry.ts | 35 ++++- src/storage/replicaStage.ts | 75 +++++++++-- src/storage/replication.ts | 41 ++++++ src/storage/replicationProtocol.ts | 157 +++++++++++++++------- src/storage/service.ts | 68 ++++++++-- src/storage/sweep.ts | 28 ++-- src/utils/unixfs-multer.storage.ts | 55 +++++--- test/garbageCollectionPlan.test.ts | 18 +++ test/integration/storageLifecycle.test.ts | 54 +++++++- test/replication.test.ts | 35 +++++ test/sweepBatch.test.ts | 14 ++ 21 files changed, 613 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index d1c90de..3df9110 100644 --- a/README.md +++ b/README.md @@ -116,15 +116,15 @@ Invalid CORS, proxy, API-key, or rate-limit configuration stops the process inst ## HTTP access policy -| Class | Routes | Policy | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | -| Public | `GET /`, `GET /api/node/health` | No authentication | -| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | -| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | -| Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | -| Peer replication | libp2p `/adamant/replication/1.0.0`, not an HTTP route | Authenticated by the libp2p handshake; accepted only from the peers listed in `nodes` | -| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | -| Authenticated user | None | The service has no end-user identity or session layer | +| Class | Routes | Policy | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Public | `GET /`, `GET /api/node/health` | No authentication | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | +| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | +| Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | +| Peer replication | libp2p `/adamant/replication/1.0.0`, not an HTTP route | Authenticated by the libp2p handshake. Pin, store, stage, commit, and abort are accepted only from the peers listed in `nodes`. `cache` is open to any peer (same effect as a public read), bounded by disk reserve and intake budget | +| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | +| Authenticated user | None | The service has no end-user identity or session layer | Administrative coverage includes pin operations, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. @@ -260,19 +260,24 @@ Example response: } ], "replication": { - "mode": "best-effort", - "desiredCopies": 1, - "copies": 1, + "mode": "quorum", + "desiredCopies": 4, + "copies": 3, "required": 1, "acknowledged": 1, - "replicas": [], + "replicaCount": 0, + "cachedCount": 0, "satisfied": true, "networkTooSmall": true, + "failedAttemptCount": 0, "attempts": [] } } ``` +The `replication` object on this public route is counts and per-attempt +outcomes only: no node names, peer ids, or peer error text. + Upload responses use the following stable status contract: | Status | Meaning | diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 8d23db6..5d97201 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -353,9 +353,11 @@ hold the blocks without pinning them. Because such a copy lasts only until its node needs the space, the file is spread to a few more nodes than a pinned one would be: several fragile copies outlive a single one. It can serve the file from then on, and it promises nothing — the copy sits in the same tier as read cache and goes when -that node needs the space. Nothing is counted as durable that is not: an upload -report lists such peers under `cached`, separately from `replicas`, and the -acknowledged count ignores them. +that node needs the space. Nothing is counted as durable that is not. The +internal placement report still lists those peers under `cached`, separately +from `replicas`, and the acknowledged count ignores them. The public upload +receipt keeps only counts (`replicaCount`, `cachedCount`, `acknowledged`) — not +node names, peer ids, or peer error text. This grants a stranger nothing it did not already have. Anyone who knows a CID can already make a node fetch and cache those blocks by asking it to serve them; @@ -586,7 +588,9 @@ The same startup phase clears admission tokens left by a request that can no longer resume. Tokens whose HTTP handler is still running in this process are skipped; unsettled tokens from a previous process, or from a handler in this process that has already returned, are removed so they cannot hide a pin from -repair. +repair. The same pass runs at the start of every real collection, and on the +same schedule even when `storage.gc.enabled` is false. A dry-run collection +does not clear tokens. Two consequences are worth planning for: @@ -608,7 +612,7 @@ Two consequences are worth planning for: a dry run. It does ask peers whether they hold the files it would hand over, because a handover unpins a local copy and a plan without them is not the plan the real run follows. The sweep position is left where it was, so the - real run starts on the same files. + real run starts on the same files. Leftover admission tokens are not cleared. 2. Confirm that every CID that must survive appears in `retainedCids`. If a CID is missing, it is not confirmed: pin it with `POST /api/helia/pin/:cid` or confirm it with `POST /api/file/:cid/confirm` first. diff --git a/src/api/file.ts b/src/api/file.ts index 3b69915..a297d12 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -52,7 +52,6 @@ router.post( replicate: replicateUploadedFile, commitReplicas: commitUploadedReplicas, abortReplicas: abortUploadedReplicas, - pinTimeoutMs: config.findFileTimeout, log: logger }) ) diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index 5e59fe3..cccb35c 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -3,6 +3,7 @@ import type { IpfsNode } from '../ipfs-node.js' import { pinFile, unpinFile } from '../storage/pinning.js' import type { FileRecord, FileRegistry } from '../storage/registry.js' import type { ReplicationReport } from '../storage/replication.js' +import { toPublicReplicationReport } from '../storage/replication.js' import { rollbackUpload } from '../storage/rollback.js' import type { UploadSession } from '../storage/uploadSession.js' import { createAdmissionId, beginAdmission, endAdmission } from '../storage/admission.js' @@ -37,11 +38,6 @@ export interface UploadRouteDependencies { abortReplicas: (cid: string, transactionId: string, report: ReplicationReport) => Promise /** Test seam for pin-datastore failure paths. */ unpin?: (cid: UnixFsMulterFile['cid']) => Promise - /** - * Bounds the pin DAG walk. Uploaded blocks are already local; the deadline - * is a backstop if disconnect cleanup won the race and the walk would fetch. - */ - pinTimeoutMs?: number log: UploadRouteLog } @@ -250,6 +246,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ return async (req: Request, res: Response, next: NextFunction) => { const admissionId = createAdmissionId() beginAdmission(admissionId) + let session: UploadSession | undefined try { if (!Array.isArray(req.files) || req.files.length === 0) { @@ -257,8 +254,9 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ return res.send({ error: 'No file uploaded' }) } - const session = dependencies.getSession(req) - if (!session.claim()) { + session = dependencies.getSession(req) + const claimedSession = session + if (!claimedSession.claim()) { throw new Error('Upload was aborted before it could be committed') } @@ -313,15 +311,11 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } const previous = await locked.get(cid) await locked.markPinIntent(cid) - const pinSignal = - dependencies.pinTimeoutMs === undefined - ? undefined - : AbortSignal.timeout(dependencies.pinTimeoutMs) let createdPin = false let registration try { - createdPin = await pinFile(node, file.cid, pinSignal) + createdPin = await pinFile(node, file.cid) registration = await locked.registerReplacing( { cid, @@ -345,11 +339,10 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ throw err } - await locked.clearPinIntent(cid) - if (!baselines.has(cid)) { baselines.set(cid, { cid, previous, createdPin, unpin }) } + await locked.clearPinIntent(cid) stored.push(registration.record) } @@ -359,7 +352,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ // session holds the shared storage lease, and keeping it across a // network round would stop collection — and, behind it, every other // upload — for as long as the slowest peer takes to answer. - session.commit() + claimedSession.commit() return { records: stored, baselines: [...baselines.values()] } } catch (err) { @@ -374,7 +367,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ ) } - await session.cleanup() + await claimedSession.cleanup() throw failure } } @@ -555,7 +548,9 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } const records = outcome.records.map((record) => finalized.get(record.cid) ?? record) - const replicationByCid = new Map(replication.map((item) => [item.cid, item.report])) + const replicationByCid = new Map( + replication.map((item) => [item.cid, toPublicReplicationReport(item.report)]) + ) res.send({ filesNames: files.map((file) => file.originalname), @@ -571,12 +566,20 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ })), // The first file's report, kept for clients written against it. New // ones should read the per-file field above. - replication: replication[0]?.report ?? null + replication: + replication[0] !== undefined ? toPublicReplicationReport(replication[0].report) : null }) } catch (err) { next(err) } finally { endAdmission(admissionId) + if (session !== undefined && !session.isSettled) { + try { + await session.cleanup() + } catch (cleanupErr) { + log.error(`Upload cleanup failed: ${(cleanupErr as Error).message}`) + } + } } } } diff --git a/src/gc.cron.ts b/src/gc.cron.ts index a298a08..55eb2c9 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -36,9 +36,9 @@ export async function collectGarbage( running = true try { - const recovery = await recoverInterruptedAdmissions(fileRegistry) - for (const error of recovery.errors) { - logger.warn(`Admission recovery: ${error}`) + // Dry-run is a report. Clearing leftover tokens would still be a write. + if (options.dryRun !== true) { + await recoverStaleAdmissions() } const collection = await collectStorage({ @@ -47,7 +47,6 @@ export async function collectGarbage( registry: fileRegistry, watermarks: config.storage.gc, reserveBytes: config.storage.diskReserveBytes, - pinTimeoutMs: config.replication.requestTimeoutMs, demote: demoteReleasableCopies, measure: refreshStorageMetrics, dryRun: options.dryRun, @@ -75,6 +74,27 @@ export const garbageCollectionCron = new CronJob(config.storage.gc.schedule, () collectGarbage().catch((err) => logger.error(`${err.message}\n${err.stack}`)) }) +/** + * Clear leftover upload tokens when the collector itself is off. + * + * Recovery also runs inside {@link collectGarbage}. This job exists so an + * operator who disables the collector still un-wedges CIDs whose handler died. + */ +export const admissionRecoveryCron = new CronJob(config.storage.gc.schedule, () => { + logger.info('[Cron] Running "admissionRecovery" cronjob.') + recoverStaleAdmissions().catch((err) => logger.error(`${err.message}\n${err.stack}`)) +}) + +async function recoverStaleAdmissions(): Promise { + const recovery = await recoverInterruptedAdmissions(fileRegistry) + for (const error of recovery.errors) { + logger.warn(`Admission recovery: ${error}`) + } + if (recovery.recovered > 0) { + logger.info(`Admission recovery: cleared ${recovery.recovered} interrupted uploads`) + } +} + export function getGarbageCollectionState() { return { enabled: config.storage.gc.enabled, diff --git a/src/index.ts b/src/index.ts index e747803..1a51d14 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ import { recoverInterruptedAdmissions } from './storage/admission.js' import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' import { fileRegistry } from './storage/state.js' -import { garbageCollectionCron } from './gc.cron.js' +import { garbageCollectionCron, admissionRecoveryCron } from './gc.cron.js' import { replicationRepairCron } from './replication.cron.js' import cors from 'cors' import * as routers from './api/index.js' @@ -57,6 +57,7 @@ function startLifecycleJobs(): void { garbageCollectionCron.start() } else { logger.info('Garbage collection is disabled. Storage grows until an operator collects it.') + admissionRecoveryCron.start() } if (config.replication.enabled) { @@ -148,14 +149,39 @@ const server = app.listen(PORT, () => { ) }) +const SHUTDOWN_MS = 15_000 + function shutdown(signal: string): void { logger.info(`Received ${signal}, shutting down`) diskUsageCron.stop() peeringCron.stop() garbageCollectionCron.stop() + admissionRecoveryCron.stop() replicationRepairCron.stop() + + const forceTimer = setTimeout(() => { + logger.warn('Shutdown timed out') + process.exit(1) + }, SHUTDOWN_MS) + forceTimer.unref() + + const closeIdle = setTimeout( + () => { + server.closeAllConnections() + }, + Math.max(1_000, SHUTDOWN_MS - 3_000) + ) + closeIdle.unref() + server.close(() => { - void helia.stop().finally(() => process.exit(0)) + clearTimeout(closeIdle) + void helia + .stop() + .catch((err: Error) => logger.error(`helia.stop failed: ${err.message}`)) + .finally(() => { + clearTimeout(forceTimer) + process.exit(0) + }) }) } diff --git a/src/storage/collection.ts b/src/storage/collection.ts index 528fc9d..8175191 100644 --- a/src/storage/collection.ts +++ b/src/storage/collection.ts @@ -16,8 +16,6 @@ export interface CollectStorageOptions { watermarks: Watermarks /** Free bytes uploads must never consume. */ reserveBytes: number - /** Bounds the DAG walk when a missing pin has to be restored. */ - pinTimeoutMs?: number /** Hands copies over to their designated holders, and reports which. */ demote: (options: { dryRun?: boolean }) => Promise<{ demoted: string[] }> /** Measures the blockstore and the volume it sits on. */ @@ -63,7 +61,6 @@ export async function collectStorage(options: CollectStorageOptions): Promise options.lock.withExclusive(work), log: options.log }) diff --git a/src/storage/gc.ts b/src/storage/gc.ts index 05ca21f..d4acef2 100644 --- a/src/storage/gc.ts +++ b/src/storage/gc.ts @@ -1,7 +1,7 @@ import { CID } from 'multiformats/cid' import type { IpfsNode } from '../ipfs-node.js' import type { GarbageCollectionConfig } from './config.js' -import { isProtected, pinFile, unpinFile } from './pinning.js' +import { hasLocalDag, isProtected, pinFile, unpinFile } from './pinning.js' import { restoreReplicaStage } from './replicaStage.js' import { FileRegistry, @@ -216,8 +216,6 @@ export interface GcRunOptions { dryRun?: boolean /** Collect even when the blockstore is below the high watermark. */ force?: boolean - /** Bounds the DAG walk when a missing pin has to be restored. */ - pinTimeoutMs?: number /** * Excludes in-flight unpinned writes while Helia deletes blocks. * @@ -310,9 +308,13 @@ export async function runGarbageCollection(options: GcRunOptions): Promise { const key = options.cid.toString() diff --git a/src/storage/pinning.ts b/src/storage/pinning.ts index 82c7e14..6fc36ca 100644 --- a/src/storage/pinning.ts +++ b/src/storage/pinning.ts @@ -1,3 +1,4 @@ +import { unixfs } from '@helia/unixfs' import { CID } from 'multiformats/cid' import type { IpfsNode } from '../ipfs-node.js' @@ -14,13 +15,24 @@ async function drain(source: AsyncIterable): Promise { * Idempotent: pinning content that already carries a direct pin is reported as * `false` instead of failing, because Helia rejects a duplicate pin. * - * @param signal Bounds the DAG walk, which fetches missing blocks from peers + * `signal` is honoured only as a pre-check. Helia `pins.add` writes per-block + * pin metadata during the DAG walk and the direct pin key only after it + * finishes. Aborting the walk leaves `/pinned-block/` refs that `pins.rm` + * cannot clear (no `/pin/` key yet) and that `isPinned` still treats as + * protected. Fetch missing blocks under a timeout *before* calling this, or + * skip the pin when {@link hasLocalDag} is false. + * * @returns True when this call created the pin */ export async function pinFile(node: IpfsNode, cid: CID, signal?: AbortSignal): Promise { + if (signal?.aborted === true) { + throw new Error('Pin was aborted') + } + try { - // Draining the generator is what performs the DAG walk. - await drain(node.pins.add(cid, { signal })) + // Draining the generator is what performs the DAG walk. The abort signal is + // deliberately not forwarded: see the JSDoc above. + await drain(node.pins.add(cid)) return true } catch (err) { if ((err as Error).name === 'AlreadyPinnedError') { @@ -30,6 +42,24 @@ export async function pinFile(node: IpfsNode, cid: CID, signal?: AbortSignal): P } } +/** + * True when every block of the DAG is already in the local blockstore. + * + * Used to decide whether {@link pinFile} can run without Helia fetching over + * the network. An offline `stat` walks the DAG; a missing block fails it. + */ +export async function hasLocalDag(node: IpfsNode, cid: CID): Promise { + try { + await unixfs({ blockstore: node.blockstore }).stat(cid, { + extended: true, + offline: true + }) + return true + } catch { + return false + } +} + /** * Remove the direct pin of a DAG, making it reclaimable. * diff --git a/src/storage/registry.ts b/src/storage/registry.ts index 548ecc8..4c61527 100644 --- a/src/storage/registry.ts +++ b/src/storage/registry.ts @@ -30,6 +30,13 @@ export interface ReplicaStageBaseline { export interface ReplicaStage { /** Upload transactions that currently need this prepared copy. */ transactionIds: string[] + /** + * Peer that staged each transaction id. + * + * Commit and abort must come from that peer. Absent on records written + * before this field existed, which any authorized mesh peer may still settle. + */ + origins?: Record /** `null` when the stage created the registry entry. */ previous: ReplicaStageBaseline | null } @@ -126,13 +133,27 @@ function isReplicaStage(value: unknown): value is ReplicaStage { previous.pinned === false && previous.heldLocally === false) - return ( - Array.isArray(stage.transactionIds) && - stage.transactionIds.length > 0 && - new Set(stage.transactionIds).size === stage.transactionIds.length && - stage.transactionIds.every((id) => typeof id === 'string' && id.length > 0) && - previousIsValid - ) + const transactionIds = stage.transactionIds + if ( + !Array.isArray(transactionIds) || + transactionIds.length === 0 || + new Set(transactionIds).size !== transactionIds.length || + !transactionIds.every((id) => typeof id === 'string' && id.length > 0) + ) { + return false + } + + const originsAreValid = + stage.origins === undefined || + (typeof stage.origins === 'object' && + stage.origins !== null && + !Array.isArray(stage.origins) && + Object.entries(stage.origins).every( + ([id, peerId]) => + transactionIds.includes(id) && typeof peerId === 'string' && peerId.length > 0 + )) + + return originsAreValid && previousIsValid } /** Validate the durable shape before lifecycle policy is allowed to trust it. */ diff --git a/src/storage/replicaStage.ts b/src/storage/replicaStage.ts index 39d41ee..92f0045 100644 --- a/src/storage/replicaStage.ts +++ b/src/storage/replicaStage.ts @@ -53,6 +53,30 @@ function wasAborted(cid: string, transactionId: string, now: number): boolean { return (abortedTransactions.get(transactionKey(cid, transactionId)) ?? 0) > now } +function withOrigin( + previous: Record | undefined, + transactionId: string, + originPeerId: string | undefined +): Record | undefined { + if (originPeerId === undefined) { + return previous + } + + return { ...previous, [transactionId]: originPeerId } +} + +function assertStageOwner( + transactionId: string, + origin: string | undefined, + peerId: string | undefined +): void { + if (origin === undefined || peerId === undefined || origin === peerId) { + return + } + + throw new Error(`Replica transaction ${transactionId} was staged by another peer`) +} + export interface ReplicaStageTarget { node: IpfsNode unixfs: UnixFS @@ -62,6 +86,8 @@ export interface ReplicaStageTarget { temporaryTtlMs: number pinTimeoutMs?: number now?: number + /** libp2p peer that asked to stage; commit and abort must come from it. */ + originPeerId?: string } export interface ReplicaStageResult { @@ -175,7 +201,7 @@ export async function stageReplica(options: ReplicaStageTarget): Promise { const key = options.cid.toString() const now = options.now ?? Date.now() - rememberAbort( - key, - options.transactionId, - now + (options.tombstoneTtlMs ?? DEFAULT_ABORT_TOMBSTONE_TTL_MS), - now - ) + const tombstoneUntil = now + (options.tombstoneTtlMs ?? DEFAULT_ABORT_TOMBSTONE_TTL_MS) return options.registry.withExclusiveCids([key], async (registry) => { const current = await registry.get(key) @@ -391,13 +432,23 @@ export async function abortReplica(options: SettleReplicaOptions): Promise id !== options.transactionId) + const origins = { ...stage.origins } + delete origins[options.transactionId] + const nextOrigins = Object.keys(origins).length > 0 ? origins : undefined if (transactionIds.length > 0) { - return registry.save({ ...current, replicaStage: { ...stage, transactionIds } }) + return registry.save({ + ...current, + replicaStage: { ...stage, transactionIds, origins: nextOrigins } + }) } return restoreReplicaStage(registry, options.node, current, false) diff --git a/src/storage/replication.ts b/src/storage/replication.ts index 8ff35d7..4cd3478 100644 --- a/src/storage/replication.ts +++ b/src/storage/replication.ts @@ -81,6 +81,47 @@ export interface ReplicationReport { attempts: ReplicationAck[] } +/** + * Upload receipt without peer identity. + * + * The internal report names holders and carries raw peer errors. Upload is + * public, so the HTTP body keeps counts and outcomes only. + */ +export interface PublicReplicationReport { + mode: ReplicationReport['mode'] + desiredCopies: number + copies: number + required: number + acknowledged: number + replicaCount: number + cachedCount: number + satisfied: boolean + networkTooSmall: boolean + failedAttemptCount: number + attempts: Array<{ ok: boolean; outcome?: PlacementOutcome; staged?: boolean }> +} + +/** Strip holder names, peer ids, and error text from an upload receipt. */ +export function toPublicReplicationReport(report: ReplicationReport): PublicReplicationReport { + return { + mode: report.mode, + desiredCopies: report.desiredCopies, + copies: report.copies, + required: report.required, + acknowledged: report.acknowledged, + replicaCount: report.replicas.length, + cachedCount: report.cached.length, + satisfied: report.satisfied, + networkTooSmall: report.networkTooSmall, + failedAttemptCount: report.attempts.filter((attempt) => !attempt.ok).length, + attempts: report.attempts.map(({ ok, outcome, staged }) => ({ + ok, + ...(outcome === undefined ? {} : { outcome }), + ...(staged === undefined ? {} : { staged }) + })) + } +} + export interface ReplicateOptions { cid: string /** Age of the file, which decides how many copies it deserves. */ diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index 227e923..6b64718 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -3,6 +3,7 @@ import type { Multiaddr } from '@multiformats/multiaddr' import type { PeerId } from '@libp2p/interface' import { CID } from 'multiformats/cid' import type { IpfsNode } from '../ipfs-node.js' +import { FileLifecycleBusyError } from './registry.js' /** * Version of the replication wire format. @@ -73,7 +74,22 @@ export type ReplicationResponse = | { ok: true; op: 'have'; has: boolean } | { ok: true; op: 'accept'; willAccept: boolean } | { ok: true; op: 'cache'; cachedBytes: number } - | { ok: false; error: string } + | { ok: false; error: string; code?: ReplicationErrorCode } + +/** Why a replication request was refused, when the peer could name the reason. */ +export type ReplicationErrorCode = + 'not_authorized' | 'busy' | 'not_staged' | 'already_aborted' | 'no_room' | 'invalid' | 'failed' + +/** Structured refusal from {@link call}, so the client can tell busy from a lost ack. */ +export class ReplicationProtocolError extends Error { + constructor( + message: string, + readonly code: ReplicationErrorCode + ) { + super(message) + this.name = 'ReplicationProtocolError' + } +} /** * Node behaviour behind the protocol. @@ -92,11 +108,15 @@ export interface ReplicationHandlers { /** Pin `cid` and register it, returning the bytes now held for it. */ store(cid: string): Promise /** Prepare a rollback-capable copy for a strict upload. */ - stage(cid: string, transactionId: string): Promise<{ storedBytes: number; staged: boolean }> + stage( + cid: string, + transactionId: string, + peerId: string + ): Promise<{ storedBytes: number; staged: boolean }> /** Commit a prepared copy. */ - commit(cid: string, transactionId: string): Promise + commit(cid: string, transactionId: string, peerId: string): Promise /** Abort one transaction's claim on a prepared copy. */ - abort(cid: string, transactionId: string): Promise + abort(cid: string, transactionId: string, peerId: string): Promise /** Whether this node holds `cid` durably, not merely cached. */ have(cid: string): Promise /** @@ -243,6 +263,44 @@ function parseRequest(value: unknown): ReplicationRequest { return { op, cid: message.cid } } +function failureResponse(err: unknown): { ok: false; error: string; code: ReplicationErrorCode } { + if (err instanceof FileLifecycleBusyError) { + return { ok: false, error: 'File lifecycle is busy', code: 'busy' } + } + + if (err instanceof ReplicationProtocolError) { + return { ok: false, error: err.message, code: err.code } + } + + const message = err instanceof Error ? err.message : 'Replication request failed' + + if (message.includes('Not authorized') || message.includes('staged by another peer')) { + return { ok: false, error: 'Not authorized', code: 'not_authorized' } + } + + if (message.includes('was already aborted')) { + return { ok: false, error: message, code: 'already_aborted' } + } + + if ( + message.includes('unrelated temporary') || + message.includes('unsettled local') || + message.includes('active lifecycle') + ) { + return { ok: false, error: message, code: 'busy' } + } + + if (message.includes('is not staged')) { + return { ok: false, error: message, code: 'not_staged' } + } + + if (message.includes('no room') || message.includes('No room')) { + return { ok: false, error: message, code: 'no_room' } + } + + return { ok: false, error: 'Replication request failed', code: 'failed' } +} + async function respond( stream: Stream, connection: Connection, @@ -252,57 +310,61 @@ async function respond( const request = parseRequest(await readMessage(stream, AbortSignal.timeout(requestTimeoutMs))) const peerId = connection.remotePeer.toString() - // Holding an extra copy is open to anyone, because it costs no more than a - // read from the same peer would. Everything that makes this node responsible - // for content stays behind the authorization check. Open still means - // accountable: the handler charges the request to the peer that made it. - if (request.op === 'cache') { - if (!(await handlers.willAccept())) { - sendMessage(stream, { ok: false, error: 'No room for another copy' }) + try { + // Holding an extra copy is open to anyone, because it costs no more than a + // read from the same peer would. Everything that makes this node responsible + // for content stays behind the authorization check. Open still means + // accountable: the handler charges the request to the peer that made it. + if (request.op === 'cache') { + if (!(await handlers.willAccept())) { + sendMessage(stream, { ok: false, error: 'No room for another copy', code: 'no_room' }) + return + } + + const cachedBytes = await handlers.cacheCopy(request.cid, peerId) + sendMessage(stream, { ok: true, op: 'cache', cachedBytes }) return } - const cachedBytes = await handlers.cacheCopy(request.cid, peerId) - sendMessage(stream, { ok: true, op: 'cache', cachedBytes }) - return - } + if (!handlers.isAuthorized(peerId)) { + handlers.onRefused?.(peerId, request.op) + sendMessage(stream, { ok: false, error: 'Not authorized', code: 'not_authorized' }) + return + } - if (!handlers.isAuthorized(peerId)) { - handlers.onRefused?.(peerId, request.op) - sendMessage(stream, { ok: false, error: 'Not authorized' }) - return - } + if (request.op === 'have') { + sendMessage(stream, { ok: true, op: 'have', has: await handlers.have(request.cid) }) + return + } - if (request.op === 'have') { - sendMessage(stream, { ok: true, op: 'have', has: await handlers.have(request.cid) }) - return - } + if (request.op === 'accept') { + sendMessage(stream, { ok: true, op: 'accept', willAccept: await handlers.willAccept() }) + return + } - if (request.op === 'accept') { - sendMessage(stream, { ok: true, op: 'accept', willAccept: await handlers.willAccept() }) - return - } + if (request.op === 'stage') { + const staged = await handlers.stage(request.cid, request.transactionId, peerId) + sendMessage(stream, { ok: true, op: 'stage', ...staged }) + return + } - if (request.op === 'stage') { - const staged = await handlers.stage(request.cid, request.transactionId) - sendMessage(stream, { ok: true, op: 'stage', ...staged }) - return - } + if (request.op === 'commit') { + await handlers.commit(request.cid, request.transactionId, peerId) + sendMessage(stream, { ok: true, op: 'commit' }) + return + } - if (request.op === 'commit') { - await handlers.commit(request.cid, request.transactionId) - sendMessage(stream, { ok: true, op: 'commit' }) - return - } + if (request.op === 'abort') { + await handlers.abort(request.cid, request.transactionId, peerId) + sendMessage(stream, { ok: true, op: 'abort' }) + return + } - if (request.op === 'abort') { - await handlers.abort(request.cid, request.transactionId) - sendMessage(stream, { ok: true, op: 'abort' }) - return + const storedBytes = await handlers.store(request.cid) + sendMessage(stream, { ok: true, op: 'store', storedBytes }) + } catch (err) { + sendMessage(stream, failureResponse(err)) } - - const storedBytes = await handlers.store(request.cid) - sendMessage(stream, { ok: true, op: 'store', storedBytes }) } /** Start answering replication requests from other ADAMANT nodes. */ @@ -345,7 +407,10 @@ async function call( const response = (await readMessage(stream, signal)) as ReplicationResponse if (response?.ok !== true) { - throw new Error(response?.error ?? 'Replication request was refused') + throw new ReplicationProtocolError( + response?.error ?? 'Replication request was refused', + response?.code ?? 'failed' + ) } return response diff --git a/src/storage/service.ts b/src/storage/service.ts index 3e2847d..639798a 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -40,6 +40,8 @@ import { requestCommit, requestStage, requestStore, + ReplicationProtocolError, + type ReplicationErrorCode, type ReplicationHandlers, type ReplicationCallOptions } from './replicationProtocol.js' @@ -319,20 +321,24 @@ async function placeCopy( const result = await requestStage(helia, peer.multiAddr, cid, transactionId, callOptions()) return { outcome: 'stored', staged: result.staged } } catch (err) { - if ((err as Error).message.includes('Not authorized')) { + if (isNotAuthorizedError(err)) { throw err } - // The peer may have pinned before the ack was lost. Abort must still - // reach it; the copy must not count towards the quorum. - return { outcome: 'failed', staged: true, error: (err as Error).message } + // A lost ack after a pin must still be aborted. A structured refusal + // (busy, not staged, already aborted) never took ownership. + return { + outcome: 'failed', + staged: isMaybeStagedError(err), + error: (err as Error).message + } } } await requestStore(helia, peer.multiAddr, cid, callOptions()) return { outcome: 'stored', staged: false } } catch (err) { - if (!(err as Error).message.includes('Not authorized')) { + if (!isNotAuthorizedError(err)) { throw err } @@ -341,6 +347,37 @@ async function placeCopy( } } +const UNSTAGED_CODES: ReplicationErrorCode[] = [ + 'not_authorized', + 'busy', + 'not_staged', + 'already_aborted', + 'no_room', + 'invalid' +] + +function isNotAuthorizedError(err: unknown): boolean { + return ( + (err instanceof ReplicationProtocolError && err.code === 'not_authorized') || + (err as Error).message.includes('Not authorized') + ) +} + +function isMaybeStagedError(err: unknown): boolean { + if (err instanceof ReplicationProtocolError) { + return !UNSTAGED_CODES.includes(err.code) + } + + const message = (err as Error).message + return ( + !message.includes('Not authorized') && + !message.includes('busy') && + !message.includes('not staged') && + !message.includes('already aborted') && + !message.includes('no room') + ) +} + /** * Restore a missing durable copy without creating a new upload transaction. * @@ -377,7 +414,7 @@ async function repairCopy( await requestStore(helia, peer.multiAddr, cid, callOptions()) return { outcome: 'stored', staged: false } } catch (err) { - if (!(err as Error).message.includes('Not authorized')) { + if (!isNotAuthorizedError(err)) { throw err } @@ -495,7 +532,8 @@ export async function acceptReplica(cid: string): Promise { /** Prepare a remote copy that can still be withdrawn after a strict miss. */ export async function acceptStagedReplica( cid: string, - transactionId: string + transactionId: string, + originPeerId: string ): Promise<{ storedBytes: number; staged: boolean }> { return storageOperationLock.withShared(async () => { await pullUnderIntakeLimits(cid) @@ -508,6 +546,7 @@ export async function acceptStagedReplica( unixfs: ifs, cid: CID.parse(cid), transactionId, + originPeerId, temporaryTtlMs: settlementTtlMs(), pinTimeoutMs: config.replication.requestTimeoutMs }) @@ -517,19 +556,28 @@ export async function acceptStagedReplica( } /** A prepared transaction becomes a normal durable holder. */ -export async function commitStagedReplica(cid: string, transactionId: string): Promise { +export async function commitStagedReplica( + cid: string, + transactionId: string, + peerId: string +): Promise { await storageOperationLock.withShared(async () => { - await commitReplica({ ...lifecycleTarget(), cid: CID.parse(cid), transactionId }) + await commitReplica({ ...lifecycleTarget(), cid: CID.parse(cid), transactionId, peerId }) }) } /** Withdraw one source upload's claim on a prepared copy. */ -export async function abortStagedReplica(cid: string, transactionId: string): Promise { +export async function abortStagedReplica( + cid: string, + transactionId: string, + peerId: string +): Promise { await storageOperationLock.withShared(async () => { await abortReplica({ ...lifecycleTarget(), cid: CID.parse(cid), transactionId, + peerId, tombstoneTtlMs: settlementTtlMs() }) }) diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index edbe25c..c4b0065 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -17,12 +17,13 @@ const cursors = new Map() * * Slicing from the front would examine the same files on every pass and never * reach the rest, so the batch wraps around the end and the cursor moves with - * A cursor pointing at a record that has since gone resumes at the first - * remaining CID greater than it, so a successful demote/rescue of the last - * batch element does not rewind to the head. + * it. Candidates are sorted by CID first: resume compares CIDs lexicographically, + * and datastore listing order is not sorted. A cursor pointing at a record that + * has since gone resumes at the first remaining CID greater than it, so a + * successful demote/rescue of the last batch element does not rewind to the head. * * @param sweep Which sweep is asking; each keeps its own position - * @param records Every candidate, in a stable order + * @param records Every candidate; order is ignored and replaced by CID order * @param options `advance: false` reports the next batch without taking it, for * a dry run that must not move the position the real pass resumes from */ @@ -33,36 +34,43 @@ export function nextSweepBatch( ): T[] { const size = SWEEP_BATCHES[sweep] const advance = options.advance !== false + const ordered = [...records].sort((left, right) => { + if (left.cid === right.cid) { + return 0 + } + + return left.cid < right.cid ? -1 : 1 + }) - if (records.length <= size) { + if (ordered.length <= size) { if (advance) { cursors.delete(sweep) } - return records + return ordered } const previous = cursors.get(sweep) let start = 0 if (previous !== undefined) { - const exact = records.findIndex((item) => item.cid === previous) + const exact = ordered.findIndex((item) => item.cid === previous) if (exact >= 0) { start = exact + 1 } else { // The cursor record left the candidate set (the usual success path for // demote/rescue). Resume at the first CID strictly greater so the pass // does not rewind to the head. - const next = records.findIndex((item) => item.cid > previous) + const next = ordered.findIndex((item) => item.cid > previous) start = next >= 0 ? next : 0 } } - if (start >= records.length) { + if (start >= ordered.length) { start = 0 } - const batch = [...records.slice(start), ...records.slice(0, start)].slice(0, size) + const batch = [...ordered.slice(start), ...ordered.slice(0, start)].slice(0, size) if (advance) { cursors.set(sweep, batch[batch.length - 1].cid) diff --git a/src/utils/unixfs-multer.storage.ts b/src/utils/unixfs-multer.storage.ts index 86cb8eb..9ce4ef1 100644 --- a/src/utils/unixfs-multer.storage.ts +++ b/src/utils/unixfs-multer.storage.ts @@ -89,28 +89,32 @@ export class UnixfsMulterStorage implements StorageEngine { ): void { file.originalname = sanitizeFilename(file.originalname) - const session = this.options.getSession(req) - const write = session.beginFile() - const metered = budgeted(file.stream, session.budget) + try { + const session = this.options.getSession(req) + const write = session.beginFile() + const metered = budgeted(file.stream, session.budget) - unixfs({ blockstore: write.blockstore }) - .addByteStream(metered.stream) - .then((cid) => { - // `size` is what multer would have set for a disk engine, and what the - // lifecycle record stores as the file's size. Leaving it unset wrote - // records without one, which nothing noticed until the registry began - // validating what it reads. - callback(undefined, { - ...file, - cid, - size: metered.bytes, - storedBytes: write.storedBytes, - protectedBytes: write.protectedBytes + unixfs({ blockstore: write.blockstore }) + .addByteStream(metered.stream) + .then((cid) => { + // `size` is what multer would have set for a disk engine, and what the + // lifecycle record stores as the file's size. Leaving it unset wrote + // records without one, which nothing noticed until the registry began + // validating what it reads. + callback(undefined, { + ...file, + cid, + size: metered.bytes, + storedBytes: write.storedBytes, + protectedBytes: write.protectedBytes + }) }) - }) - .catch((err) => { - callback(err, undefined) - }) + .catch((err) => { + callback(err, undefined) + }) + } catch (err) { + callback(err as Error) + } } /** @@ -124,8 +128,15 @@ export class UnixfsMulterStorage implements StorageEngine { file: Express.Multer.File, callback: (error: Error | null) => void ): void { - this.options - .getSession(req) + let session + try { + session = this.options.getSession(req) + } catch (err) { + callback(err as Error) + return + } + + session .cleanup() .then((removed) => { if (removed > 0) { diff --git a/test/garbageCollectionPlan.test.ts b/test/garbageCollectionPlan.test.ts index 732764d..195f9b8 100644 --- a/test/garbageCollectionPlan.test.ts +++ b/test/garbageCollectionPlan.test.ts @@ -221,4 +221,22 @@ describe('planGarbageCollection', () => { assert.deepEqual(plan.evicted, []) assert.ok(plan.retained.some((item) => item.cid === 'uploading')) }) + + it('does not expire a temporary file whose upload is still in flight', () => { + const uploading = record({ + cid: 'late-upload', + state: 'temporary', + expiresAt: 500, + admissionId: 'req-late' + }) + const plan = planGarbageCollection({ + blockstoreBytes: 10, + watermarks, + records: [uploading], + now: 1000 + }) + + assert.deepEqual(plan.expired, []) + assert.ok(plan.retained.some((item) => item.cid === 'late-upload')) + }) }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index b176500..4bfe52f 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -561,7 +561,6 @@ describe('garbage collection against fixture data', () => { withCollectionLease: runWithoutOtherWriters, watermarks: TIGHT, blockstoreBytes: 4096, - pinTimeoutMs: 1500, now: 1000 }) @@ -1134,7 +1133,7 @@ describe('lifecycle transitions', () => { assert.equal(await isDirectlyPinned(node, cid), true) }) - it('passes the configured timeout to a known CID pin', async () => { + it('does not abort Helia pins.add, which cannot roll back a partial walk', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-timeout')) await registry.save( @@ -1164,8 +1163,7 @@ describe('lifecycle transitions', () => { pinTimeoutMs: 30_000 }) - assert.ok(receivedSignal instanceof AbortSignal) - assert.equal(receivedSignal.aborted, false) + assert.equal(receivedSignal, undefined) }) it('removes the pin it created when confirmation cannot be recorded', async () => { @@ -1299,13 +1297,20 @@ describe('lifecycle transitions', () => { }) describe('strict-upload replica staging', () => { - const stage = (registry: FileRegistry, cid: CID, transactionId: string, now = 1000) => + const stage = ( + registry: FileRegistry, + cid: CID, + transactionId: string, + now = 1000, + originPeerId?: string + ) => stageReplica({ node, unixfs: ifs, registry, cid, transactionId, + originPeerId, temporaryTtlMs: 1000, now }) @@ -1327,6 +1332,45 @@ describe('strict-upload replica staging', () => { assert.equal(await isDirectlyPinned(node, cid), false) }) + it('refuses commit and abort from a peer that did not stage the transaction', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_000, 'replica-stage-owner')) + + await stage(registry, cid, 'tx-owned', 1000, 'peer-origin') + + await assert.rejects( + () => + commitReplica({ + node, + registry, + cid, + transactionId: 'tx-owned', + peerId: 'peer-other' + }), + /staged by another peer/ + ) + await assert.rejects( + () => + abortReplica({ + node, + registry, + cid, + transactionId: 'tx-owned', + peerId: 'peer-other' + }), + /staged by another peer/ + ) + + const committed = await commitReplica({ + node, + registry, + cid, + transactionId: 'tx-owned', + peerId: 'peer-origin' + }) + assert.equal(committed?.state, 'confirmed') + }) + it('keeps a shared stage until one transaction commits it', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(21_000, 'replica-stage-shared')) diff --git a/test/replication.test.ts b/test/replication.test.ts index e680627..64cf63a 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -7,6 +7,7 @@ import { isUnderReplicated, replicate, requiredAcks, + toPublicReplicationReport, type ReplicationPeer } from '../src/storage/replication.js' @@ -336,3 +337,37 @@ describe('isUnderReplicated', () => { assert.equal(isUnderReplicated(1, placement, SELF), false) }) }) + +describe('toPublicReplicationReport', () => { + it('strips holder names, peer ids, and error text', () => { + const publicReport = toPublicReplicationReport({ + mode: 'quorum', + desiredCopies: 4, + copies: 3, + required: 2, + acknowledged: 2, + replicas: ['ipfs2'], + cached: ['ipfs3'], + satisfied: true, + networkTooSmall: false, + attempts: [ + { + node: 'ipfs2', + peerId: '12D3KooWsecret', + ok: false, + outcome: 'stored', + staged: true, + error: 'ECONNRESET at /var/lib/ipfs' + } + ] + }) + + assert.equal(publicReport.replicaCount, 1) + assert.equal(publicReport.cachedCount, 1) + assert.equal(publicReport.failedAttemptCount, 1) + assert.deepEqual(publicReport.attempts, [{ ok: false, outcome: 'stored', staged: true }]) + assert.equal(JSON.stringify(publicReport).includes('12D3KooWsecret'), false) + assert.equal(JSON.stringify(publicReport).includes('ECONNRESET'), false) + assert.equal(JSON.stringify(publicReport).includes('ipfs2'), false) + }) +}) diff --git a/test/sweepBatch.test.ts b/test/sweepBatch.test.ts index 4aa417a..5df81bd 100644 --- a/test/sweepBatch.test.ts +++ b/test/sweepBatch.test.ts @@ -76,4 +76,18 @@ describe('nextSweepBatch', () => { assert.ok(second[0].cid > cursor) }) + + it('does not rewind on an unsorted listing when the cursor CID is gone', () => { + const size = SWEEP_BATCHES.demote + const all = Array.from({ length: size + 8 }, (unused, index) => ({ + cid: `z-${String(index).padStart(3, '0')}` + })) + const shuffled = [...all.slice(size / 2), ...all.slice(0, size / 2)] + const first = nextSweepBatch('demote', shuffled) + const cursor = first[first.length - 1].cid + const remaining = shuffled.filter((record) => record.cid !== cursor) + const second = nextSweepBatch('demote', remaining) + + assert.ok(second[0].cid > cursor) + }) }) From 756cb353c51769b012d6ca61a0a5d7681ddc1553 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Wed, 26 Aug 2026 22:01:18 +0300 Subject: [PATCH 135/167] fix(storage): enhance lifecycle management with preparation for unknown CID pinning --- src/storage/lifecycle.ts | 15 +++ src/storage/replicaStage.ts | 72 ++++++++++++--- src/storage/service.ts | 13 ++- test/integration/storageLifecycle.test.ts | 107 ++++++++++++++++++++++ 4 files changed, 190 insertions(+), 17 deletions(-) diff --git a/src/storage/lifecycle.ts b/src/storage/lifecycle.ts index 6dbe75b..9cc9238 100644 --- a/src/storage/lifecycle.ts +++ b/src/storage/lifecycle.ts @@ -163,6 +163,13 @@ export interface ConfirmOptions extends RegisterPinnedOptions { * stores and registers it. */ registerUnknown?: boolean + /** + * Fetch or validate content before an explicit unknown-CID pin. + * + * Required when `registerUnknown` is true. It runs before the CID lock so a + * bounded network transfer cannot block other lifecycle work for this CID. + */ + prepareForPin?: () => Promise now?: number } @@ -180,6 +187,14 @@ export interface ConfirmOptions extends RegisterPinnedOptions { export async function confirmStoredFile(options: ConfirmOptions): Promise { const key = options.cid.toString() + if (options.registerUnknown === true) { + if (options.prepareForPin === undefined) { + throw new Error(`CID ${key} must be fetched or validated before it can be adopted`) + } + + await options.prepareForPin() + } + return options.registry.withExclusiveCids([key], async (registry) => { const current = await registry.get(key) diff --git a/src/storage/replicaStage.ts b/src/storage/replicaStage.ts index 92f0045..4b5b353 100644 --- a/src/storage/replicaStage.ts +++ b/src/storage/replicaStage.ts @@ -22,8 +22,16 @@ const abortedTransactions = new Map() const DEFAULT_ABORT_TOMBSTONE_TTL_MS = 5 * 60 * 1000 const MAX_ABORT_TOMBSTONES = 10_000 -function transactionKey(cid: string, transactionId: string): string { - return `${cid}\0${transactionId}` +function transactionPrefix(cid: string, transactionId: string): string { + return `${cid}\0${transactionId}\0` +} + +function transactionKey( + cid: string, + transactionId: string, + originPeerId: string | undefined +): string { + return `${transactionPrefix(cid, transactionId)}${originPeerId ?? ''}` } function pruneAbortTombstones(now: number): void { @@ -42,15 +50,37 @@ function pruneAbortTombstones(now: number): void { } } -function rememberAbort(cid: string, transactionId: string, expiresAt: number, now: number): void { +function rememberAbort( + cid: string, + transactionId: string, + expiresAt: number, + now: number, + originPeerId?: string +): void { pruneAbortTombstones(now) - abortedTransactions.set(transactionKey(cid, transactionId), expiresAt) + abortedTransactions.set(transactionKey(cid, transactionId, originPeerId), expiresAt) pruneAbortTombstones(now) } -function wasAborted(cid: string, transactionId: string, now: number): boolean { +function wasAborted( + cid: string, + transactionId: string, + originPeerId: string | undefined, + now: number +): boolean { pruneAbortTombstones(now) - return (abortedTransactions.get(transactionKey(cid, transactionId)) ?? 0) > now + const prefix = transactionPrefix(cid, transactionId) + + // Protocol calls always identify their peer. Keeping unknown legacy callers + // conservative preserves the old abort-before-stage behaviour. + if (originPeerId === undefined) { + return [...abortedTransactions.keys()].some((key) => key.startsWith(prefix)) + } + + return ( + abortedTransactions.has(transactionKey(cid, transactionId, undefined)) || + abortedTransactions.has(transactionKey(cid, transactionId, originPeerId)) + ) } function withOrigin( @@ -194,17 +224,27 @@ export async function stageReplica(options: ReplicaStageTarget): Promise { - if (wasAborted(key, options.transactionId, now)) { + if (wasAborted(key, options.transactionId, options.originPeerId, now)) { throw new Error(`Replica transaction ${options.transactionId} for ${key} was already aborted`) } const previous = await registry.get(key) if (previous?.replicaStage !== undefined) { + const alreadyStaged = previous.replicaStage.transactionIds.includes(options.transactionId) + + if (alreadyStaged) { + assertStageOwner( + options.transactionId, + previous.replicaStage.origins?.[options.transactionId], + options.originPeerId + ) + } + await pinFile(options.node, options.cid) try { - const transactionIds = previous.replicaStage.transactionIds.includes(options.transactionId) + const transactionIds = alreadyStaged ? previous.replicaStage.transactionIds : [...previous.replicaStage.transactionIds, options.transactionId] const record = await registry.save({ @@ -216,11 +256,13 @@ export async function stageReplica(options: ReplicaStageTarget): Promise id !== options.transactionId) const origins = { ...stage.origins } diff --git a/src/storage/service.ts b/src/storage/service.ts index 639798a..c714101 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -7,7 +7,7 @@ import { availableStorageSize } from '../utils/utils.js' import { getNodesList } from '../utils/utils.js' import { blockstorePath } from '../store.js' import { mayDemote, placeFile, storageTargets, type Placement } from './placement.js' -import { isDirectlyPinned, pinFile, unpinFile } from './pinning.js' +import { hasLocalDag, isDirectlyPinned, pinFile, unpinFile } from './pinning.js' import { confirmStoredFile, registerPinnedFile, @@ -449,13 +449,22 @@ export async function confirmFile( cid: string, options: { registerUnknown?: boolean } = {} ): Promise { + const parsed = CID.parse(cid) const confirmed = await storageOperationLock.withShared(() => confirmStoredFile({ ...lifecycleTarget(), unixfs: ifs, - cid: CID.parse(cid), + cid: parsed, name: cid, registerUnknown: options.registerUnknown, + prepareForPin: + options.registerUnknown === true + ? async () => { + if (!(await hasLocalDag(helia, parsed))) { + await pullUnderIntakeLimits(cid) + } + } + : undefined, temporaryTtlMs: config.storage.temporaryTtlMs, pinTimeoutMs: config.replication.requestTimeoutMs }) diff --git a/test/integration/storageLifecycle.test.ts b/test/integration/storageLifecycle.test.ts index 4bfe52f..f5a30fb 100644 --- a/test/integration/storageLifecycle.test.ts +++ b/test/integration/storageLifecycle.test.ts @@ -1102,6 +1102,7 @@ describe('lifecycle transitions', () => { unixfs: ifs, cid, registerUnknown: true, + prepareForPin: async () => undefined, temporaryTtlMs: TTL }) @@ -1110,6 +1111,30 @@ describe('lifecycle transitions', () => { assert.equal(await isDirectlyPinned(node, cid), true) }) + it('does not start pinning when bounded preparation of an unknown CID times out', async () => { + const registry = createRegistry() + const cid = CID.parse('bafkreiapv3pyvtsdmvcqcgzvvjmayksybgcgfvkbfbbccjnisz3ndnvcam') + + await assert.rejects( + () => + confirmStoredFile({ + node, + registry, + unixfs: ifs, + cid, + registerUnknown: true, + prepareForPin: async () => { + throw new Error('bounded fetch timed out') + }, + temporaryTtlMs: TTL + }), + /bounded fetch timed out/ + ) + + assert.equal(await registry.get(cid.toString()), undefined) + assert.equal(await isDirectlyPinned(node, cid), false) + }) + it('confirms a temporary file, clears its expiry and pins it', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(2048, 'confirm-temporary')) @@ -1371,6 +1396,88 @@ describe('strict-upload replica staging', () => { assert.equal(committed?.state, 'confirmed') }) + it('does not let an idempotent stage replace the transaction owner', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_100, 'replica-stage-restage-owner')) + + await stage(registry, cid, 'tx-owned', 1000, 'peer-origin') + + await assert.rejects( + () => stage(registry, cid, 'tx-owned', 1100, 'peer-other'), + /staged by another peer/ + ) + + assert.equal( + (await registry.get(cid.toString()))?.replicaStage?.origins?.['tx-owned'], + 'peer-origin' + ) + + await abortReplica({ + node, + registry, + cid, + transactionId: 'tx-owned', + peerId: 'peer-origin' + }) + assert.equal(await registry.get(cid.toString()), undefined) + }) + + it('keeps peer abort tombstones isolated', async () => { + const registry = createRegistry() + const cid = await ifs.addBytes(deterministicBytes(21_200, 'replica-stage-abort-owner')) + + await abortReplica({ + node, + registry, + cid, + transactionId: 'tx-raced', + peerId: 'peer-other', + tombstoneTtlMs: 1000, + now: 1000 + }) + + const prepared = await stage(registry, cid, 'tx-raced', 1100, 'peer-origin') + + assert.equal(prepared.staged, true) + assert.equal(prepared.record.replicaStage?.origins?.['tx-raced'], 'peer-origin') + + await abortReplica({ + node, + registry, + cid, + transactionId: 'tx-raced', + peerId: 'peer-origin', + now: 1200 + }) + + const secondCid = await ifs.addBytes( + deterministicBytes(21_300, 'replica-stage-abort-owner-overwrite') + ) + await abortReplica({ + node, + registry, + cid: secondCid, + transactionId: 'tx-raced-again', + peerId: 'peer-origin', + tombstoneTtlMs: 1000, + now: 2000 + }) + await abortReplica({ + node, + registry, + cid: secondCid, + transactionId: 'tx-raced-again', + peerId: 'peer-other', + tombstoneTtlMs: 1000, + now: 2100 + }) + + await assert.rejects( + () => stage(registry, secondCid, 'tx-raced-again', 2200, 'peer-origin'), + /already aborted/ + ) + }) + it('keeps a shared stage until one transaction commits it', async () => { const registry = createRegistry() const cid = await ifs.addBytes(deterministicBytes(21_000, 'replica-stage-shared')) From cee8b0d8b316d578daa497de30044c981431c849 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Sun, 30 Aug 2026 22:34:27 +0300 Subject: [PATCH 136/167] feat: enhance health monitoring and replication repair mechanisms - Introduced health service with startup reconciliation and health state management. - Added HTTP metrics collection middleware for observability. - Enhanced replication repair logic to maintain state across cycles and handle evidence. - Updated storage service to support external abort signals during retrieval. - Improved file download response handling with additional headers and disconnect management. - Refactored timed stream utility for better timeout handling. - Added tests for health state evaluation, timed streams, and sweep batch processing. - Updated configuration validation to include health-related parameters. - Enhanced logging configuration to support pretty output based on settings. --- README.md | 68 ++++-- config.default.json5 | 11 + config.test.json5 | 10 + docs/openapi.yaml | 239 +++++++++++++++++++ docs/storage-lifecycle.md | 12 +- package.json | 1 + src/api/file.ts | 23 +- src/api/node.ts | 25 +- src/config.ts | 93 +++++++- src/health/membership.ts | 9 + src/health/persistence.ts | 43 ++++ src/health/protocol.ts | 159 ++++++++++++ src/health/service.ts | 169 +++++++++++++ src/health/state.ts | 114 +++++++++ src/index.ts | 18 +- src/observability.ts | 50 ++++ src/replication.cron.ts | 152 +++++++++++- src/storage/retrieval.ts | 18 +- src/storage/service.ts | 61 +++-- src/storage/sweep.ts | 94 +++++--- src/utils/downloadResponse.ts | 18 +- src/utils/file.ts | 48 ++-- src/utils/logger.ts | 22 +- src/utils/timedStream.ts | 48 ++++ test/config.test.ts | 14 ++ test/downloadResponse.test.ts | 3 + test/healthState.test.ts | 80 +++++++ test/integration/replicationProtocol.test.ts | 63 +++++ test/security.test.ts | 19 +- test/sweepBatch.test.ts | 18 +- test/timedStream.test.ts | 44 ++++ 31 files changed, 1608 insertions(+), 138 deletions(-) create mode 100644 docs/openapi.yaml create mode 100644 src/health/membership.ts create mode 100644 src/health/persistence.ts create mode 100644 src/health/protocol.ts create mode 100644 src/health/service.ts create mode 100644 src/health/state.ts create mode 100644 src/observability.ts create mode 100644 src/utils/timedStream.ts create mode 100644 test/healthState.test.ts create mode 100644 test/timedStream.test.ts diff --git a/README.md b/README.md index 3df9110..0cb5695 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This service embeds a Helia/libp2p node and exposes the file-transfer API used b The application is not a Kubo wrapper. It is a Node.js service with an Express REST API around an in-process Helia node. +The stable client and lifecycle contract is also available as [OpenAPI 3.1](docs/openapi.yaml). + ## Requirements - Node.js 24 LTS. The repository ships an `.nvmrc`, so `nvm use` selects it @@ -57,6 +59,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, }, "adminApiKey": "", "enableDebugApi": false, + "prettyLogs": false, "peeringSchedule": "*/30 * * * * *", "storage": { "maxRequestSizeBytes": 536870912, @@ -83,11 +86,22 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "requestTimeoutMs": 30000, "repairEnabled": true, "repairSchedule": "0 */30 * * * *" + }, + "health": { + "checkpointIntervalMs": 60000, + "maxCheckpointAgeMs": 180000, + "storageMaxAgeMs": 120000, + "repairMaxAgeMs": 3600000, + "clockSkewToleranceMs": 10000, + "peerAttestationTimeoutMs": 5000, + "requiredPeerCount": 1 } } ``` -`storage` and `replication` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Both sections are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. +`storage`, `replication`, and `health` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Storage and replication are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. + +Logs are newline-delimited JSON by default. Set `prettyLogs: true` only for an interactive development terminal; production collectors should keep the structured fields emitted by Pino and the health checkpoint. Generate the administrative secret before enabling operator endpoints: @@ -110,21 +124,22 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - `storage.gc.enabled` is on by default and frees blocks only when the blockstore passes `highWatermarkBytes` or free space falls into `diskReserveBytes`; released files stay readable until then - `replication.enabled` is on by default and needs no key and no extra address: copies travel on a libp2p protocol between the peers already listed in `nodes`. Turning it off leaves every file in a single copy - Tune `replication.placement` if the deployment wants a different number of copies per file age +- Keep `health.requiredPeerCount` within the number of configured remote peers. A single-node or test deployment may set it to `0` - `peeringSchedule` is optional and defaults to every thirty seconds; it redials the peers in `nodes` that are not connected, which `autoPeeringPeriod` never did Invalid CORS, proxy, API-key, or rate-limit configuration stops the process instead of silently weakening the boundary. ## HTTP access policy -| Class | Routes | Policy | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Public | `GET /`, `GET /api/node/health` | No authentication | -| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | -| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | -| Administrative | `GET /api/node/info`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | -| Peer replication | libp2p `/adamant/replication/1.0.0`, not an HTTP route | Authenticated by the libp2p handshake. Pin, store, stage, commit, and abort are accepted only from the peers listed in `nodes`. `cache` is open to any peer (same effect as a public read), bounded by disk reserve and intake budget | -| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | -| Authenticated user | None | The service has no end-user identity or session layer | +| Class | Routes | Policy | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Public | `GET /`, `GET /api/node/health`, `GET /api/node/info` | No authentication. `/info` is the sanitized legacy PWA/iOS contract | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; endpoint-specific rate limits and upload limits apply | +| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication; no filename or peer topology is exposed | +| Administrative | `GET /api/node/details`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, all `/api/storage/*` writes, all `/api/helia/*`, all `/api/libp2p/*` | A matching `x-api-key` header is required | +| Peer protocols | libp2p `/adamant/replication/1.0.0` and `/adamant/health/1.0.0`, not HTTP routes | Authenticated by the libp2p handshake. Health attestations and durable replication operations are accepted only from peers listed in `nodes`; open cache remains bounded like a public read | +| Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | +| Authenticated user | None | The service has no end-user identity or session layer | Administrative coverage includes pin operations, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. @@ -228,7 +243,7 @@ Three configurations share one set of compiler options: Type-check everything without emitting: ```bash -npx tsc --noEmit +npm run typecheck ``` Pass no file arguments to `tsc`. Naming a file on the command line makes TypeScript ignore `tsconfig.json` entirely, so `outDir` is not applied — the `.js` file is written next to its source — and the project's `lib`, `module`, and `moduleResolution` settings are replaced by defaults, which reports module-resolution and missing-`@types/node` errors that the project itself does not have. @@ -336,9 +351,10 @@ Download responses use the following stable status contract: | `400 Bad Request` | The CID is invalid | | `408 Request Timeout` | The file could not be found or retrieved before the configured timeout | | `429 Too Many Requests` | The read rate limit was exceeded | +| `416 Range Not Satisfiable` | Range retrieval is intentionally unsupported | | `500 Internal Server Error` | An unexpected internal failure occurred before streaming started | -The timeout status remains `408 Request Timeout` for compatibility with existing clients. If an error occurs after response bytes have started, the server terminates the incomplete response because an HTTP status and JSON error body can no longer be sent safely. +The timeout covers the complete transfer, not only discovery or the first chunk, and remains `408 Request Timeout` for compatibility with existing clients. Client disconnect cancels the underlying retrieval. Successful immutable-CID responses include `ETag`, long-lived immutable caching, and `Accept-Ranges: none`. If an error occurs after response bytes have started, the server terminates the incomplete response because an HTTP status and JSON error body can no longer be sent safely. ### Check public health @@ -350,19 +366,43 @@ Example response: ```json { + "status": "ready", + "height": 1720614960000, "timestamp": 1720614998797, - "heliaStatus": "started" + "checkpointIntervalMs": 60000, + "membershipVersion": "d0f1...", + "checks": { + "helia": true, + "startupReconciliation": true, + "storageFresh": true, + "storageReserve": true, + "repairFresh": true, + "peerAttestations": true + }, + "peerAttestations": { "required": 1, "received": 2 } } ``` +The endpoint always returns `200`; consumers must inspect `status`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle, and the configured peer attestations all pass. It freezes on failure. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. + +### Get legacy client node information + +```bash +curl --fail-with-body https://ipfs.example.org/api/node/info +``` + +This public compatibility route retains `version`, `timestamp`, `heliaStatus`, `blockstoreSizeMb`, `datastoreSizeMb`, and `availableSizeInMb` for the current PWA and iOS application. It does not expose peer identity or topology. + ### Get administrative node information ```bash curl --fail-with-body \ --header 'x-api-key: your-generated-key' \ - https://ipfs.example.org/api/node/info + https://ipfs.example.org/api/node/details ``` +The authenticated response adds peer addresses, byte-accurate storage figures, checkpoint detail, and bounded HTTP counters without paths, CIDs, IP addresses, or user-controlled metric labels. + ### Run garbage collection Add `?dryRun=true` to report the exact CIDs that would be released and retained without deleting anything. This is the supported way to review a deletion policy before enabling the scheduled collector. diff --git a/config.default.json5 b/config.default.json5 index c5d119f..445b0b0 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -15,6 +15,8 @@ ], storeFolder: '.adm-ipfs', logLevel: 'debug', + // Production logs are structured JSON. Enable this only for local terminals. + prettyLogs: false, peerDiscovery: { bootstrap: [ '/ip4/194.163.154.252/tcp/4001/p2p/12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq', // ipfs1 @@ -95,5 +97,14 @@ requestTimeoutMs: 30000, repairEnabled: true, repairSchedule: '0 */30 * * * *' + }, + health: { + checkpointIntervalMs: 60000, + maxCheckpointAgeMs: 180000, + storageMaxAgeMs: 120000, + repairMaxAgeMs: 3600000, + clockSkewToleranceMs: 10000, + peerAttestationTimeoutMs: 5000, + requiredPeerCount: 1 } } diff --git a/config.test.json5 b/config.test.json5 index 0762b67..8c107fb 100644 --- a/config.test.json5 +++ b/config.test.json5 @@ -4,6 +4,7 @@ nodes: [], storeFolder: '.adm-ipfs-test', logLevel: 'silent', + prettyLogs: false, peerDiscovery: { bootstrap: [], // Port 0 lets the OS pick a free port so tests never clash with a running node @@ -50,5 +51,14 @@ requestTimeoutMs: 5000, repairEnabled: false, repairSchedule: '0 */30 * * * *' + }, + health: { + checkpointIntervalMs: 1000, + maxCheckpointAgeMs: 3000, + storageMaxAgeMs: 2000, + repairMaxAgeMs: 5000, + clockSkewToleranceMs: 100, + peerAttestationTimeoutMs: 100, + requiredPeerCount: 0 } } diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..069e6e4 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,239 @@ +openapi: 3.1.0 +info: + title: ADAMANT IPFS Node API + version: 0.1.0 + description: >- + Stable client, lifecycle, and node-health contract. Low-level Helia, + libp2p, and optional debug operator routes are intentionally excluded. +servers: + - url: https://ipfs.example.org +paths: + /api/node/health: + get: + summary: Read the network-aware node checkpoint + responses: + '200': + description: The current state; inspect status because this route is always 200 + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + /api/node/info: + get: + summary: Read sanitized legacy PWA and iOS node information + responses: + '200': + description: Legacy node information + content: + application/json: + schema: + $ref: '#/components/schemas/LegacyNodeInfo' + /api/node/details: + get: + summary: Read detailed operator state + security: + - AdminApiKey: [] + responses: + '200': + description: Node identity, storage, health, and bounded HTTP metrics + '401': + $ref: '#/components/responses/Unauthorized' + '503': + $ref: '#/components/responses/AdminNotConfigured' + /api/file/upload: + post: + summary: Upload one or more files + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [files] + properties: + files: + type: array + items: + type: string + format: binary + responses: + '200': + description: Files were stored and pinned + '400': + $ref: '#/components/responses/BadRequest' + '413': + description: Request or file limit exceeded + '429': + description: Upload rate or concurrency limit exceeded + '503': + description: Required replication quorum was not reached + '507': + description: The disk reserve would be consumed + /api/file/{cid}: + parameters: + - $ref: '#/components/parameters/Cid' + get: + summary: Download a complete immutable file + responses: + '200': + description: Complete file attachment + headers: + ETag: + schema: { type: string } + Cache-Control: + schema: { type: string } + Accept-Ranges: + schema: { type: string, const: none } + content: + application/octet-stream: + schema: { type: string, format: binary } + '400': + $ref: '#/components/responses/BadRequest' + '408': + description: Discovery or complete retrieval timed out + '416': + description: Range requests are not supported + '429': + description: Read rate limit exceeded + /api/file/{cid}/status: + parameters: + - $ref: '#/components/parameters/Cid' + get: + summary: Read public lifecycle and replication counts + responses: + '200': { description: File lifecycle state } + '404': { description: Unknown CID } + /api/file/{cid}/confirm: + parameters: + - $ref: '#/components/parameters/Cid' + post: + summary: Promote a temporary file to durable storage + security: + - AdminApiKey: [] + responses: + '200': { description: File confirmed } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { description: Unknown CID } + /api/file/{cid}/unpin: + parameters: + - $ref: '#/components/parameters/Cid' + post: + summary: Release durable content + security: + - AdminApiKey: [] + responses: + '200': { description: File released for later collection } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { description: Unknown CID } + /api/storage/metrics: + get: + summary: Read public bounded storage and replication counts + responses: + '200': { description: Cached storage metrics } + /api/storage/policy: + get: + summary: Read upload, lifecycle, and durability policy + responses: + '200': { description: Public storage policy } + /api/storage/gc: + post: + summary: Run or preview garbage collection + security: + - AdminApiKey: [] + parameters: + - name: dryRun + in: query + schema: { type: boolean, default: false } + responses: + '200': { description: Garbage collection report } + '401': { $ref: '#/components/responses/Unauthorized' } + '409': { description: Garbage collection is already running } + /api/storage/repair: + post: + summary: Run the next bounded replication repair batch + security: + - AdminApiKey: [] + responses: + '200': { description: Repair batch and cycle progress } + '401': { $ref: '#/components/responses/Unauthorized' } + '409': { description: Replication repair is already running } +components: + securitySchemes: + AdminApiKey: + type: apiKey + in: header + name: x-api-key + parameters: + Cid: + name: cid + in: path + required: true + schema: { type: string } + schemas: + Health: + type: object + required: + - status + - height + - timestamp + - checkpointIntervalMs + - membershipVersion + - checks + - peerAttestations + properties: + status: + type: string + enum: [starting, ready, stale, degraded] + height: + type: integer + minimum: 0 + description: Persisted Unix milliseconds at the start of the last valid fixed round + timestamp: { type: integer } + checkpointIntervalMs: { type: integer, minimum: 1000 } + membershipVersion: { type: string, pattern: '^[a-f0-9]{64}$' } + checks: + type: object + additionalProperties: { type: boolean } + peerAttestations: + type: object + required: [required, received] + properties: + required: { type: integer, minimum: 0 } + received: { type: integer, minimum: 0 } + LegacyNodeInfo: + type: object + required: + - version + - timestamp + - heliaStatus + - blockstoreSizeMb + - datastoreSizeMb + - availableSizeInMb + properties: + version: { type: string } + timestamp: { type: integer } + heliaStatus: { type: string } + blockstoreSizeMb: { type: number } + datastoreSizeMb: { type: number } + availableSizeInMb: { type: number } + Error: + type: object + required: [error] + properties: + error: { type: string } + responses: + BadRequest: + description: Invalid request + content: + application/json: + schema: { $ref: '#/components/schemas/Error' } + Unauthorized: + description: Administrative API key is missing or invalid + content: + application/json: + schema: { $ref: '#/components/schemas/Error' } + AdminNotConfigured: + description: Administrative API key is not configured + content: + application/json: + schema: { $ref: '#/components/schemas/Error' } diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 5d97201..4915b79 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -458,6 +458,13 @@ The repair job (`replication.repairSchedule`, or `POST /api/storage/repair`) lists confirmed files this node holds whose designated peers have not all acknowledged, and places the missing copies again. +Each scheduled pass remains bounded to 50 held files. Its sorted cursor and +aggregate result are persisted under the node datastore, so restart resumes the +same cycle. A repair checkpoint is recorded only after a pass reaches the end +of the candidate set; a pass over one batch is not presented as full-fleet +evidence. A cycle containing an unresolved shortfall is complete but unhealthy, +and therefore cannot advance the node health checkpoint. + Replication needs the peers to be connected over libp2p, because the copy itself travels by bitswap. Startup peering opens those connections before the API starts serving, so the first upload after a restart already has somewhere to @@ -536,8 +543,9 @@ A collection report also lists `demoted`: files whose local copy was handed over to their designated holders during that pass. Values are refreshed on the `diskUsageScanPeriod` schedule, because a directory -scan and a full registry sweep are too expensive for a request path. A subset is -also included in `GET /api/node/info`. +scan and a full registry sweep are too expensive for a request path. A legacy +megabyte subset is included in public `GET /api/node/info`; detailed byte values +are included in authenticated `GET /api/node/details`. ## Recovery and rollback diff --git a/package.json b/package.json index 445d4ae..3833e36 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "lint:fix": "eslint . --fix", "format": "prettier --check .", "format:fix": "prettier --write .", + "typecheck": "tsc -p tsconfig.json --noEmit", "test:build": "rimraf dist-test && tsc -p tsconfig.test.json", "test": "npm run test:build && npm run test:unit && npm run test:integration", "test:unit": "IPFS_NODE_CONFIG=test node --test \"dist-test/test/*.test.js\"", diff --git a/src/api/file.ts b/src/api/file.ts index a297d12..94a053c 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -96,23 +96,36 @@ router.get('/:cid/status', readLimiter, async (req, res, next) => { }) router.get('/:cid', readLimiter, async (req, res, next) => { + const requestController = new AbortController() + res.once('close', () => { + if (!res.writableEnded) { + requestController.abort(new Error('Download client disconnected')) + } + }) + try { + if (req.headers.range !== undefined) { + res.set('Accept-Ranges', 'none') + return res.status(416).send({ error: 'Range requests are not supported' }) + } + const cid = parseCid(req.params.cid) // Reach the file's holders first. Without this the read only succeeds if a // peer that has the file is already connected, which stops being true as // soon as the network is larger than the connection limit. - await prepareFileRetrieval(cid) + await prepareFileRetrieval(cid, requestController.signal) - const fileStats = await getFileStats(cid) - const stream = downloadFile(cid) + const fileStats = await getFileStats(cid, requestController.signal) + const download = downloadFile(cid, { signal: requestController.signal }) sendDownloadStream( - stream, + download.stream, res, { cid: cid.toString(), fileSize: fileStats.size }, next, - (error) => logger.error(error) + (error) => logger.error(error), + () => download.abort(new Error('Download client disconnected')) ) } catch (error) { next(error) diff --git a/src/api/node.ts b/src/api/node.ts index b11f5bf..5dc014c 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -3,22 +3,39 @@ import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' import { getStorageMetrics } from '../storage/metrics.js' +import { getHealthSnapshot } from '../health/service.js' +import { getHttpMetrics } from '../observability.js' export const publicNodeRouter = Router() const router = Router() publicNodeRouter.get('/health', async (req, res, next) => { try { + res.status(200).send(getHealthSnapshot()) + } catch (err) { + next(err) + } +}) + +/** Legacy PWA/iOS contract. Keep public and free of node identity or operator data. */ +publicNodeRouter.get('/info', async (req, res, next) => { + try { + const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() res.send({ + version: packageJson.version, timestamp: Date.now(), - heliaStatus: helia.libp2p.status + heliaStatus: helia.libp2p.status, + blockstoreSizeMb, + datastoreSizeMb, + availableSizeInMb }) } catch (err) { next(err) } }) -router.get('/info', async (req, res, next) => { +/** Detailed node identity and storage report for authenticated operators. */ +router.get('/details', async (req, res, next) => { try { const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() const storage = getStorageMetrics() @@ -35,7 +52,9 @@ router.get('/info', async (req, res, next) => { pinnedBytes: storage.pinnedBytes, reclaimableBytes: storage.reclaimableBytes, availableBytes: storage.availableBytes, - reservedBytes: storage.reservedBytes + reservedBytes: storage.reservedBytes, + health: getHealthSnapshot(), + http: getHttpMetrics() }) } catch (err) { next(err) diff --git a/src/config.ts b/src/config.ts index 6dcfedd..bc97b82 100644 --- a/src/config.ts +++ b/src/config.ts @@ -53,12 +53,32 @@ export interface ConfigNode { multiAddr: string } +/** Policy used to produce a bounded, network-aware health checkpoint. */ +export interface HealthConfig { + /** Length of one checkpoint round, in milliseconds. */ + checkpointIntervalMs: number + /** Maximum age of the last completed checkpoint before it becomes stale. */ + maxCheckpointAgeMs: number + /** Maximum age of the cached storage scan accepted by a checkpoint. */ + storageMaxAgeMs: number + /** Maximum age of a completed full repair sweep. */ + repairMaxAgeMs: number + /** Allowed clock difference between two attesting peers. */ + clockSkewToleranceMs: number + /** Bound for one peer attestation call. */ + peerAttestationTimeoutMs: number + /** Minimum number of configured remote peers that must attest a round. */ + requiredPeerCount: number +} + export interface Config { /** Known ADAMANT IPFS nodes. Their multiaddrs are also the connection manager allow list. */ nodes: ConfigNode[] /** File storage directory, resolved from the user's home directory. */ storeFolder: string logLevel: LogLevel + /** Enable human-oriented log formatting explicitly for local development. */ + prettyLogs: boolean peerDiscovery: { /** Multiaddrs dialled on startup to join the ADAMANT peer set. */ bootstrap: string[] @@ -94,6 +114,8 @@ export interface Config { storage: StorageConfig /** Cross-node durability policy; see `src/storage/config.ts`. */ replication: ReplicationConfig + /** Network-aware readiness and checkpoint policy. */ + health: HealthConfig } /** @@ -176,6 +198,71 @@ function requireInteger(value: unknown, path: string, min: number): number { return value } +function optionalInteger(value: unknown, path: string, fallback: number, min: number): number { + return value === undefined ? fallback : requireInteger(value, path, min) +} + +/** Resolve health defaults without breaking configuration files from earlier releases. */ +function resolveHealthConfig(value: unknown, nodeCount: number): HealthConfig { + const raw = value === undefined ? {} : requireObject(value, 'health') + const checkpointIntervalMs = optionalInteger( + raw.checkpointIntervalMs, + 'health.checkpointIntervalMs', + 60_000, + 1_000 + ) + const maxCheckpointAgeMs = optionalInteger( + raw.maxCheckpointAgeMs, + 'health.maxCheckpointAgeMs', + checkpointIntervalMs * 3, + checkpointIntervalMs + ) + const storageMaxAgeMs = optionalInteger( + raw.storageMaxAgeMs, + 'health.storageMaxAgeMs', + checkpointIntervalMs * 2, + checkpointIntervalMs + ) + const repairMaxAgeMs = optionalInteger( + raw.repairMaxAgeMs, + 'health.repairMaxAgeMs', + 3_600_000, + checkpointIntervalMs + ) + const clockSkewToleranceMs = optionalInteger( + raw.clockSkewToleranceMs, + 'health.clockSkewToleranceMs', + 10_000, + 0 + ) + const peerAttestationTimeoutMs = optionalInteger( + raw.peerAttestationTimeoutMs, + 'health.peerAttestationTimeoutMs', + 5_000, + 1 + ) + const requiredPeerCount = optionalInteger( + raw.requiredPeerCount, + 'health.requiredPeerCount', + nodeCount > 1 ? 1 : 0, + 0 + ) + + if (requiredPeerCount > Math.max(0, nodeCount - 1)) { + fail('health.requiredPeerCount', 'cannot exceed the number of configured remote peers') + } + + return { + checkpointIntervalMs, + maxCheckpointAgeMs, + storageMaxAgeMs, + repairMaxAgeMs, + clockSkewToleranceMs, + peerAttestationTimeoutMs, + requiredPeerCount + } +} + /** * Validate an untrusted parsed config object and return it as a typed `Config`. * @@ -252,9 +339,11 @@ export function validateConfig(raw: unknown): Config { // files written before this feature keep working. let storage: StorageConfig let replication: ReplicationConfig + let health: HealthConfig try { storage = resolveStorageConfig(root.storage, root.uploadLimitSizeBytes as number) replication = resolveReplicationConfig(root.replication) + health = resolveHealthConfig(root.health, nodes.length) } catch (err) { if (err instanceof ConfigError) { throw err @@ -273,6 +362,7 @@ export function validateConfig(raw: unknown): Config { nodes: nodes.map(({ name, multiAddr }) => ({ name, multiAddr })), storeFolder, logLevel: logLevel as LogLevel, + prettyLogs: root.prettyLogs === true, peerDiscovery: { bootstrap, listen }, serverPort, diskUsageScanPeriod, @@ -286,7 +376,8 @@ export function validateConfig(raw: unknown): Config { adminApiKey: (root.adminApiKey ?? '') as string, enableDebugApi: root.enableDebugApi === true, storage, - replication + replication, + health } } diff --git a/src/health/membership.ts b/src/health/membership.ts new file mode 100644 index 0000000..714211b --- /dev/null +++ b/src/health/membership.ts @@ -0,0 +1,9 @@ +import { createHash } from 'node:crypto' +import { peerIdFromMultiaddr } from '../utils/utils.js' +import type { ConfigNode } from '../config.js' + +/** Stable identifier for the configured peer set used by checkpoint attestations. */ +export function membershipVersion(nodes: ConfigNode[]): string { + const peerIds = nodes.map((node) => peerIdFromMultiaddr(node.multiAddr).toString()).sort() + return createHash('sha256').update(peerIds.join('\n')).digest('hex') +} diff --git a/src/health/persistence.ts b/src/health/persistence.ts new file mode 100644 index 0000000..ac545d6 --- /dev/null +++ b/src/health/persistence.ts @@ -0,0 +1,43 @@ +import { Key } from 'interface-datastore' +import type { Datastore } from 'interface-datastore' +import type { HealthCheckpoint } from './state.js' + +const CHECKPOINT_KEY = new Key('/adm/health/checkpoint') +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +function parseCheckpoint(value: unknown): HealthCheckpoint | null { + const item = value as Partial + + if ( + item === null || + !Number.isSafeInteger(item.height) || + !Number.isSafeInteger(item.completedAt) || + typeof item.membershipVersion !== 'string' || + !Number.isSafeInteger(item.attestedPeers) + ) { + return null + } + + return item as HealthCheckpoint +} + +/** Load the last durable checkpoint, ignoring malformed legacy or partial data. */ +export async function loadHealthCheckpoint(store: Datastore): Promise { + try { + return parseCheckpoint(JSON.parse(decoder.decode(await store.get(CHECKPOINT_KEY))) as unknown) + } catch (err) { + if ((err as { code?: string }).code === 'ERR_NOT_FOUND' || err instanceof SyntaxError) { + return null + } + throw err + } +} + +/** Persist a completed checkpoint atomically through the datastore implementation. */ +export async function saveHealthCheckpoint( + store: Datastore, + checkpoint: HealthCheckpoint +): Promise { + await store.put(CHECKPOINT_KEY, encoder.encode(JSON.stringify(checkpoint))) +} diff --git a/src/health/protocol.ts b/src/health/protocol.ts new file mode 100644 index 0000000..ed56a1a --- /dev/null +++ b/src/health/protocol.ts @@ -0,0 +1,159 @@ +import type { Connection, Stream } from '@libp2p/interface' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { IpfsNode } from '../ipfs-node.js' + +export const HEALTH_PROTOCOL_VERSION = '1.0.0' +export const HEALTH_PROTOCOL = `/adamant/health/${HEALTH_PROTOCOL_VERSION}` + +const MAX_MESSAGE_BYTES = 1024 +const LENGTH_PREFIX_BYTES = 4 + +export interface Attestation { + round: number + timestamp: number + membershipVersion: string +} + +function encode(message: unknown): Uint8Array { + const body = new TextEncoder().encode(JSON.stringify(message)) + const frame = new Uint8Array(LENGTH_PREFIX_BYTES + body.byteLength) + new DataView(frame.buffer).setUint32(0, body.byteLength) + frame.set(body, LENGTH_PREFIX_BYTES) + return frame +} + +function send(stream: Stream, message: unknown): void { + stream.send(encode(message)) +} + +async function read(stream: Stream, signal: AbortSignal): Promise { + const abort = (): void => stream.abort(new Error('Health attestation timed out')) + signal.addEventListener('abort', abort, { once: true }) + + try { + const chunks: Uint8Array[] = [] + let received = 0 + + for await (const chunk of stream) { + const bytes = chunk instanceof Uint8Array ? chunk : chunk.subarray() + chunks.push(bytes) + received += bytes.byteLength + + if (received > MAX_MESSAGE_BYTES + LENGTH_PREFIX_BYTES) { + throw new Error('Health attestation is too large') + } + if (received < LENGTH_PREFIX_BYTES) continue + + const buffer = Buffer.concat(chunks) + const length = buffer.readUInt32BE(0) + if (length > MAX_MESSAGE_BYTES) throw new Error('Health attestation is too large') + if (buffer.byteLength >= LENGTH_PREFIX_BYTES + length) { + return JSON.parse( + buffer.subarray(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + length).toString('utf8') + ) as unknown + } + } + + throw new Error('Health stream ended before a complete message arrived') + } finally { + signal.removeEventListener('abort', abort) + } +} + +function parse(value: unknown): Attestation { + const message = value as Partial + if ( + !Number.isSafeInteger(message.round) || + !Number.isSafeInteger(message.timestamp) || + typeof message.membershipVersion !== 'string' || + message.membershipVersion.length !== 64 + ) { + throw new Error('Invalid health attestation') + } + return message as Attestation +} + +export interface HealthProtocolOptions { + timeoutMs: number + checkpointIntervalMs: number + clockSkewToleranceMs: number + membershipVersion: string + authorizedPeerIds: Set + now?: () => number + onError?: (message: string) => void +} + +/** + * Register the authenticated peer-to-peer checkpoint protocol. + * + * Authorization uses the peer id proven by the libp2p connection; no HTTP + * secret is shared and an unconfigured peer cannot contribute to readiness. + */ +export async function registerHealthProtocol( + node: IpfsNode, + options: HealthProtocolOptions +): Promise { + const now = options.now ?? Date.now + + await node.libp2p.handle(HEALTH_PROTOCOL, (stream, connection: Connection) => { + const handle = async (): Promise => { + if (!options.authorizedPeerIds.has(connection.remotePeer.toString())) { + throw new Error('Health attestation peer is not authorized') + } + + const request = parse(await read(stream, AbortSignal.timeout(options.timeoutMs))) + const timestamp = now() + const localRound = + Math.floor(timestamp / options.checkpointIntervalMs) * options.checkpointIntervalMs + + if ( + request.membershipVersion !== options.membershipVersion || + request.round !== localRound || + Math.abs(timestamp - request.timestamp) > options.clockSkewToleranceMs + ) { + throw new Error('Health attestation does not match the local round') + } + + send(stream, { + round: localRound, + timestamp, + membershipVersion: options.membershipVersion + }) + } + + handle() + .then(async () => stream.close()) + .catch((err: Error) => { + options.onError?.(err.message) + stream.abort(err) + }) + }) +} + +/** Ask one configured peer to attest the same fixed checkpoint round. */ +export async function requestHealthAttestation( + node: IpfsNode, + peer: Multiaddr, + request: Attestation, + options: Pick +): Promise { + const signal = AbortSignal.timeout(options.timeoutMs) + const stream = await node.libp2p.dialProtocol(peer, HEALTH_PROTOCOL, { signal }) + const abort = (): void => stream.abort(new Error('Health attestation timed out')) + signal.addEventListener('abort', abort, { once: true }) + + try { + send(stream, request) + const response = parse(await read(stream, signal)) + if ( + response.round !== request.round || + response.membershipVersion !== request.membershipVersion || + Math.abs(response.timestamp - request.timestamp) > options.clockSkewToleranceMs + ) { + throw new Error('Peer attested a different health round') + } + } finally { + signal.removeEventListener('abort', abort) + await stream.close().catch(() => {}) + } +} diff --git a/src/health/service.ts b/src/health/service.ts new file mode 100644 index 0000000..d9c6b09 --- /dev/null +++ b/src/health/service.ts @@ -0,0 +1,169 @@ +import { config } from '../config.js' +import { helia } from '../helia.js' +import { datastore } from '../store.js' +import { getStorageMetrics } from '../storage/metrics.js' +import { getNodesList } from '../utils/utils.js' +import { logger } from '../utils/logger.js' +import { getRepairHealthEvidence } from '../replication.cron.js' +import { membershipVersion } from './membership.js' +import { loadHealthCheckpoint, saveHealthCheckpoint } from './persistence.js' +import { HEALTH_PROTOCOL, registerHealthProtocol, requestHealthAttestation } from './protocol.js' +import { + checkpointRound, + evaluateHealth, + type HealthCheckpoint, + type HealthSnapshot +} from './state.js' + +const networkMembershipVersion = membershipVersion(config.nodes) +let previous: HealthCheckpoint | null = null +let startupComplete = false +let startupHealthy = false +let running = false +let rerunRequested = false +let timer: NodeJS.Timeout | undefined + +let snapshot: HealthSnapshot = evaluateHealth(config.health, { + now: Date.now(), + heliaReady: false, + startupComplete: false, + startupHealthy: false, + storageUpdatedAt: null, + storageAvailableBytes: 0, + storageReservedBytes: config.storage.diskReserveBytes, + repairRequired: config.replication.enabled && config.replication.repairEnabled, + repairCompletedAt: null, + attestedPeers: 0, + membershipVersion: networkMembershipVersion, + previous: null +}).snapshot + +async function countAttestations(now: number): Promise { + if (config.health.requiredPeerCount === 0) return 0 + + const round = checkpointRound(now, config.health.checkpointIntervalMs) + const peers = getNodesList([helia.libp2p.peerId.toString()]) + const answers = await Promise.all( + peers.map(async (peer) => { + try { + await requestHealthAttestation( + helia, + peer.multiAddr, + { round, timestamp: now, membershipVersion: networkMembershipVersion }, + { + timeoutMs: config.health.peerAttestationTimeoutMs, + clockSkewToleranceMs: config.health.clockSkewToleranceMs + } + ) + return true + } catch (err) { + logger.debug({ peer: peer.name, err }, 'Health attestation failed') + return false + } + }) + ) + return answers.filter(Boolean).length +} + +/** Run one bounded health checkpoint attempt. Overlapping attempts are skipped. */ +export async function runHealthCheckpoint(): Promise { + if (running) { + rerunRequested = true + return snapshot + } + running = true + + try { + const now = Date.now() + const [attestedPeers, repair] = await Promise.all([ + countAttestations(now), + Promise.resolve(getRepairHealthEvidence()) + ]) + const storage = getStorageMetrics() + const evaluated = evaluateHealth(config.health, { + now, + heliaReady: helia.libp2p.status === 'started', + startupComplete, + startupHealthy, + storageUpdatedAt: storage.updatedAt, + storageAvailableBytes: storage.availableBytes, + storageReservedBytes: storage.reservedBytes, + repairRequired: repair.required, + repairCompletedAt: repair.completedAt, + attestedPeers, + membershipVersion: networkMembershipVersion, + previous + }) + + if (evaluated.completed !== undefined) { + await saveHealthCheckpoint(datastore, evaluated.completed) + previous = evaluated.completed + } + snapshot = evaluated.snapshot + + logger.info( + { + event: 'health_checkpoint', + status: snapshot.status, + height: snapshot.height, + checks: snapshot.checks, + attestedPeers + }, + 'Health checkpoint evaluated' + ) + return snapshot + } finally { + running = false + if (rerunRequested) { + rerunRequested = false + scheduleCheckpoint() + } + } +} + +function scheduleCheckpoint(): void { + void runHealthCheckpoint().catch((err: Error) => { + logger.error({ event: 'health_checkpoint_error', err }, 'Health checkpoint failed') + }) +} + +/** Start the health protocol and the fixed-round checkpoint scheduler. */ +export async function startHealthService(): Promise { + previous = await loadHealthCheckpoint(datastore) + if (previous?.membershipVersion !== networkMembershipVersion) previous = null + + const authorizedPeerIds = new Set( + getNodesList([helia.libp2p.peerId.toString()]).map((peer) => peer.peerId.toString()) + ) + await registerHealthProtocol(helia, { + timeoutMs: config.health.peerAttestationTimeoutMs, + checkpointIntervalMs: config.health.checkpointIntervalMs, + clockSkewToleranceMs: config.health.clockSkewToleranceMs, + membershipVersion: networkMembershipVersion, + authorizedPeerIds, + onError: (message) => + logger.warn({ protocol: HEALTH_PROTOCOL, message }, 'Health protocol error') + }) + + timer = setInterval(scheduleCheckpoint, config.health.checkpointIntervalMs) + timer.unref() + scheduleCheckpoint() +} + +/** Mark startup reconciliation complete and immediately re-evaluate readiness. */ +export function setStartupReconciliationResult(healthy: boolean): void { + startupComplete = true + startupHealthy = healthy + scheduleCheckpoint() +} + +/** Stop scheduling new health work during graceful shutdown. */ +export function stopHealthService(): void { + if (timer !== undefined) clearInterval(timer) + timer = undefined +} + +/** Cached, read-only response for the always-200 health endpoint. */ +export function getHealthSnapshot(): HealthSnapshot { + return { ...snapshot, timestamp: Date.now() } +} diff --git a/src/health/state.ts b/src/health/state.ts new file mode 100644 index 0000000..b960331 --- /dev/null +++ b/src/health/state.ts @@ -0,0 +1,114 @@ +import type { HealthConfig } from '../config.js' + +export type HealthStatus = 'starting' | 'ready' | 'stale' | 'degraded' + +export interface HealthCheckpoint { + /** Unix milliseconds at the start of the validated fixed round. */ + height: number + completedAt: number + membershipVersion: string + attestedPeers: number +} + +export interface HealthInputs { + now: number + heliaReady: boolean + startupComplete: boolean + startupHealthy: boolean + storageUpdatedAt: number | null + storageAvailableBytes: number + storageReservedBytes: number + repairRequired: boolean + repairCompletedAt: number | null + attestedPeers: number + membershipVersion: string + previous: HealthCheckpoint | null +} + +export interface HealthSnapshot { + status: HealthStatus + /** Monotonic checkpoint height, never a request timestamp. */ + height: number + timestamp: number + checkpointIntervalMs: number + membershipVersion: string + checks: { + helia: boolean + startupReconciliation: boolean + storageFresh: boolean + storageReserve: boolean + repairFresh: boolean + peerAttestations: boolean + } + peerAttestations: { + required: number + received: number + } +} + +/** Return the fixed checkpoint round containing `time`. */ +export function checkpointRound(time: number, intervalMs: number): number { + return Math.floor(time / intervalMs) * intervalMs +} + +/** + * Evaluate one checkpoint attempt without performing network or datastore I/O. + * + * A failed attempt freezes the previous height. This is the core compatibility + * property used by clients: height only advances after every bounded check for + * the same round succeeds. + */ +export function evaluateHealth( + policy: HealthConfig, + input: HealthInputs +): { snapshot: HealthSnapshot; completed?: HealthCheckpoint } { + const checks = { + helia: input.heliaReady, + startupReconciliation: input.startupComplete && input.startupHealthy, + storageFresh: + input.storageUpdatedAt !== null && + input.now - input.storageUpdatedAt <= policy.storageMaxAgeMs, + storageReserve: input.storageAvailableBytes >= input.storageReservedBytes, + repairFresh: + !input.repairRequired || + (input.repairCompletedAt !== null && + input.now - input.repairCompletedAt <= policy.repairMaxAgeMs), + peerAttestations: input.attestedPeers >= policy.requiredPeerCount + } + const complete = Object.values(checks).every(Boolean) + const previousAge = input.previous === null ? null : input.now - input.previous.completedAt + const status: HealthStatus = complete + ? 'ready' + : !input.startupComplete + ? 'starting' + : previousAge !== null && previousAge > policy.maxCheckpointAgeMs + ? 'stale' + : 'degraded' + const completed = complete + ? { + height: Math.max( + input.previous?.height ?? 0, + checkpointRound(input.now, policy.checkpointIntervalMs) + ), + completedAt: input.now, + membershipVersion: input.membershipVersion, + attestedPeers: input.attestedPeers + } + : undefined + + return { + snapshot: { + status, + height: completed?.height ?? input.previous?.height ?? 0, + timestamp: input.now, + checkpointIntervalMs: policy.checkpointIntervalMs, + membershipVersion: input.membershipVersion, + checks, + peerAttestations: { + required: policy.requiredPeerCount, + received: input.attestedPeers + } + }, + completed + } +} diff --git a/src/index.ts b/src/index.ts index 1a51d14..db55575 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' import { fileRegistry } from './storage/state.js' import { garbageCollectionCron, admissionRecoveryCron } from './gc.cron.js' -import { replicationRepairCron } from './replication.cron.js' +import { initializeReplicationRepairState, replicationRepairCron } from './replication.cron.js' import cors from 'cors' import * as routers from './api/index.js' import { errorHandler, notFoundHandler } from './middleware/errorHandler.js' @@ -18,6 +18,12 @@ import { mountApiRoutes } from './security/accessPolicy.js' import { createApiKeyAuth } from './security/apiKey.js' import { createCorsOriginDelegate } from './security/cors.js' import { parseTrustProxy } from './security/trustProxy.js' +import { + setStartupReconciliationResult, + startHealthService, + stopHealthService +} from './health/service.js' +import { collectHttpMetrics } from './observability.js' logger.info(`Using config file: ${CONFIG_FILE_NAME}`) @@ -41,6 +47,8 @@ const legacyPins = await snapshotPins(helia) await registerReplicationProtocol(helia, createReplicationHandlers(), { requestTimeoutMs: config.replication.requestTimeoutMs }) +await initializeReplicationRepairState() +await startHealthService() /** The scheduled work that reads the file registry and acts on what it finds. */ function startLifecycleJobs(): void { @@ -100,8 +108,12 @@ void Promise.all([ if (admissions.recovered > 0) { logger.info(`Admission recovery: cleared ${admissions.recovered} interrupted uploads`) } + setStartupReconciliationResult(backfill.errors.length === 0 && admissions.errors.length === 0) + }) + .catch((err: Error) => { + logger.error(`Startup storage reconciliation failed: ${err.message}`) + setStartupReconciliationResult(false) }) - .catch((err: Error) => logger.error(`Startup storage reconciliation failed: ${err.message}`)) // Even a failed reconciliation must not leave the node without a collector: // it reclaims nothing that is unregistered, so an incomplete registry makes // it do less, never more. @@ -121,6 +133,7 @@ if (trustProxy === false) { } app.use(httpLogger) +app.use(collectHttpMetrics) app.use( cors({ @@ -158,6 +171,7 @@ function shutdown(signal: string): void { garbageCollectionCron.stop() admissionRecoveryCron.stop() replicationRepairCron.stop() + stopHealthService() const forceTimer = setTimeout(() => { logger.warn('Shutdown timed out') diff --git a/src/observability.ts b/src/observability.ts new file mode 100644 index 0000000..0de9a19 --- /dev/null +++ b/src/observability.ts @@ -0,0 +1,50 @@ +import type { RequestHandler } from 'express' + +interface HttpMetrics { + requests: number + responses: Record<'2xx' | '3xx' | '4xx' | '5xx', number> + inFlight: number + totalResponseTimeMs: number + maxResponseTimeMs: number +} + +const metrics: HttpMetrics = { + requests: 0, + responses: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0 }, + inFlight: 0, + totalResponseTimeMs: 0, + maxResponseTimeMs: 0 +} + +/** Bounded HTTP counters with no path, CID, IP, or user-controlled labels. */ +export const collectHttpMetrics: RequestHandler = (req, res, next) => { + void req + const started = performance.now() + let settled = false + metrics.requests += 1 + metrics.inFlight += 1 + + const settle = (): void => { + if (settled) return + settled = true + metrics.inFlight -= 1 + const elapsed = performance.now() - started + metrics.totalResponseTimeMs += elapsed + metrics.maxResponseTimeMs = Math.max(metrics.maxResponseTimeMs, elapsed) + const group = `${Math.floor(res.statusCode / 100)}xx` as keyof HttpMetrics['responses'] + if (group in metrics.responses) metrics.responses[group] += 1 + } + res.once('finish', settle) + res.once('close', settle) + next() +} + +/** Snapshot suitable for the authenticated operator details endpoint. */ +export function getHttpMetrics(): HttpMetrics & { averageResponseTimeMs: number } { + return { + ...metrics, + responses: { ...metrics.responses }, + averageResponseTimeMs: + metrics.requests === 0 ? 0 : metrics.totalResponseTimeMs / metrics.requests + } +} diff --git a/src/replication.cron.ts b/src/replication.cron.ts index a0ce706..a973b79 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -3,9 +3,97 @@ import { config } from './config.js' import { REPLICATION_PROTOCOL } from './storage/replicationProtocol.js' import { repairReplication, type RepairReport } from './storage/service.js' import { logger } from './utils/logger.js' +import { datastore } from './store.js' +import { Key } from 'interface-datastore' +import { createHash } from 'node:crypto' +import { membershipVersion } from './health/membership.js' let running = false let lastReport: RepairReport | null = null +const REPAIR_STATE_KEY = new Key('/adm/health/repair-cycle') +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +interface RepairCycleEvidence { + membershipVersion: string + policyVersion: string + cursor?: string + startedAt: number + checked: number + underReplicated: number + repaired: number + stillMissing: number + unrecoverable: number + lastCompletedAt: number | null + lastCompletedSuccessfully: boolean +} + +let evidence: RepairCycleEvidence | null = null + +function currentPolicyVersion(): string { + return createHash('sha256').update(JSON.stringify(config.replication.placement)).digest('hex') +} + +function emptyEvidence(now = Date.now()): RepairCycleEvidence { + return { + membershipVersion: membershipVersion(config.nodes), + policyVersion: currentPolicyVersion(), + startedAt: now, + checked: 0, + underReplicated: 0, + repaired: 0, + stillMissing: 0, + unrecoverable: 0, + lastCompletedAt: config.replication.enabled && config.replication.repairEnabled ? null : now, + lastCompletedSuccessfully: !config.replication.enabled || !config.replication.repairEnabled + } +} + +async function loadEvidence(): Promise { + if (evidence !== null) return evidence + + try { + const stored = await datastore.get(REPAIR_STATE_KEY) + let parsed: Partial + try { + parsed = JSON.parse(decoder.decode(stored)) as Partial + } catch { + logger.warn('Ignoring malformed persisted repair-cycle evidence') + parsed = {} + } + evidence = + typeof parsed.membershipVersion === 'string' && + typeof parsed.policyVersion === 'string' && + (parsed.cursor === undefined || typeof parsed.cursor === 'string') && + Number.isSafeInteger(parsed.startedAt) && + Number.isSafeInteger(parsed.checked) && + Number.isSafeInteger(parsed.underReplicated) && + Number.isSafeInteger(parsed.repaired) && + Number.isSafeInteger(parsed.stillMissing) && + Number.isSafeInteger(parsed.unrecoverable) && + (parsed.lastCompletedAt === null || Number.isSafeInteger(parsed.lastCompletedAt)) && + typeof parsed.lastCompletedSuccessfully === 'boolean' + ? (parsed as RepairCycleEvidence) + : emptyEvidence() + } catch (err) { + if ((err as { code?: string }).code !== 'ERR_NOT_FOUND') throw err + evidence = emptyEvidence() + } + + if ( + evidence.membershipVersion !== membershipVersion(config.nodes) || + evidence.policyVersion !== currentPolicyVersion() + ) { + evidence = emptyEvidence() + } + + return evidence +} + +async function saveEvidence(value: RepairCycleEvidence): Promise { + await datastore.put(REPAIR_STATE_KEY, encoder.encode(JSON.stringify(value))) + evidence = value +} export class ReplicationRepairBusyError extends Error { constructor() { @@ -25,7 +113,34 @@ export async function repairUnderReplicatedFiles(): Promise { running = true try { - lastReport = await repairReplication() + const current = await loadEvidence() + lastReport = await repairReplication({ cursor: current.cursor }) + + const aggregate = { + checked: current.checked + lastReport.checked, + underReplicated: current.underReplicated + lastReport.underReplicated, + repaired: current.repaired + lastReport.repaired.length, + stillMissing: current.stillMissing + lastReport.stillMissing.length, + unrecoverable: current.unrecoverable + lastReport.unrecoverable.length + } + const completedAt = lastReport.cycleCompleted ? Date.now() : current.lastCompletedAt + const completedSuccessfully = lastReport.cycleCompleted + ? aggregate.stillMissing === 0 && aggregate.unrecoverable === 0 + : current.lastCompletedSuccessfully + + await saveEvidence( + lastReport.cycleCompleted + ? { + ...emptyEvidence(), + lastCompletedAt: completedAt, + lastCompletedSuccessfully: completedSuccessfully + } + : { + ...current, + cursor: lastReport.nextCursor, + ...aggregate + } + ) if (lastReport.underReplicated > 0) { logger.info( @@ -61,14 +176,47 @@ export function getReplicationState() { repairEnabled: config.replication.repairEnabled, repairSchedule: config.replication.repairSchedule, running, + cycle: evidence + ? { + startedAt: evidence.startedAt, + checked: evidence.checked, + underReplicated: evidence.underReplicated, + repaired: evidence.repaired, + stillMissing: evidence.stillMissing, + unrecoverable: evidence.unrecoverable, + lastCompletedAt: evidence.lastCompletedAt, + lastCompletedSuccessfully: evidence.lastCompletedSuccessfully + } + : null, lastRun: lastReport ? { checked: lastReport.checked, underReplicated: lastReport.underReplicated, repaired: lastReport.repaired.length, stillMissing: lastReport.stillMissing.length, - rescued: lastReport.rescued.length + rescued: lastReport.rescued.length, + unrecoverable: lastReport.unrecoverable.length } : null } } + +/** Restore repair evidence before the first health checkpoint is evaluated. */ +export async function initializeReplicationRepairState(): Promise { + await loadEvidence() +} + +/** Last successful complete repair cycle used by the health checkpoint. */ +export function getRepairHealthEvidence(): { + required: boolean + completedAt: number | null +} { + const required = config.replication.enabled && config.replication.repairEnabled + return { + required, + completedAt: + !required || evidence?.lastCompletedSuccessfully === true + ? (evidence?.lastCompletedAt ?? Date.now()) + : null + } +} diff --git a/src/storage/retrieval.ts b/src/storage/retrieval.ts index c24a289..00624d6 100644 --- a/src/storage/retrieval.ts +++ b/src/storage/retrieval.ts @@ -76,7 +76,8 @@ export function retrievalTargets( export async function connectToHolders( node: IpfsNode, targets: ReplicationPeer[], - timeoutMs: number = HOLDER_DIAL_TIMEOUT_MS + timeoutMs: number = HOLDER_DIAL_TIMEOUT_MS, + externalSignal?: AbortSignal ): Promise { const connected = new Set(node.libp2p.getPeers().map((peer) => peer.toString())) const missing = targets.filter((peer) => !connected.has(peer.peerId)) @@ -86,9 +87,13 @@ export async function connectToHolders( } const results = await Promise.allSettled( - missing.map(async (peer) => - node.libp2p.dial(peer.multiAddr, { signal: AbortSignal.timeout(timeoutMs) }) - ) + missing.map(async (peer) => { + const timeoutSignal = AbortSignal.timeout(timeoutMs) + const signal = externalSignal + ? AbortSignal.any([timeoutSignal, externalSignal]) + : timeoutSignal + return node.libp2p.dial(peer.multiAddr, { signal }) + }) ) return results.filter((result) => result.status === 'fulfilled').length @@ -104,11 +109,12 @@ export async function prepareRetrieval( node: IpfsNode, cid: CID, targets: () => ReplicationPeer[], - timeoutMs: number = HOLDER_DIAL_TIMEOUT_MS + timeoutMs: number = HOLDER_DIAL_TIMEOUT_MS, + signal?: AbortSignal ): Promise { if (await node.blockstore.has(cid)) { return } - await connectToHolders(node, targets(), timeoutMs) + await connectToHolders(node, targets(), timeoutMs, signal) } diff --git a/src/storage/service.ts b/src/storage/service.ts index c714101..d3c77f7 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -56,7 +56,7 @@ import { } from './meter.js' import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter, storageOperationLock } from './state.js' -import { nextSweepBatch } from './sweep.js' +import { nextSweepBatch, sweepBatch } from './sweep.js' import { claimRepairRecord, releaseRepairRecord } from './repairClaim.js' const callOptions = (): ReplicationCallOptions => ({ @@ -282,9 +282,13 @@ export async function replicateFile(cid: string): Promise { * not one of its holders. Placement tells it exactly which nodes to reach, so * the read does not depend on a holder happening to be connected already. */ -export async function prepareFileRetrieval(cid: CID): Promise { - await prepareRetrieval(helia, cid, () => - retrievalTargets(cid.toString(), config.replication, selfPeerId(), getReplicationPeers()) +export async function prepareFileRetrieval(cid: CID, signal?: AbortSignal): Promise { + await prepareRetrieval( + helia, + cid, + () => retrievalTargets(cid.toString(), config.replication, selfPeerId(), getReplicationPeers()), + undefined, + signal ) } @@ -794,6 +798,12 @@ export interface RepairReport { stillMissing: string[] /** Files nobody was found holding, whose local blocks were pinned again. */ rescued: string[] + /** Files no designated peer holds and whose local DAG is no longer complete. */ + unrecoverable: string[] + /** This pass reached the end of a complete bounded repair cycle. */ + cycleCompleted: boolean + /** Durable resume cursor for the next pass. */ + nextCursor?: string } /** Ask the designated peers which durable copies exist right now. */ @@ -826,23 +836,16 @@ async function liveHolderNames(holders: ReplicationPeer[], cid: string): Promise async function rescueOrphanedFiles( records: FileRecord[], peers: ReplicationPeer[] -): Promise { +): Promise<{ rescued: string[]; unrecoverable: string[] }> { const self = selfPeerId() const rescued: string[] = [] + const unrecoverable: string[] = [] const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) - const candidates = nextSweepBatch( - 'rescue', - records.filter((record) => record.state === 'confirmed' && !record.heldLocally) - ) + const candidates = records.filter((record) => record.state === 'confirmed' && !record.heldLocally) for (const record of candidates) { const cid = CID.parse(record.cid) - - if (!(await helia.blockstore.has(cid))) { - continue - } - const holders = storageTargets(placementFor(record.cid, record.createdAt, peers), self) .map((peerId) => byPeerId.get(peerId)) .filter((peer): peer is ReplicationPeer => peer !== undefined) @@ -861,6 +864,12 @@ async function rescueOrphanedFiles( continue } + if (!(await helia.blockstore.has(cid))) { + unrecoverable.push(record.cid) + logger.error(`No configured node reports holding ${record.cid}`) + continue + } + try { const kept = await storageOperationLock.withShared(async () => { // Only worth pinning if the whole DAG is still here; an offline stat says @@ -917,7 +926,7 @@ async function rescueOrphanedFiles( } } - return rescued + return { rescued, unrecoverable } } /** @@ -927,13 +936,15 @@ async function rescueOrphanedFiles( * may disappear by policy, and a file this node released is another node's * responsibility. */ -export async function repairReplication(): Promise { +export async function repairReplication(options: { cursor?: string } = {}): Promise { const report: RepairReport = { checked: 0, underReplicated: 0, repaired: [], stillMissing: [], - rescued: [] + rescued: [], + unrecoverable: [], + cycleCompleted: true } if (!config.replication.enabled) { @@ -943,13 +954,23 @@ export async function repairReplication(): Promise { const peers = getReplicationPeers() const self = selfPeerId() const records = await fileRegistry.all() - const candidates = nextSweepBatch('repair', records.filter(isSettledHeldFile)) + const selected = sweepBatch( + 'repair', + records.filter((record) => record.state === 'confirmed' && !isLifecycleBusy(record)), + options.cursor + ) + const candidates = selected.records + report.cycleCompleted = selected.cycleCompleted + report.nextCursor = selected.nextCursor report.checked = candidates.length const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) for (const record of candidates) { + if (!isSettledHeldFile(record)) { + continue + } if (!(await claimRepairRecord(fileRegistry, record))) { continue } @@ -988,7 +1009,9 @@ export async function repairReplication(): Promise { } } - report.rescued = await rescueOrphanedFiles(records, peers) + const rescue = await rescueOrphanedFiles(candidates, peers) + report.rescued = rescue.rescued + report.unrecoverable = rescue.unrecoverable return report } diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index c4b0065..cdd7ff1 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -12,6 +12,53 @@ export type SweepName = keyof typeof SWEEP_BATCHES /** Where each sweep stopped last time, so the next pass continues from there. */ const cursors = new Map() +export interface SweepBatch { + records: T[] + /** True when this batch reached the end of one complete sorted sweep. */ + cycleCompleted: boolean + /** Cursor to persist for the next pass; absent after a completed cycle. */ + nextCursor?: string +} + +/** + * Select one non-wrapping batch and expose its cycle boundary. + * + * A persisted cursor may be supplied after restart. A batch never mixes the + * tail of one cycle with the head of the next, so completing a batch proves + * that every candidate present in that ordered cycle was visited. + */ +export function sweepBatch( + sweep: SweepName, + records: T[], + cursor?: string +): SweepBatch { + const size = SWEEP_BATCHES[sweep] + const ordered = [...records].sort((left, right) => left.cid.localeCompare(right.cid)) + let start = 0 + + if (cursor !== undefined) { + const exact = ordered.findIndex((item) => item.cid === cursor) + if (exact >= 0) { + start = exact + 1 + } else { + const next = ordered.findIndex((item) => item.cid > cursor) + start = next >= 0 ? next : ordered.length + } + } + + if (start >= ordered.length) { + return { records: [], cycleCompleted: true } + } + + const batch = ordered.slice(start, start + size) + const cycleCompleted = start + batch.length >= ordered.length + return { + records: batch, + cycleCompleted, + nextCursor: cycleCompleted ? undefined : batch.at(-1)?.cid + } +} + /** * Take the next batch of a periodic sweep, continuing where it left off. * @@ -32,49 +79,22 @@ export function nextSweepBatch( records: T[], options: { advance?: boolean } = {} ): T[] { - const size = SWEEP_BATCHES[sweep] const advance = options.advance !== false - const ordered = [...records].sort((left, right) => { - if (left.cid === right.cid) { - return 0 - } - - return left.cid < right.cid ? -1 : 1 - }) - - if (ordered.length <= size) { - if (advance) { - cursors.delete(sweep) - } + let selected = sweepBatch(sweep, records, cursors.get(sweep)) - return ordered + // The prior cursor can sort after every current candidate when records were + // deleted or replaced. Start a new cycle instead of returning an empty pass. + if (selected.records.length === 0 && records.length > 0) { + selected = sweepBatch(sweep, records) } - const previous = cursors.get(sweep) - let start = 0 - - if (previous !== undefined) { - const exact = ordered.findIndex((item) => item.cid === previous) - if (exact >= 0) { - start = exact + 1 + if (advance) { + if (selected.nextCursor === undefined) { + cursors.delete(sweep) } else { - // The cursor record left the candidate set (the usual success path for - // demote/rescue). Resume at the first CID strictly greater so the pass - // does not rewind to the head. - const next = ordered.findIndex((item) => item.cid > previous) - start = next >= 0 ? next : 0 + cursors.set(sweep, selected.nextCursor) } } - if (start >= ordered.length) { - start = 0 - } - - const batch = [...ordered.slice(start), ...ordered.slice(0, start)].slice(0, size) - - if (advance) { - cursors.set(sweep, batch[batch.length - 1].cid) - } - - return batch + return selected.records } diff --git a/src/utils/downloadResponse.ts b/src/utils/downloadResponse.ts index cc73fe3..ebfbbfa 100644 --- a/src/utils/downloadResponse.ts +++ b/src/utils/downloadResponse.ts @@ -15,14 +15,17 @@ export type DownloadMetadata = { * @param metadata CID and expected byte length * @param next Express error callback for failures before streaming starts * @param onLateError logger callback for failures after response bytes start + * @param onDisconnect cancels the underlying retrieval when the client leaves */ export function sendDownloadStream( stream: Readable, res: Response, metadata: DownloadMetadata, next: NextFunction, - onLateError: (error: Error) => void + onLateError: (error: Error) => void, + onDisconnect: () => void = () => undefined ): void { + let clientDisconnected = false const setDownloadHeaders = (): void => { if (res.headersSent) { return @@ -32,12 +35,18 @@ export function sendDownloadStream( res.set('Content-Length', metadata.fileSize.toString()) res.set('Content-Disposition', `attachment; filename="${metadata.cid}"`) res.set('X-Content-Type-Options', 'nosniff') + res.set('ETag', `"${metadata.cid}"`) + res.set('Cache-Control', 'public, max-age=31536000, immutable') + res.set('Accept-Ranges', 'none') } stream.once('data', setDownloadHeaders) stream.once('end', setDownloadHeaders) stream.once('error', (error: Error) => { stream.unpipe(res) + if (clientDisconnected) { + return + } if (res.headersSent) { onLateError(error) res.destroy(error) @@ -46,5 +55,12 @@ export function sendDownloadStream( next(error) }) + res.once('close', () => { + if (res.writableEnded) return + clientDisconnected = true + onDisconnect() + stream.destroy(new Error('Download client disconnected')) + }) + stream.pipe(res) } diff --git a/src/utils/file.ts b/src/utils/file.ts index 31770c8..e400a48 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -1,15 +1,15 @@ import { CID } from 'multiformats/cid' -import { Readable } from 'node:stream' import { clearTimeout } from 'node:timers' import { config } from '../config.js' import { ifs } from '../helia.js' import { FileNotFoundError } from './fileErrors.js' +import { createTimedReadable, type TimedReadable } from './timedStream.js' /** * Return file statistics by CID. * Throws a timeout error if the file is not found. */ -export async function getFileStats(cid: CID) { +export async function getFileStats(cid: CID, externalSignal?: AbortSignal) { let timeout: NodeJS.Timeout | undefined try { const abortController = new AbortController() @@ -17,7 +17,10 @@ export async function getFileStats(cid: CID) { abortController.abort(new Error('Cannot find requested CID. Request timed out.')) }, config.findFileTimeout) - const stats = await ifs.stat(cid, { signal: abortController.signal }) + const signal = externalSignal + ? AbortSignal.any([abortController.signal, externalSignal]) + : abortController.signal + const stats = await ifs.stat(cid, { signal }) return stats } catch { throw new FileNotFoundError('Cannot find requested CID. Request timed out.') @@ -32,35 +35,14 @@ export async function getFileStats(cid: CID) { * Return a file stream by CID. * Throws a timeout error if the file is not found. */ -export function downloadFile(cid: CID) { - const abortController = new AbortController() - - let aborted = false - const abort = () => { - if (aborted) return - - aborted = true - abortController.abort(new FileNotFoundError('Unable to retrieve the file. Request timed out.')) - } - const abortTimer = setTimeout(abort, config.findFileTimeout) - - const stream = Readable.from( - ifs.cat(cid, { - signal: abortController.signal - }) +export function downloadFile( + cid: CID, + options: { signal?: AbortSignal; timeoutMs?: number } = {} +): TimedReadable { + return createTimedReadable( + (signal) => ifs.cat(cid, { signal }), + options.timeoutMs ?? config.findFileTimeout, + () => new FileNotFoundError('Unable to retrieve the file. Request timed out.'), + options.signal ) - stream.on('data', () => { - clearTimeout(abortTimer) - }) - stream.on('end', () => { - clearTimeout(abortTimer) - }) - stream.on('error', () => { - clearTimeout(abortTimer) - }) - stream.on('close', () => { - clearTimeout(abortTimer) - }) - - return stream } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 7ef664e..067da7f 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -5,19 +5,21 @@ import { config } from '../config.js' /** * Application logger. * - * `pino-pretty` is loaded as a transport at runtime, so it is a runtime - * dependency rather than a development one. + * Production output is newline-delimited JSON. Pretty output is opt-in because + * formatting destroys fields that log collectors use for querying and alerts. */ export const logger = pino({ level: config.logLevel, - transport: { - target: 'pino-pretty', - options: { - colorize: true, - translateTime: 'dd.mm.yy HH:MM:ss Z', - ignore: 'pid,hostname' - } - } + transport: config.prettyLogs + ? { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'dd.mm.yy HH:MM:ss Z', + ignore: 'pid,hostname' + } + } + : undefined }) /** diff --git a/src/utils/timedStream.ts b/src/utils/timedStream.ts new file mode 100644 index 0000000..27bfdfb --- /dev/null +++ b/src/utils/timedStream.ts @@ -0,0 +1,48 @@ +import { Readable } from 'node:stream' + +export interface TimedReadable { + stream: Readable + abort: (reason?: Error) => void +} + +/** + * Create a readable whose deadline covers the complete iteration. + * + * The timer is cleared only when the stream ends, errors, or closes. Receiving + * a first chunk is deliberately not success: a peer may stall mid-transfer. + */ +export function createTimedReadable( + source: (signal: AbortSignal) => AsyncIterable, + timeoutMs: number, + timeoutError: () => Error, + externalSignal?: AbortSignal +): TimedReadable { + const controller = new AbortController() + let aborted = false + const abort = (reason = timeoutError()): void => { + if (aborted) return + aborted = true + controller.abort(reason) + } + const externalAbort = (): void => + abort( + externalSignal?.reason instanceof Error + ? externalSignal.reason + : new Error('Stream request was cancelled') + ) + externalSignal?.addEventListener('abort', externalAbort, { once: true }) + const timer = setTimeout(abort, timeoutMs) + const guardedSource = async function* (): AsyncGenerator { + if (controller.signal.aborted) throw controller.signal.reason + yield* source(controller.signal) + } + const stream = Readable.from(guardedSource()) + const cleanup = (): void => { + clearTimeout(timer) + externalSignal?.removeEventListener('abort', externalAbort) + } + stream.once('end', cleanup) + stream.once('error', cleanup) + stream.once('close', cleanup) + return { stream, abort } +} diff --git a/test/config.test.ts b/test/config.test.ts index 0c1185d..c912827 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -49,6 +49,9 @@ describe('validateConfig', () => { assert.deepEqual(parsed.peerDiscovery.listen, ['/ip4/0.0.0.0/tcp/4001']) assert.deepEqual(parsed.cors.allowedOrigins, ['https://adm.im']) assert.equal(parsed.enableDebugApi, false) + assert.equal(parsed.prettyLogs, false) + assert.equal(parsed.health.checkpointIntervalMs, 60_000) + assert.equal(parsed.health.requiredPeerCount, 0) }) it('ignores unknown keys so deployments can carry extras', () => { @@ -166,6 +169,17 @@ describe('validateConfig', () => { /requireQuorumOnUpload/ ) }) + + it('validates health timing and peer coverage', () => { + assert.throws( + () => validateConfig({ ...validRaw(), health: { checkpointIntervalMs: 999 } }), + /health\.checkpointIntervalMs/ + ) + assert.throws( + () => validateConfig({ ...validRaw(), health: { requiredPeerCount: 1 } }), + /health\.requiredPeerCount/ + ) + }) }) describe('the config loaded at startup', () => { diff --git a/test/downloadResponse.test.ts b/test/downloadResponse.test.ts index 6ed9f84..a7d2020 100644 --- a/test/downloadResponse.test.ts +++ b/test/downloadResponse.test.ts @@ -71,6 +71,9 @@ describe('download response streaming', () => { assert.equal(response.status, 200) assert.equal(response.headers.get('content-type'), 'application/octet-stream') assert.equal(response.headers.get('content-disposition'), 'attachment; filename="bafytest"') + assert.equal(response.headers.get('etag'), '"bafytest"') + assert.equal(response.headers.get('accept-ranges'), 'none') + assert.match(response.headers.get('cache-control') ?? '', /immutable/) await assert.rejects(response.arrayBuffer()) assert.equal(lateErrors, 1) } finally { diff --git a/test/healthState.test.ts b/test/healthState.test.ts new file mode 100644 index 0000000..680ae95 --- /dev/null +++ b/test/healthState.test.ts @@ -0,0 +1,80 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import type { HealthConfig } from '../src/config.js' +import { checkpointRound, evaluateHealth } from '../src/health/state.js' + +const policy: HealthConfig = { + checkpointIntervalMs: 1_000, + maxCheckpointAgeMs: 3_000, + storageMaxAgeMs: 2_000, + repairMaxAgeMs: 5_000, + clockSkewToleranceMs: 100, + peerAttestationTimeoutMs: 100, + requiredPeerCount: 1 +} + +const healthy = (now: number) => ({ + now, + heliaReady: true, + startupComplete: true, + startupHealthy: true, + storageUpdatedAt: now, + storageAvailableBytes: 2_000, + storageReservedBytes: 1_000, + repairRequired: true, + repairCompletedAt: now, + attestedPeers: 1, + membershipVersion: 'a'.repeat(64), + previous: null +}) + +describe('health checkpoint state', () => { + it('uses a fixed Unix-millisecond round rather than the request timestamp', () => { + assert.equal(checkpointRound(12_345, 1_000), 12_000) + const result = evaluateHealth(policy, healthy(12_345)) + + assert.equal(result.snapshot.status, 'ready') + assert.equal(result.snapshot.height, 12_000) + assert.equal(result.snapshot.timestamp, 12_345) + }) + + it('freezes height when peer coverage is lost', () => { + const prior = evaluateHealth(policy, healthy(12_345)).completed! + const result = evaluateHealth(policy, { + ...healthy(13_456), + attestedPeers: 0, + previous: prior + }) + + assert.equal(result.snapshot.status, 'degraded') + assert.equal(result.snapshot.height, 12_000) + assert.equal(result.completed, undefined) + }) + + it('reports stale after the last valid checkpoint exceeds its age bound', () => { + const prior = evaluateHealth(policy, healthy(12_345)).completed! + const result = evaluateHealth(policy, { + ...healthy(16_000), + storageUpdatedAt: 12_345, + previous: prior + }) + + assert.equal(result.snapshot.status, 'stale') + assert.equal(result.snapshot.height, prior.height) + }) + + it('distinguishes incomplete startup from a failed reconciliation', () => { + const starting = evaluateHealth(policy, { + ...healthy(12_345), + startupComplete: false, + startupHealthy: false + }) + const failed = evaluateHealth(policy, { + ...healthy(12_345), + startupHealthy: false + }) + + assert.equal(starting.snapshot.status, 'starting') + assert.equal(failed.snapshot.status, 'degraded') + }) +}) diff --git a/test/integration/replicationProtocol.test.ts b/test/integration/replicationProtocol.test.ts index 94a28c1..97be2fa 100644 --- a/test/integration/replicationProtocol.test.ts +++ b/test/integration/replicationProtocol.test.ts @@ -20,6 +20,7 @@ import { type ReplicationHandlers } from '../../src/storage/replicationProtocol.js' import { deterministicBytes } from '../fixtures.js' +import { registerHealthProtocol, requestHealthAttestation } from '../../src/health/protocol.js' /** Only loopback, and port 0 so the OS picks a free port. */ const LISTEN = ['/ip4/127.0.0.1/tcp/0'] @@ -60,6 +61,9 @@ let refusals: string[] let hasRoom = true let committed: string[] let aborted: string[] +let healthAuthorized: Set +const HEALTH_MEMBERSHIP = 'a'.repeat(64) +const HEALTH_NOW = 1_720_614_998_797 before(async () => { sender = await startNode() @@ -71,6 +75,7 @@ before(async () => { refusals = [] committed = [] aborted = [] + healthAuthorized = new Set([sender.node.libp2p.peerId.toString()]) const handlers: ReplicationHandlers = { isAuthorized: (peerId) => authorized.has(peerId), @@ -104,11 +109,69 @@ before(async () => { } await registerReplicationProtocol(holder.node, handlers) + await registerHealthProtocol(holder.node, { + timeoutMs: 1_000, + checkpointIntervalMs: 60_000, + clockSkewToleranceMs: 10_000, + membershipVersion: HEALTH_MEMBERSHIP, + authorizedPeerIds: healthAuthorized, + now: () => HEALTH_NOW + }) // The holder pulls blocks over this connection, so it has to exist first await sender.node.libp2p.dial(holder.node.libp2p.getMultiaddrs()) }) +describe('health attestations over libp2p', () => { + it('accepts the same fixed round from an authorized configured peer', async () => { + await requestHealthAttestation( + sender.node, + holder.node.libp2p.getMultiaddrs()[0], + { + round: Math.floor(HEALTH_NOW / 60_000) * 60_000, + timestamp: HEALTH_NOW, + membershipVersion: HEALTH_MEMBERSHIP + }, + { timeoutMs: 1_000, clockSkewToleranceMs: 10_000 } + ) + }) + + it('rejects a different membership version', async () => { + await assert.rejects( + requestHealthAttestation( + sender.node, + holder.node.libp2p.getMultiaddrs()[0], + { + round: Math.floor(HEALTH_NOW / 60_000) * 60_000, + timestamp: HEALTH_NOW, + membershipVersion: 'b'.repeat(64) + }, + { timeoutMs: 1_000, clockSkewToleranceMs: 10_000 } + ) + ) + }) + + it('rejects a peer outside the configured membership', async () => { + healthAuthorized.clear() + try { + await assert.rejects( + requestHealthAttestation( + sender.node, + holder.node.libp2p.getMultiaddrs()[0], + { + round: Math.floor(HEALTH_NOW / 60_000) * 60_000, + timestamp: HEALTH_NOW, + membershipVersion: HEALTH_MEMBERSHIP + }, + { timeoutMs: 1_000, clockSkewToleranceMs: 10_000 } + ) + ) + } finally { + healthAuthorized.add(sender.node.libp2p.peerId.toString()) + } + }) +}) + after(async () => { await stopNode(sender) await stopNode(holder) diff --git a/test/security.test.ts b/test/security.test.ts index 91921e6..6344d50 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -215,9 +215,11 @@ describe('route access policy', () => { before(async () => { const file = Router().get('/test', (req, res) => res.send({ public: true })) const fileAdminRouter = Router().post('/test/confirm', (req, res) => res.send({ admin: true })) - const publicNodeRouter = Router().get('/health', (req, res) => res.send({ public: true })) + const publicNodeRouter = Router() + .get('/health', (req, res) => res.send({ public: true })) + .get('/info', (req, res) => res.send({ legacy: true })) const node = Router() - .get('/info', (req, res) => res.send({ admin: true })) + .get('/details', (req, res) => res.send({ admin: true })) .get('/future', (req, res) => res.send({ admin: true })) const helia = Router().get('/test', (req, res) => res.send({ admin: true })) const libp2p = Router().get('/test', (req, res) => res.send({ admin: true })) @@ -251,10 +253,23 @@ describe('route access policy', () => { it('keeps health, file transfer, and the storage report public', async () => { assert.equal((await fetch(`${serverUrl}/api/node/health`)).status, 200) + assert.equal((await fetch(`${serverUrl}/api/node/info`)).status, 200) assert.equal((await fetch(`${serverUrl}/api/file/test`)).status, 200) assert.equal((await fetch(`${serverUrl}/api/storage/metrics`)).status, 200) }) + it('keeps detailed node identity behind the administrative key', async () => { + assert.equal((await fetch(`${serverUrl}/api/node/details`)).status, 401) + assert.equal( + ( + await fetch(`${serverUrl}/api/node/details`, { + headers: { 'x-api-key': key } + }) + ).status, + 200 + ) + }) + it('protects the routes that make content durable or reclaim it', async () => { for (const path of ['/api/file/test/confirm', '/api/storage/gc']) { assert.equal((await fetch(`${serverUrl}${path}`, { method: 'POST' })).status, 401) diff --git a/test/sweepBatch.test.ts b/test/sweepBatch.test.ts index 5df81bd..344d564 100644 --- a/test/sweepBatch.test.ts +++ b/test/sweepBatch.test.ts @@ -1,11 +1,27 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' -import { nextSweepBatch, SWEEP_BATCHES } from '../src/storage/sweep.js' +import { nextSweepBatch, sweepBatch, SWEEP_BATCHES } from '../src/storage/sweep.js' const records = (count: number) => Array.from({ length: count }, (unused, i) => ({ cid: `cid-${i}` })) describe('nextSweepBatch', () => { + it('exposes a durable cursor and an unambiguous full-cycle boundary', () => { + const all = records(SWEEP_BATCHES.repair + 3) + const first = sweepBatch('repair', all) + const second = sweepBatch('repair', all, first.nextCursor) + + assert.equal(first.cycleCompleted, false) + assert.equal(first.records.length, SWEEP_BATCHES.repair) + assert.equal(second.cycleCompleted, true) + assert.equal(second.records.length, 3) + assert.equal(second.nextCursor, undefined) + assert.equal( + new Set([...first.records, ...second.records].map((item) => item.cid)).size, + all.length + ) + }) + it('returns everything when the set fits in one pass', () => { const all = records(3) diff --git a/test/timedStream.test.ts b/test/timedStream.test.ts new file mode 100644 index 0000000..5a5fc9c --- /dev/null +++ b/test/timedStream.test.ts @@ -0,0 +1,44 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { createTimedReadable } from '../src/utils/timedStream.js' + +describe('complete stream deadline', () => { + it('aborts a transfer that stalls after its first chunk', async () => { + async function* source(signal: AbortSignal): AsyncGenerator { + yield Buffer.from('first') + await new Promise((resolve, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }) + }) + } + + const { stream } = createTimedReadable(source, 20, () => new Error('complete timeout')) + const chunks: Buffer[] = [] + + await assert.rejects(async () => { + for await (const chunk of stream) chunks.push(Buffer.from(chunk)) + }, /complete timeout/) + assert.equal(Buffer.concat(chunks).toString(), 'first') + }) + + it('propagates external cancellation before the deadline', async () => { + const controller = new AbortController() + async function* source(signal: AbortSignal): AsyncGenerator { + if (signal.aborted) throw signal.reason + await new Promise((resolve, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }) + }) + yield Buffer.alloc(0) + } + const { stream } = createTimedReadable( + source, + 10_000, + () => new Error('timeout'), + controller.signal + ) + + controller.abort(new Error('client left')) + await assert.rejects(async () => { + for await (const unused of stream) void unused + }, /client left/) + }) +}) From 130b3529aea59ce736e12c234f9e247365d06736 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Sun, 30 Aug 2026 22:41:04 +0300 Subject: [PATCH 137/167] feat: complete node health response contract --- README.md | 35 +++++++++++++--- docs/openapi.yaml | 61 ++++++++++++++++++++++------ src/api/node.ts | 6 ++- src/health/service.ts | 27 ++++++++++++- src/health/state.ts | 86 +++++++++++++++++++++++++++++++--------- src/replication.cron.ts | 23 +++++++---- test/healthState.test.ts | 26 +++++++++--- 7 files changed, 211 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 0cb5695..151a702 100644 --- a/README.md +++ b/README.md @@ -366,11 +366,35 @@ Example response: ```json { - "status": "ready", + "version": "0.1.0", + "uptimeMs": 123456, + "state": "ready", "height": 1720614960000, "timestamp": 1720614998797, - "checkpointIntervalMs": 60000, - "membershipVersion": "d0f1...", + "checkpoint": { + "intervalMs": 60000, + "observedAt": 1720614998700, + "ageMs": 97, + "maxAgeMs": 180000 + }, + "membership": { + "version": "d0f1...", + "requiredPeers": 1, + "attestedPeers": 2 + }, + "startup": { "complete": true, "healthy": true }, + "storage": { + "measuredAt": 1720614980000, + "measurementAgeMs": 18797, + "availableBytes": 8589934592, + "reservedBytes": 5368709120 + }, + "replication": { + "repairRequired": true, + "lastCompleteAt": 1720614900000, + "ageMs": 98797, + "backlog": 0 + }, "checks": { "helia": true, "startupReconciliation": true, @@ -378,12 +402,11 @@ Example response: "storageReserve": true, "repairFresh": true, "peerAttestations": true - }, - "peerAttestations": { "required": 1, "received": 2 } + } } ``` -The endpoint always returns `200`; consumers must inspect `status`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle, and the configured peer attestations all pass. It freezes on failure. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. +The endpoint always returns `200`; consumers must inspect `state`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle with no known backlog, and the configured peer attestations all pass. It freezes on failure. Observation timestamps and ages let clients reject an absolutely stale cluster even when every node reports the same height. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. ### Get legacy client node information diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 069e6e4..7344886 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -173,15 +173,21 @@ components: Health: type: object required: - - status + - version + - uptimeMs + - state - height - timestamp - - checkpointIntervalMs - - membershipVersion + - checkpoint + - membership + - startup + - storage + - replication - checks - - peerAttestations properties: - status: + version: { type: string } + uptimeMs: { type: integer, minimum: 0 } + state: type: string enum: [starting, ready, stale, degraded] height: @@ -189,17 +195,46 @@ components: minimum: 0 description: Persisted Unix milliseconds at the start of the last valid fixed round timestamp: { type: integer } - checkpointIntervalMs: { type: integer, minimum: 1000 } - membershipVersion: { type: string, pattern: '^[a-f0-9]{64}$' } - checks: + checkpoint: type: object - additionalProperties: { type: boolean } - peerAttestations: + required: [intervalMs, observedAt, ageMs, maxAgeMs] + properties: + intervalMs: { type: integer, minimum: 1000 } + observedAt: { type: [integer, 'null'] } + ageMs: { type: [integer, 'null'], minimum: 0 } + maxAgeMs: { type: integer, minimum: 1000 } + membership: + type: object + required: [version, requiredPeers, attestedPeers] + properties: + version: { type: string, pattern: '^[a-f0-9]{64}$' } + requiredPeers: { type: integer, minimum: 0 } + attestedPeers: { type: integer, minimum: 0 } + startup: type: object - required: [required, received] + required: [complete, healthy] properties: - required: { type: integer, minimum: 0 } - received: { type: integer, minimum: 0 } + complete: { type: boolean } + healthy: { type: boolean } + storage: + type: object + required: [measuredAt, measurementAgeMs, availableBytes, reservedBytes] + properties: + measuredAt: { type: [integer, 'null'] } + measurementAgeMs: { type: [integer, 'null'], minimum: 0 } + availableBytes: { type: integer, minimum: 0 } + reservedBytes: { type: integer, minimum: 0 } + replication: + type: object + required: [repairRequired, lastCompleteAt, ageMs, backlog] + properties: + repairRequired: { type: boolean } + lastCompleteAt: { type: [integer, 'null'] } + ageMs: { type: [integer, 'null'], minimum: 0 } + backlog: { type: integer, minimum: 0 } + checks: + type: object + additionalProperties: { type: boolean } LegacyNodeInfo: type: object required: diff --git a/src/api/node.ts b/src/api/node.ts index 5dc014c..54a597d 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -11,7 +11,11 @@ const router = Router() publicNodeRouter.get('/health', async (req, res, next) => { try { - res.status(200).send(getHealthSnapshot()) + res.status(200).send({ + version: packageJson.version, + uptimeMs: Math.floor(process.uptime() * 1000), + ...getHealthSnapshot() + }) } catch (err) { next(err) } diff --git a/src/health/service.ts b/src/health/service.ts index d9c6b09..23618f6 100644 --- a/src/health/service.ts +++ b/src/health/service.ts @@ -33,6 +33,8 @@ let snapshot: HealthSnapshot = evaluateHealth(config.health, { storageReservedBytes: config.storage.diskReserveBytes, repairRequired: config.replication.enabled && config.replication.repairEnabled, repairCompletedAt: null, + repairHealthy: !config.replication.enabled || !config.replication.repairEnabled, + repairBacklog: 0, attestedPeers: 0, membershipVersion: networkMembershipVersion, previous: null @@ -90,6 +92,8 @@ export async function runHealthCheckpoint(): Promise { storageReservedBytes: storage.reservedBytes, repairRequired: repair.required, repairCompletedAt: repair.completedAt, + repairHealthy: repair.healthy, + repairBacklog: repair.backlog, attestedPeers, membershipVersion: networkMembershipVersion, previous @@ -104,7 +108,7 @@ export async function runHealthCheckpoint(): Promise { logger.info( { event: 'health_checkpoint', - status: snapshot.status, + state: snapshot.state, height: snapshot.height, checks: snapshot.checks, attestedPeers @@ -165,5 +169,24 @@ export function stopHealthService(): void { /** Cached, read-only response for the always-200 health endpoint. */ export function getHealthSnapshot(): HealthSnapshot { - return { ...snapshot, timestamp: Date.now() } + const timestamp = Date.now() + const currentAge = (observedAt: number | null): number | null => + observedAt === null ? null : Math.max(0, timestamp - observedAt) + + return { + ...snapshot, + timestamp, + checkpoint: { + ...snapshot.checkpoint, + ageMs: currentAge(snapshot.checkpoint.observedAt) + }, + storage: { + ...snapshot.storage, + measurementAgeMs: currentAge(snapshot.storage.measuredAt) + }, + replication: { + ...snapshot.replication, + ageMs: currentAge(snapshot.replication.lastCompleteAt) + } + } } diff --git a/src/health/state.ts b/src/health/state.ts index b960331..c40473d 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -1,6 +1,6 @@ import type { HealthConfig } from '../config.js' -export type HealthStatus = 'starting' | 'ready' | 'stale' | 'degraded' +export type HealthState = 'starting' | 'ready' | 'stale' | 'degraded' export interface HealthCheckpoint { /** Unix milliseconds at the start of the validated fixed round. */ @@ -20,18 +20,45 @@ export interface HealthInputs { storageReservedBytes: number repairRequired: boolean repairCompletedAt: number | null + repairHealthy: boolean + repairBacklog: number attestedPeers: number membershipVersion: string previous: HealthCheckpoint | null } export interface HealthSnapshot { - status: HealthStatus + state: HealthState /** Monotonic checkpoint height, never a request timestamp. */ height: number timestamp: number - checkpointIntervalMs: number - membershipVersion: string + checkpoint: { + intervalMs: number + observedAt: number | null + ageMs: number | null + maxAgeMs: number + } + membership: { + version: string + requiredPeers: number + attestedPeers: number + } + startup: { + complete: boolean + healthy: boolean + } + storage: { + measuredAt: number | null + measurementAgeMs: number | null + availableBytes: number + reservedBytes: number + } + replication: { + repairRequired: boolean + lastCompleteAt: number | null + ageMs: number | null + backlog: number + } checks: { helia: boolean startupReconciliation: boolean @@ -40,10 +67,10 @@ export interface HealthSnapshot { repairFresh: boolean peerAttestations: boolean } - peerAttestations: { - required: number - received: number - } +} + +function age(now: number, observedAt: number | null): number | null { + return observedAt === null ? null : Math.max(0, now - observedAt) } /** Return the fixed checkpoint round containing `time`. */ @@ -71,13 +98,15 @@ export function evaluateHealth( storageReserve: input.storageAvailableBytes >= input.storageReservedBytes, repairFresh: !input.repairRequired || - (input.repairCompletedAt !== null && + (input.repairHealthy && + input.repairBacklog === 0 && + input.repairCompletedAt !== null && input.now - input.repairCompletedAt <= policy.repairMaxAgeMs), peerAttestations: input.attestedPeers >= policy.requiredPeerCount } const complete = Object.values(checks).every(Boolean) const previousAge = input.previous === null ? null : input.now - input.previous.completedAt - const status: HealthStatus = complete + const state: HealthState = complete ? 'ready' : !input.startupComplete ? 'starting' @@ -98,16 +127,37 @@ export function evaluateHealth( return { snapshot: { - status, + state, height: completed?.height ?? input.previous?.height ?? 0, timestamp: input.now, - checkpointIntervalMs: policy.checkpointIntervalMs, - membershipVersion: input.membershipVersion, - checks, - peerAttestations: { - required: policy.requiredPeerCount, - received: input.attestedPeers - } + checkpoint: { + intervalMs: policy.checkpointIntervalMs, + observedAt: completed?.completedAt ?? input.previous?.completedAt ?? null, + ageMs: age(input.now, completed?.completedAt ?? input.previous?.completedAt ?? null), + maxAgeMs: policy.maxCheckpointAgeMs + }, + membership: { + version: input.membershipVersion, + requiredPeers: policy.requiredPeerCount, + attestedPeers: input.attestedPeers + }, + startup: { + complete: input.startupComplete, + healthy: input.startupHealthy + }, + storage: { + measuredAt: input.storageUpdatedAt, + measurementAgeMs: age(input.now, input.storageUpdatedAt), + availableBytes: input.storageAvailableBytes, + reservedBytes: input.storageReservedBytes + }, + replication: { + repairRequired: input.repairRequired, + lastCompleteAt: input.repairCompletedAt, + ageMs: age(input.now, input.repairCompletedAt), + backlog: input.repairBacklog + }, + checks }, completed } diff --git a/src/replication.cron.ts b/src/replication.cron.ts index a973b79..a339285 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -26,6 +26,7 @@ interface RepairCycleEvidence { unrecoverable: number lastCompletedAt: number | null lastCompletedSuccessfully: boolean + lastCompletedBacklog: number } let evidence: RepairCycleEvidence | null = null @@ -45,7 +46,8 @@ function emptyEvidence(now = Date.now()): RepairCycleEvidence { stillMissing: 0, unrecoverable: 0, lastCompletedAt: config.replication.enabled && config.replication.repairEnabled ? null : now, - lastCompletedSuccessfully: !config.replication.enabled || !config.replication.repairEnabled + lastCompletedSuccessfully: !config.replication.enabled || !config.replication.repairEnabled, + lastCompletedBacklog: 0 } } @@ -72,7 +74,8 @@ async function loadEvidence(): Promise { Number.isSafeInteger(parsed.stillMissing) && Number.isSafeInteger(parsed.unrecoverable) && (parsed.lastCompletedAt === null || Number.isSafeInteger(parsed.lastCompletedAt)) && - typeof parsed.lastCompletedSuccessfully === 'boolean' + typeof parsed.lastCompletedSuccessfully === 'boolean' && + Number.isSafeInteger(parsed.lastCompletedBacklog) ? (parsed as RepairCycleEvidence) : emptyEvidence() } catch (err) { @@ -133,7 +136,8 @@ export async function repairUnderReplicatedFiles(): Promise { ? { ...emptyEvidence(), lastCompletedAt: completedAt, - lastCompletedSuccessfully: completedSuccessfully + lastCompletedSuccessfully: completedSuccessfully, + lastCompletedBacklog: aggregate.stillMissing + aggregate.unrecoverable } : { ...current, @@ -185,7 +189,8 @@ export function getReplicationState() { stillMissing: evidence.stillMissing, unrecoverable: evidence.unrecoverable, lastCompletedAt: evidence.lastCompletedAt, - lastCompletedSuccessfully: evidence.lastCompletedSuccessfully + lastCompletedSuccessfully: evidence.lastCompletedSuccessfully, + lastCompletedBacklog: evidence.lastCompletedBacklog } : null, lastRun: lastReport @@ -210,13 +215,15 @@ export async function initializeReplicationRepairState(): Promise { export function getRepairHealthEvidence(): { required: boolean completedAt: number | null + healthy: boolean + backlog: number } { const required = config.replication.enabled && config.replication.repairEnabled + const activeBacklog = (evidence?.stillMissing ?? 0) + (evidence?.unrecoverable ?? 0) return { required, - completedAt: - !required || evidence?.lastCompletedSuccessfully === true - ? (evidence?.lastCompletedAt ?? Date.now()) - : null + completedAt: evidence?.lastCompletedAt ?? (required ? null : Date.now()), + healthy: !required || evidence?.lastCompletedSuccessfully === true, + backlog: required ? Math.max(evidence?.lastCompletedBacklog ?? 0, activeBacklog) : 0 } } diff --git a/test/healthState.test.ts b/test/healthState.test.ts index 680ae95..ef58446 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -23,6 +23,8 @@ const healthy = (now: number) => ({ storageReservedBytes: 1_000, repairRequired: true, repairCompletedAt: now, + repairHealthy: true, + repairBacklog: 0, attestedPeers: 1, membershipVersion: 'a'.repeat(64), previous: null @@ -33,9 +35,12 @@ describe('health checkpoint state', () => { assert.equal(checkpointRound(12_345, 1_000), 12_000) const result = evaluateHealth(policy, healthy(12_345)) - assert.equal(result.snapshot.status, 'ready') + assert.equal(result.snapshot.state, 'ready') assert.equal(result.snapshot.height, 12_000) assert.equal(result.snapshot.timestamp, 12_345) + assert.equal(result.snapshot.checkpoint.observedAt, 12_345) + assert.equal(result.snapshot.storage.measurementAgeMs, 0) + assert.equal(result.snapshot.replication.backlog, 0) }) it('freezes height when peer coverage is lost', () => { @@ -46,7 +51,7 @@ describe('health checkpoint state', () => { previous: prior }) - assert.equal(result.snapshot.status, 'degraded') + assert.equal(result.snapshot.state, 'degraded') assert.equal(result.snapshot.height, 12_000) assert.equal(result.completed, undefined) }) @@ -59,7 +64,7 @@ describe('health checkpoint state', () => { previous: prior }) - assert.equal(result.snapshot.status, 'stale') + assert.equal(result.snapshot.state, 'stale') assert.equal(result.snapshot.height, prior.height) }) @@ -74,7 +79,18 @@ describe('health checkpoint state', () => { startupHealthy: false }) - assert.equal(starting.snapshot.status, 'starting') - assert.equal(failed.snapshot.status, 'degraded') + assert.equal(starting.snapshot.state, 'starting') + assert.equal(failed.snapshot.state, 'degraded') + }) + + it('does not advance while a known repair backlog remains', () => { + const result = evaluateHealth(policy, { + ...healthy(12_345), + repairBacklog: 1 + }) + + assert.equal(result.snapshot.state, 'degraded') + assert.equal(result.snapshot.checks.repairFresh, false) + assert.equal(result.completed, undefined) }) }) From 52b13fb665aa503e7607e6d8cc8b6be540bf7f60 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 31 Aug 2026 16:59:13 +0300 Subject: [PATCH 138/167] fix: enhance health monitoring and replication repair processes --- README.md | 21 ++- config.default.json5 | 7 +- docs/openapi.yaml | 78 ++++++++- docs/storage-lifecycle.md | 2 +- src/api/file.ts | 27 ++- src/api/node.ts | 75 ++------ src/api/nodeRoutes.ts | 94 ++++++++++ src/config.ts | 18 ++ src/health/protocol.ts | 15 +- src/health/service.ts | 58 +++---- src/health/state.ts | 34 +++- src/index.ts | 22 ++- src/observability.ts | 7 +- src/replication.cron.ts | 53 +++++- src/storage/config.ts | 11 +- src/storage/service.ts | 172 +++++++++++-------- src/storage/sweep.ts | 10 +- src/utils/downloadResponse.ts | 43 +++-- src/utils/file.ts | 24 ++- src/utils/timedStream.ts | 24 ++- test/config.test.ts | 16 ++ test/downloadResponse.test.ts | 12 +- test/healthState.test.ts | 18 +- test/integration/replicationProtocol.test.ts | 15 ++ test/nodeRoutes.test.ts | 105 +++++++++++ test/replication.test.ts | 3 +- test/retrieval.test.ts | 3 +- test/storageConfig.test.ts | 5 + test/sweepBatch.test.ts | 26 ++- test/timedStream.test.ts | 42 ++++- 30 files changed, 786 insertions(+), 254 deletions(-) create mode 100644 src/api/nodeRoutes.ts create mode 100644 test/nodeRoutes.test.ts diff --git a/README.md b/README.md index 151a702..2c953ad 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "serverPort": 4000, "uploadLimitSizeBytes": 268435456, "maxFileCount": 10, + "findFileTimeout": 20000, + "downloadIdleTimeout": 20000, + "downloadMinBytesPerSecond": 32768, "cors": { "allowedOrigins": ["https://adm.im", "https://*.adamant.im", "http://localhost:8080"] }, @@ -85,7 +88,8 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "requireQuorumOnUpload": false, "requestTimeoutMs": 30000, "repairEnabled": true, - "repairSchedule": "0 */30 * * * *" + "repairSchedule": "0 */30 * * * *", + "repairBatchDelayMs": 1000 }, "health": { "checkpointIntervalMs": 60000, @@ -99,6 +103,11 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, } ``` +`findFileTimeout` bounds discovery, while `downloadIdleTimeout` (defaulting to +`findFileTimeout`) bounds a stalled transfer. `downloadMinBytesPerSecond` +(default `32768`) derives a complete-transfer deadline from the file size, so a +large active download is not cut off by the short discovery timeout. + `storage`, `replication`, and `health` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Storage and replication are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. Logs are newline-delimited JSON by default. Set `prettyLogs: true` only for an interactive development terminal; production collectors should keep the structured fields emitted by Pino and the health checkpoint. @@ -351,10 +360,9 @@ Download responses use the following stable status contract: | `400 Bad Request` | The CID is invalid | | `408 Request Timeout` | The file could not be found or retrieved before the configured timeout | | `429 Too Many Requests` | The read rate limit was exceeded | -| `416 Range Not Satisfiable` | Range retrieval is intentionally unsupported | | `500 Internal Server Error` | An unexpected internal failure occurred before streaming started | -The timeout covers the complete transfer, not only discovery or the first chunk, and remains `408 Request Timeout` for compatibility with existing clients. Client disconnect cancels the underlying retrieval. Successful immutable-CID responses include `ETag`, long-lived immutable caching, and `Accept-Ranges: none`. If an error occurs after response bytes have started, the server terminates the incomplete response because an HTTP status and JSON error body can no longer be sent safely. +Discovery, idle transfer time, and the size-aware complete transfer all have bounded timeouts and remain `408 Request Timeout` for compatibility with existing clients. Client disconnect cancels the underlying retrieval. Range headers are ignored and the server returns the complete `200` representation with `Accept-Ranges: none`, preserving current PWA/iOS behavior. Successful responses include an `ETag` and private one-hour caching with revalidation; a matching `If-None-Match` returns `304` after availability has been checked. If an error occurs after response bytes have started, the server terminates the incomplete response because an HTTP status and JSON error body can no longer be sent safely. ### Check public health @@ -386,8 +394,7 @@ Example response: "storage": { "measuredAt": 1720614980000, "measurementAgeMs": 18797, - "availableBytes": 8589934592, - "reservedBytes": 5368709120 + "reserveHealthy": true }, "replication": { "repairRequired": true, @@ -408,6 +415,10 @@ Example response: The endpoint always returns `200`; consumers must inspect `state`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle with no known backlog, and the configured peer attestations all pass. It freezes on failure. Observation timestamps and ages let clients reject an absolutely stale cluster even when every node reports the same height. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. +`membership.version` identifies the configured peer-set epoch. Changing the node list resets the persisted checkpoint for that node: `height` remains `0` until the first valid checkpoint under the new membership. Clients must compare heights only when membership versions match and treat a version change as a new epoch. + +Repair runs in bounded 50-record passes. A scheduled run starts a full cycle immediately and continues its remaining passes after `replication.repairBatchDelayMs`; `repairSchedule` controls when a new completed cycle is refreshed. Set `health.repairMaxAgeMs` longer than the largest expected full-cycle duration plus the schedule interval. An incomplete or backlogged cycle intentionally keeps health degraded. + ### Get legacy client node information ```bash diff --git a/config.default.json5 b/config.default.json5 index 445b0b0..9e9312c 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -33,6 +33,9 @@ uploadLimitSizeBytes: 268435456, // 256 Mb. maxFileCount: 10, findFileTimeout: 20000, + // A transfer may run longer than discovery while chunks keep arriving. + downloadIdleTimeout: 20000, + downloadMinBytesPerSecond: 32768, cors: { // Exact origins and left-most subdomain wildcards are supported. allowedOrigins: [ @@ -96,7 +99,9 @@ requireQuorumOnUpload: false, // Incompatible with confirmationRequired; needs ackQuorum >= 2 requestTimeoutMs: 30000, repairEnabled: true, - repairSchedule: '0 */30 * * * *' + repairSchedule: '0 */30 * * * *', + // Bounded passes continue until a complete repair cycle is recorded. + repairBatchDelayMs: 1000 }, health: { checkpointIntervalMs: 60000, diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 7344886..cfa1222 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -36,6 +36,10 @@ paths: responses: '200': description: Node identity, storage, health, and bounded HTTP metrics + content: + application/json: + schema: + $ref: '#/components/schemas/NodeDetails' '401': $ref: '#/components/responses/Unauthorized' '503': @@ -73,7 +77,8 @@ paths: parameters: - $ref: '#/components/parameters/Cid' get: - summary: Download a complete immutable file + summary: Download a complete content-addressed file + description: Range headers are ignored and return the complete 200 representation responses: '200': description: Complete file attachment @@ -87,12 +92,12 @@ paths: content: application/octet-stream: schema: { type: string, format: binary } + '304': + description: The validated CID representation has not changed '400': $ref: '#/components/responses/BadRequest' '408': description: Discovery or complete retrieval timed out - '416': - description: Range requests are not supported '429': description: Read rate limit exceeded /api/file/{cid}/status: @@ -207,7 +212,10 @@ components: type: object required: [version, requiredPeers, attestedPeers] properties: - version: { type: string, pattern: '^[a-f0-9]{64}$' } + version: + type: string + pattern: '^[a-f0-9]{64}$' + description: Peer-set epoch; compare checkpoint heights only when versions match requiredPeers: { type: integer, minimum: 0 } attestedPeers: { type: integer, minimum: 0 } startup: @@ -218,12 +226,11 @@ components: healthy: { type: boolean } storage: type: object - required: [measuredAt, measurementAgeMs, availableBytes, reservedBytes] + required: [measuredAt, measurementAgeMs, reserveHealthy] properties: measuredAt: { type: [integer, 'null'] } measurementAgeMs: { type: [integer, 'null'], minimum: 0 } - availableBytes: { type: integer, minimum: 0 } - reservedBytes: { type: integer, minimum: 0 } + reserveHealthy: { type: boolean } replication: type: object required: [repairRequired, lastCompleteAt, ageMs, backlog] @@ -235,6 +242,63 @@ components: checks: type: object additionalProperties: { type: boolean } + HttpMetrics: + type: object + required: + - requests + - responses + - inFlight + - totalResponseTimeMs + - maxResponseTimeMs + - averageResponseTimeMs + properties: + requests: { type: integer, minimum: 0 } + responses: + type: object + required: [2xx, 3xx, 4xx, 5xx] + properties: + 2xx: { type: integer, minimum: 0 } + 3xx: { type: integer, minimum: 0 } + 4xx: { type: integer, minimum: 0 } + 5xx: { type: integer, minimum: 0 } + inFlight: { type: integer, minimum: 0 } + totalResponseTimeMs: { type: number, minimum: 0 } + maxResponseTimeMs: { type: number, minimum: 0 } + averageResponseTimeMs: { type: number, minimum: 0 } + NodeDetails: + type: object + required: + - version + - timestamp + - heliaStatus + - peerId + - multiAddresses + - blockstoreSizeMb + - datastoreSizeMb + - availableSizeInMb + - pinnedBytes + - reclaimableBytes + - availableBytes + - reservedBytes + - health + - http + properties: + version: { type: string } + timestamp: { type: integer } + heliaStatus: { type: string } + peerId: { type: string } + multiAddresses: + type: array + items: { type: string } + blockstoreSizeMb: { type: number } + datastoreSizeMb: { type: number } + availableSizeInMb: { type: number } + pinnedBytes: { type: integer, minimum: 0 } + reclaimableBytes: { type: integer, minimum: 0 } + availableBytes: { type: integer, minimum: 0 } + reservedBytes: { type: integer, minimum: 0 } + health: { $ref: '#/components/schemas/Health' } + http: { $ref: '#/components/schemas/HttpMetrics' } LegacyNodeInfo: type: object required: diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 4915b79..24ff9e3 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -458,7 +458,7 @@ The repair job (`replication.repairSchedule`, or `POST /api/storage/repair`) lists confirmed files this node holds whose designated peers have not all acknowledged, and places the missing copies again. -Each scheduled pass remains bounded to 50 held files. Its sorted cursor and +Each scheduled pass remains bounded to 50 confirmed records. Its sorted cursor and aggregate result are persisted under the node datastore, so restart resumes the same cycle. A repair checkpoint is recorded only after a pass reaches the end of the candidate set; a pass over one batch is not presented as full-fleet diff --git a/src/api/file.ts b/src/api/file.ts index 94a053c..2b951e4 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -16,7 +16,11 @@ import { import { fileRegistry } from '../storage/state.js' import { createUploadHandler } from './uploadRoute.js' import { parseCid } from '../utils/cid.js' -import { sendDownloadStream } from '../utils/downloadResponse.js' +import { + matchesDownloadEtag, + sendDownloadStream, + setDownloadHeaders +} from '../utils/downloadResponse.js' import { downloadFile, getFileStats } from '../utils/file.js' import { logger } from '../utils/logger.js' @@ -104,11 +108,6 @@ router.get('/:cid', readLimiter, async (req, res, next) => { }) try { - if (req.headers.range !== undefined) { - res.set('Accept-Ranges', 'none') - return res.status(416).send({ error: 'Range requests are not supported' }) - } - const cid = parseCid(req.params.cid) // Reach the file's holders first. Without this the read only succeeds if a @@ -117,17 +116,29 @@ router.get('/:cid', readLimiter, async (req, res, next) => { await prepareFileRetrieval(cid, requestController.signal) const fileStats = await getFileStats(cid, requestController.signal) - const download = downloadFile(cid, { signal: requestController.signal }) + const metadata = { cid: cid.toString(), fileSize: fileStats.size } + + // Range is deliberately ignored and the complete representation is sent. + // This preserves established PWA/iOS behavior without advertising ranges. + if (matchesDownloadEtag(req.headers['if-none-match'], metadata.cid)) { + setDownloadHeaders(res, metadata) + res.removeHeader('Content-Length') + res.removeHeader('Content-Disposition') + return res.status(304).end() + } + + const download = downloadFile(cid, fileStats.size, { signal: requestController.signal }) sendDownloadStream( download.stream, res, - { cid: cid.toString(), fileSize: fileStats.size }, + metadata, next, (error) => logger.error(error), () => download.abort(new Error('Download client disconnected')) ) } catch (error) { + if (requestController.signal.aborted || res.destroyed) return next(error) } }) diff --git a/src/api/node.ts b/src/api/node.ts index 54a597d..7dd43a5 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -1,68 +1,25 @@ -import { Router } from 'express' import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' import { getStorageMetrics } from '../storage/metrics.js' import { getHealthSnapshot } from '../health/service.js' import { getHttpMetrics } from '../observability.js' +import { readLimiter } from '../middleware/rateLimiter.js' +import { createNodeRouters } from './nodeRoutes.js' -export const publicNodeRouter = Router() -const router = Router() - -publicNodeRouter.get('/health', async (req, res, next) => { - try { - res.status(200).send({ - version: packageJson.version, - uptimeMs: Math.floor(process.uptime() * 1000), - ...getHealthSnapshot() - }) - } catch (err) { - next(err) - } -}) - -/** Legacy PWA/iOS contract. Keep public and free of node identity or operator data. */ -publicNodeRouter.get('/info', async (req, res, next) => { - try { - const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() - res.send({ - version: packageJson.version, - timestamp: Date.now(), - heliaStatus: helia.libp2p.status, - blockstoreSizeMb, - datastoreSizeMb, - availableSizeInMb - }) - } catch (err) { - next(err) - } -}) - -/** Detailed node identity and storage report for authenticated operators. */ -router.get('/details', async (req, res, next) => { - try { - const { blockstoreSizeMb, datastoreSizeMb, availableSizeInMb } = getDiskUsageStats() - const storage = getStorageMetrics() - res.send({ - version: packageJson.version, - timestamp: Date.now(), - heliaStatus: helia.libp2p.status, - peerId: helia.libp2p.peerId, - multiAddresses: helia.libp2p.getMultiaddrs(), - blockstoreSizeMb, - datastoreSizeMb, - availableSizeInMb, - // Byte-accurate figures; GET /api/storage/metrics carries the full report. - pinnedBytes: storage.pinnedBytes, - reclaimableBytes: storage.reclaimableBytes, - availableBytes: storage.availableBytes, - reservedBytes: storage.reservedBytes, - health: getHealthSnapshot(), - http: getHttpMetrics() - }) - } catch (err) { - next(err) - } +const routers = createNodeRouters({ + version: packageJson.version, + now: Date.now, + uptimeMs: () => Math.floor(process.uptime() * 1000), + readLimiter, + getHeliaStatus: () => helia.libp2p.status, + getPeerId: () => helia.libp2p.peerId.toString(), + getMultiAddresses: () => helia.libp2p.getMultiaddrs().map((address) => address.toString()), + getDiskUsageStats, + getStorageMetrics, + getHealthSnapshot, + getHttpMetrics }) -export default router +export const publicNodeRouter = routers.publicNodeRouter +export default routers.nodeRouter diff --git a/src/api/nodeRoutes.ts b/src/api/nodeRoutes.ts new file mode 100644 index 0000000..d98be12 --- /dev/null +++ b/src/api/nodeRoutes.ts @@ -0,0 +1,94 @@ +import { Router, type RequestHandler } from 'express' +import type { HealthSnapshot } from '../health/state.js' + +/** Runtime values used by the testable node HTTP contract. */ +export interface NodeRouteDependencies { + version: string + now: () => number + uptimeMs: () => number + readLimiter: RequestHandler + getHeliaStatus: () => string + getPeerId: () => string + getMultiAddresses: () => string[] + getDiskUsageStats: () => { + blockstoreSizeMb: number + datastoreSizeMb: number + availableSizeInMb: number + } + getStorageMetrics: () => { + pinnedBytes: number + reclaimableBytes: number + availableBytes: number + reservedBytes: number + } + getHealthSnapshot: () => HealthSnapshot + getHttpMetrics: () => unknown +} + +/** Build public and administrative node routers from explicit runtime dependencies. */ +export function createNodeRouters(deps: NodeRouteDependencies): { + publicNodeRouter: Router + nodeRouter: Router +} { + const publicNodeRouter = Router() + const nodeRouter = Router() + + publicNodeRouter.get('/health', (req, res, next) => { + void req + try { + res.status(200).send({ + version: deps.version, + uptimeMs: deps.uptimeMs(), + ...deps.getHealthSnapshot() + }) + } catch (err) { + next(err) + } + }) + + /** Legacy PWA/iOS contract. Keep public and free of node identity or operator data. */ + publicNodeRouter.get('/info', deps.readLimiter, (req, res, next) => { + void req + try { + res.send({ + version: deps.version, + timestamp: deps.now(), + heliaStatus: deps.getHeliaStatus(), + ...deps.getDiskUsageStats() + }) + } catch (err) { + next(err) + } + }) + + /** Detailed node identity and storage report for authenticated operators. */ + nodeRouter.get('/details', (req, res, next) => { + void req + try { + const storage = deps.getStorageMetrics() + res.send({ + version: deps.version, + timestamp: deps.now(), + heliaStatus: deps.getHeliaStatus(), + peerId: deps.getPeerId(), + multiAddresses: deps.getMultiAddresses(), + ...deps.getDiskUsageStats(), + // Byte-accurate figures; GET /api/storage/metrics carries the full report. + pinnedBytes: storage.pinnedBytes, + reclaimableBytes: storage.reclaimableBytes, + availableBytes: storage.availableBytes, + reservedBytes: storage.reservedBytes, + health: { + version: deps.version, + uptimeMs: deps.uptimeMs(), + ...deps.getHealthSnapshot() + }, + http: deps.getHttpMetrics() + }) + } catch (err) { + next(err) + } + }) + + return { publicNodeRouter, nodeRouter } +} diff --git a/src/config.ts b/src/config.ts index bc97b82..5a47b0c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -101,6 +101,10 @@ export interface Config { maxFileCount: number /** Time limit, in milliseconds, for locating a file on the IPFS network. */ findFileTimeout: number + /** Maximum pause between download chunks before retrieval is cancelled. */ + downloadIdleTimeout: number + /** Minimum sustained rate used to derive a size-aware complete-transfer deadline. */ + downloadMinBytesPerSecond: number cors: { /** Exact origins and left-most subdomain wildcards; see `src/security/cors.ts`. */ allowedOrigins: string[] @@ -326,6 +330,18 @@ export function validateConfig(raw: unknown): Config { ? DEFAULT_PEERING_SCHEDULE : requireString(root.peeringSchedule, 'peeringSchedule') const findFileTimeout = requireInteger(root.findFileTimeout, 'findFileTimeout', 1) + const downloadIdleTimeout = optionalInteger( + root.downloadIdleTimeout, + 'downloadIdleTimeout', + findFileTimeout, + 1 + ) + const downloadMinBytesPerSecond = optionalInteger( + root.downloadMinBytesPerSecond, + 'downloadMinBytesPerSecond', + 32 * 1024, + 1 + ) const bootstrap = requireStringArray(peerDiscovery.bootstrap, 'peerDiscovery.bootstrap') // Owns cors, trustProxy, adminApiKey, enableDebugApi, rateLimits, @@ -370,6 +386,8 @@ export function validateConfig(raw: unknown): Config { uploadLimitSizeBytes: root.uploadLimitSizeBytes as number, maxFileCount: root.maxFileCount as number, findFileTimeout, + downloadIdleTimeout, + downloadMinBytesPerSecond, cors: { allowedOrigins: cors.allowedOrigins as string[] }, trustProxy: (root.trustProxy ?? false) as TrustProxySetting, rateLimits: root.rateLimits as Config['rateLimits'], diff --git a/src/health/protocol.ts b/src/health/protocol.ts index ed56a1a..d6388f3 100644 --- a/src/health/protocol.ts +++ b/src/health/protocol.ts @@ -14,6 +14,15 @@ export interface Attestation { membershipVersion: string } +/** Accept the local fixed round or one adjacent round during a tolerated clock skew. */ +export function isCompatibleRound( + requestedRound: number, + localRound: number, + intervalMs: number +): boolean { + return Math.abs(requestedRound - localRound) <= intervalMs +} + function encode(message: unknown): Uint8Array { const body = new TextEncoder().encode(JSON.stringify(message)) const frame = new Uint8Array(LENGTH_PREFIX_BYTES + body.byteLength) @@ -108,14 +117,16 @@ export async function registerHealthProtocol( if ( request.membershipVersion !== options.membershipVersion || - request.round !== localRound || + !isCompatibleRound(request.round, localRound, options.checkpointIntervalMs) || Math.abs(timestamp - request.timestamp) > options.clockSkewToleranceMs ) { throw new Error('Health attestation does not match the local round') } send(stream, { - round: localRound, + // Echo the accepted request round so the caller gets an attestation for + // the checkpoint it is currently evaluating across the boundary. + round: request.round, timestamp, membershipVersion: options.membershipVersion }) diff --git a/src/health/service.ts b/src/health/service.ts index 23618f6..3f363d6 100644 --- a/src/health/service.ts +++ b/src/health/service.ts @@ -11,6 +11,7 @@ import { HEALTH_PROTOCOL, registerHealthProtocol, requestHealthAttestation } fro import { checkpointRound, evaluateHealth, + refreshHealthSnapshot, type HealthCheckpoint, type HealthSnapshot } from './state.js' @@ -19,9 +20,10 @@ const networkMembershipVersion = membershipVersion(config.nodes) let previous: HealthCheckpoint | null = null let startupComplete = false let startupHealthy = false -let running = false let rerunRequested = false +let stopping = false let timer: NodeJS.Timeout | undefined +let inFlight: Promise | undefined let snapshot: HealthSnapshot = evaluateHealth(config.health, { now: Date.now(), @@ -68,19 +70,16 @@ async function countAttestations(now: number): Promise { } /** Run one bounded health checkpoint attempt. Overlapping attempts are skipped. */ -export async function runHealthCheckpoint(): Promise { - if (running) { +export function runHealthCheckpoint(): Promise { + if (inFlight !== undefined) { rerunRequested = true - return snapshot + return Promise.resolve(snapshot) } - running = true - try { + const run = (async () => { const now = Date.now() - const [attestedPeers, repair] = await Promise.all([ - countAttestations(now), - Promise.resolve(getRepairHealthEvidence()) - ]) + const repair = getRepairHealthEvidence() + const attestedPeers = await countAttestations(now) const storage = getStorageMetrics() const evaluated = evaluateHealth(config.health, { now, @@ -116,16 +115,22 @@ export async function runHealthCheckpoint(): Promise { 'Health checkpoint evaluated' ) return snapshot - } finally { - running = false - if (rerunRequested) { + })() + inFlight = run + const finish = (): void => { + if (inFlight === run) inFlight = undefined + if (rerunRequested && !stopping) { rerunRequested = false scheduleCheckpoint() } } + void run.then(finish, finish) + return run } function scheduleCheckpoint(): void { + if (stopping) return + void runHealthCheckpoint().catch((err: Error) => { logger.error({ event: 'health_checkpoint_error', err }, 'Health checkpoint failed') }) @@ -133,6 +138,7 @@ function scheduleCheckpoint(): void { /** Start the health protocol and the fixed-round checkpoint scheduler. */ export async function startHealthService(): Promise { + stopping = false previous = await loadHealthCheckpoint(datastore) if (previous?.membershipVersion !== networkMembershipVersion) previous = null @@ -162,31 +168,15 @@ export function setStartupReconciliationResult(healthy: boolean): void { } /** Stop scheduling new health work during graceful shutdown. */ -export function stopHealthService(): void { +export async function stopHealthService(): Promise { + stopping = true + rerunRequested = false if (timer !== undefined) clearInterval(timer) timer = undefined + await inFlight } /** Cached, read-only response for the always-200 health endpoint. */ export function getHealthSnapshot(): HealthSnapshot { - const timestamp = Date.now() - const currentAge = (observedAt: number | null): number | null => - observedAt === null ? null : Math.max(0, timestamp - observedAt) - - return { - ...snapshot, - timestamp, - checkpoint: { - ...snapshot.checkpoint, - ageMs: currentAge(snapshot.checkpoint.observedAt) - }, - storage: { - ...snapshot.storage, - measurementAgeMs: currentAge(snapshot.storage.measuredAt) - }, - replication: { - ...snapshot.replication, - ageMs: currentAge(snapshot.replication.lastCompleteAt) - } - } + return refreshHealthSnapshot(snapshot, Date.now()) } diff --git a/src/health/state.ts b/src/health/state.ts index c40473d..a632869 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -50,8 +50,7 @@ export interface HealthSnapshot { storage: { measuredAt: number | null measurementAgeMs: number | null - availableBytes: number - reservedBytes: number + reserveHealthy: boolean } replication: { repairRequired: boolean @@ -148,8 +147,7 @@ export function evaluateHealth( storage: { measuredAt: input.storageUpdatedAt, measurementAgeMs: age(input.now, input.storageUpdatedAt), - availableBytes: input.storageAvailableBytes, - reservedBytes: input.storageReservedBytes + reserveHealthy: checks.storageReserve }, replication: { repairRequired: input.repairRequired, @@ -162,3 +160,31 @@ export function evaluateHealth( completed } } + +/** Refresh elapsed ages and stale state without performing checkpoint I/O. */ +export function refreshHealthSnapshot(current: HealthSnapshot, timestamp: number): HealthSnapshot { + const checkpointAge = age(timestamp, current.checkpoint.observedAt) + + return { + ...current, + state: + current.state !== 'starting' && + checkpointAge !== null && + checkpointAge > current.checkpoint.maxAgeMs + ? 'stale' + : current.state, + timestamp, + checkpoint: { + ...current.checkpoint, + ageMs: checkpointAge + }, + storage: { + ...current.storage, + measurementAgeMs: age(timestamp, current.storage.measuredAt) + }, + replication: { + ...current.replication, + ageMs: age(timestamp, current.replication.lastCompleteAt) + } + } +} diff --git a/src/index.ts b/src/index.ts index db55575..1b9e43a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,11 @@ import { registerReplicationProtocol } from './storage/replicationProtocol.js' import { createReplicationHandlers } from './storage/service.js' import { fileRegistry } from './storage/state.js' import { garbageCollectionCron, admissionRecoveryCron } from './gc.cron.js' -import { initializeReplicationRepairState, replicationRepairCron } from './replication.cron.js' +import { + initializeReplicationRepairState, + startReplicationRepair, + stopReplicationRepair +} from './replication.cron.js' import cors from 'cors' import * as routers from './api/index.js' import { errorHandler, notFoundHandler } from './middleware/errorHandler.js' @@ -70,7 +74,7 @@ function startLifecycleJobs(): void { if (config.replication.enabled) { if (config.replication.repairEnabled) { - replicationRepairCron.start() + startReplicationRepair() } } else { logger.info('Replication is disabled. Uploaded content is stored best effort on this node.') @@ -170,8 +174,7 @@ function shutdown(signal: string): void { peeringCron.stop() garbageCollectionCron.stop() admissionRecoveryCron.stop() - replicationRepairCron.stop() - stopHealthService() + const backgroundStopped = Promise.allSettled([stopReplicationRepair(), stopHealthService()]) const forceTimer = setTimeout(() => { logger.warn('Shutdown timed out') @@ -189,8 +192,15 @@ function shutdown(signal: string): void { server.close(() => { clearTimeout(closeIdle) - void helia - .stop() + void backgroundStopped + .then((results) => { + for (const result of results) { + if (result.status === 'rejected') { + logger.error(`Background shutdown failed: ${String(result.reason)}`) + } + } + return helia.stop() + }) .catch((err: Error) => logger.error(`helia.stop failed: ${err.message}`)) .finally(() => { clearTimeout(forceTimer) diff --git a/src/observability.ts b/src/observability.ts index 0de9a19..f6a1a1c 100644 --- a/src/observability.ts +++ b/src/observability.ts @@ -41,10 +41,15 @@ export const collectHttpMetrics: RequestHandler = (req, res, next) => { /** Snapshot suitable for the authenticated operator details endpoint. */ export function getHttpMetrics(): HttpMetrics & { averageResponseTimeMs: number } { + const completedResponses = Object.values(metrics.responses).reduce( + (total, count) => total + count, + 0 + ) + return { ...metrics, responses: { ...metrics.responses }, averageResponseTimeMs: - metrics.requests === 0 ? 0 : metrics.totalResponseTimeMs / metrics.requests + completedResponses === 0 ? 0 : metrics.totalResponseTimeMs / completedResponses } } diff --git a/src/replication.cron.ts b/src/replication.cron.ts index a339285..48935ca 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -19,6 +19,7 @@ interface RepairCycleEvidence { policyVersion: string cursor?: string startedAt: number + examined: number checked: number underReplicated: number repaired: number @@ -40,6 +41,7 @@ function emptyEvidence(now = Date.now()): RepairCycleEvidence { membershipVersion: membershipVersion(config.nodes), policyVersion: currentPolicyVersion(), startedAt: now, + examined: 0, checked: 0, underReplicated: 0, repaired: 0, @@ -68,6 +70,7 @@ async function loadEvidence(): Promise { typeof parsed.policyVersion === 'string' && (parsed.cursor === undefined || typeof parsed.cursor === 'string') && Number.isSafeInteger(parsed.startedAt) && + (parsed.examined === undefined || Number.isSafeInteger(parsed.examined)) && Number.isSafeInteger(parsed.checked) && Number.isSafeInteger(parsed.underReplicated) && Number.isSafeInteger(parsed.repaired) && @@ -76,7 +79,7 @@ async function loadEvidence(): Promise { (parsed.lastCompletedAt === null || Number.isSafeInteger(parsed.lastCompletedAt)) && typeof parsed.lastCompletedSuccessfully === 'boolean' && Number.isSafeInteger(parsed.lastCompletedBacklog) - ? (parsed as RepairCycleEvidence) + ? ({ ...parsed, examined: parsed.examined ?? parsed.checked } as RepairCycleEvidence) : emptyEvidence() } catch (err) { if ((err as { code?: string }).code !== 'ERR_NOT_FOUND') throw err @@ -120,6 +123,7 @@ export async function repairUnderReplicatedFiles(): Promise { lastReport = await repairReplication({ cursor: current.cursor }) const aggregate = { + examined: current.examined + lastReport.examined, checked: current.checked + lastReport.checked, underReplicated: current.underReplicated + lastReport.underReplicated, repaired: current.repaired + lastReport.repaired.length, @@ -160,13 +164,47 @@ export async function repairUnderReplicatedFiles(): Promise { } export const replicationRepairCron = new CronJob(config.replication.repairSchedule, () => { - if (running) { - return - } + void runScheduledRepair() +}) + +let stopping = false +let continuationTimer: NodeJS.Timeout | undefined +let scheduledInFlight: Promise | undefined + +async function runScheduledRepair(): Promise { + if (stopping || running || continuationTimer !== undefined) return logger.info('[Cron] Running "replicationRepair" cronjob.') - repairUnderReplicatedFiles().catch((err) => logger.error(`${err.message}\n${err.stack}`)) -}) + const run = repairUnderReplicatedFiles() + .then((report) => { + if (stopping || report.cycleCompleted) return + continuationTimer = setTimeout(() => { + continuationTimer = undefined + void runScheduledRepair() + }, config.replication.repairBatchDelayMs) + continuationTimer.unref() + }) + .catch((err: Error) => logger.error(`${err.message}\n${err.stack}`)) + scheduledInFlight = run + await run + if (scheduledInFlight === run) scheduledInFlight = undefined +} + +/** Start the periodic repair job and immediately begin the first full cycle. */ +export function startReplicationRepair(): void { + stopping = false + replicationRepairCron.start() + void runScheduledRepair() +} + +/** Stop scheduled repair work and wait for the active bounded pass. */ +export async function stopReplicationRepair(): Promise { + stopping = true + replicationRepairCron.stop() + if (continuationTimer !== undefined) clearTimeout(continuationTimer) + continuationTimer = undefined + await scheduledInFlight +} export function getReplicationState() { return { @@ -183,6 +221,7 @@ export function getReplicationState() { cycle: evidence ? { startedAt: evidence.startedAt, + examined: evidence.examined, checked: evidence.checked, underReplicated: evidence.underReplicated, repaired: evidence.repaired, @@ -195,7 +234,9 @@ export function getReplicationState() { : null, lastRun: lastReport ? { + examined: lastReport.examined, checked: lastReport.checked, + releasedChecked: lastReport.releasedChecked, underReplicated: lastReport.underReplicated, repaired: lastReport.repaired.length, stillMissing: lastReport.stillMissing.length, diff --git a/src/storage/config.ts b/src/storage/config.ts index aedcded..a0ce99e 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -83,6 +83,8 @@ export interface ReplicationConfig { requestTimeoutMs: number repairEnabled: boolean repairSchedule: string + /** Pause between bounded passes while completing one full repair cycle. */ + repairBatchDelayMs: number } export const DEFAULT_STORAGE_CONFIG: StorageConfig = { @@ -110,7 +112,8 @@ export const DEFAULT_REPLICATION_CONFIG: ReplicationConfig = { requireQuorumOnUpload: false, requestTimeoutMs: 30000, repairEnabled: true, - repairSchedule: '0 */30 * * * *' + repairSchedule: '0 */30 * * * *', + repairBatchDelayMs: 1_000 } function fail(path: string, expectation: string): never { @@ -316,6 +319,12 @@ export function resolveReplicationConfig(raw: unknown): ReplicationConfig { input.repairSchedule, 'replication.repairSchedule', defaults.repairSchedule + ), + repairBatchDelayMs: optionalInteger( + input.repairBatchDelayMs, + 'replication.repairBatchDelayMs', + defaults.repairBatchDelayMs, + 0 ) } diff --git a/src/storage/service.ts b/src/storage/service.ts index d3c77f7..9416da1 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -792,7 +792,12 @@ async function cacheFileLocallyUnderLock(cid: string, peerId: string): Promise { +): Promise<{ checked: number; rescued: string[]; unrecoverable: string[] }> { const self = selfPeerId() + let checked = 0 const rescued: string[] = [] const unrecoverable: string[] = [] const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) const candidates = records.filter((record) => record.state === 'confirmed' && !record.heldLocally) - for (const record of candidates) { - const cid = CID.parse(record.cid) - const holders = storageTargets(placementFor(record.cid, record.createdAt, peers), self) - .map((peerId) => byPeerId.get(peerId)) - .filter((peer): peer is ReplicationPeer => peer !== undefined) - - const answers = await Promise.all( - holders.map(async (peer) => { - try { - return await probeHave(helia, peer.multiAddr, record.cid, callOptions()) - } catch { - return false - } - }) - ) + let nextIndex = 0 + const processNext = async (): Promise => { + while (nextIndex < candidates.length) { + const record = candidates[nextIndex] + nextIndex += 1 + checked += 1 + const cid = CID.parse(record.cid) + const holders = storageTargets(placementFor(record.cid, record.createdAt, peers), self) + .map((peerId) => byPeerId.get(peerId)) + .filter((peer): peer is ReplicationPeer => peer !== undefined) - if (answers.some(Boolean)) { - continue - } + const answers = await Promise.all( + holders.map(async (peer) => { + try { + return await probeHave(helia, peer.multiAddr, record.cid, callOptions()) + } catch { + return false + } + }) + ) - if (!(await helia.blockstore.has(cid))) { - unrecoverable.push(record.cid) - logger.error(`No configured node reports holding ${record.cid}`) - continue - } + if (answers.some(Boolean)) { + continue + } - try { - const kept = await storageOperationLock.withShared(async () => { - // Only worth pinning if the whole DAG is still here; an offline stat says - // so without going near the network. Collection cannot invalidate that - // result before the following pin is committed. - await ifs.stat(cid, { extended: true, offline: true }) - - // The record was chosen before the probes, which take as long as the - // network does. Writing the snapshot back would undo whatever happened to - // the file meanwhile — a re-upload, a release, an updated replica list — - // so the pin and the state change happen together, against the record as - // it is now. - return fileRegistry.withExclusiveCids([record.cid], async (registry) => { - const current = await registry.get(record.cid) - - if ( - current === undefined || - current.revision !== record.revision || - current.state !== 'confirmed' || - current.heldLocally - ) { - return undefined - } + if (!(await helia.blockstore.has(cid))) { + unrecoverable.push(record.cid) + logger.error(`No configured node reports holding ${record.cid}`) + continue + } - const createdPin = await pinFile(helia, cid) + try { + const kept = await storageOperationLock.withShared(async () => { + // Only worth pinning if the whole DAG is still here; an offline stat says + // so without going near the network. Collection cannot invalidate that + // result before the following pin is committed. + await ifs.stat(cid, { extended: true, offline: true }) + + // The record was chosen before the probes, which take as long as the + // network does. Writing the snapshot back would undo whatever happened to + // the file meanwhile — a re-upload, a release, an updated replica list — + // so the pin and the state change happen together, against the record as + // it is now. + return fileRegistry.withExclusiveCids([record.cid], async (registry) => { + const current = await registry.get(record.cid) + + if ( + current === undefined || + current.revision !== record.revision || + current.state !== 'confirmed' || + current.heldLocally + ) { + return undefined + } - try { - return await registry.save({ - ...current, - pinned: true, - heldLocally: true, - admissionId: undefined, - admissionSettledAt: undefined, - replicaStage: undefined - }) - } catch (err) { - if (createdPin && current.pinned !== true) { - await unpinFile(helia, cid) + const createdPin = await pinFile(helia, cid) + + try { + return await registry.save({ + ...current, + pinned: true, + heldLocally: true, + admissionId: undefined, + admissionSettledAt: undefined, + replicaStage: undefined + }) + } catch (err) { + if (createdPin && current.pinned !== true) { + await unpinFile(helia, cid) + } + throw err } - throw err - } + }) }) - }) - if (kept === undefined) { + if (kept === undefined) { + continue + } + + rescued.push(record.cid) + logger.warn(`No node was holding ${record.cid}; kept the local copy instead`) + } catch { + // The blocks are only partly here, so there is nothing to rescue continue } - - rescued.push(record.cid) - logger.warn(`No node was holding ${record.cid}; kept the local copy instead`) - } catch { - // The blocks are only partly here, so there is nothing to rescue - continue } } - return { rescued, unrecoverable } + await Promise.all( + Array.from({ length: Math.min(4, candidates.length) }, async () => processNext()) + ) + + rescued.sort() + unrecoverable.sort() + return { checked, rescued, unrecoverable } } /** @@ -938,7 +954,9 @@ async function rescueOrphanedFiles( */ export async function repairReplication(options: { cursor?: string } = {}): Promise { const report: RepairReport = { + examined: 0, checked: 0, + releasedChecked: 0, underReplicated: 0, repaired: [], stillMissing: [], @@ -963,7 +981,7 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom report.cycleCompleted = selected.cycleCompleted report.nextCursor = selected.nextCursor - report.checked = candidates.length + report.examined = candidates.length const byPeerId = new Map(peers.map((peer) => [peer.peerId, peer])) @@ -974,6 +992,7 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom if (!(await claimRepairRecord(fileRegistry, record))) { continue } + report.checked += 1 try { const placement = placementFor(record.cid, record.createdAt, peers) @@ -1010,6 +1029,7 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom } const rescue = await rescueOrphanedFiles(candidates, peers) + report.releasedChecked = rescue.checked report.rescued = rescue.rescued report.unrecoverable = rescue.unrecoverable diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index cdd7ff1..2e4af17 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -5,7 +5,7 @@ * records into a source of steady chatter. Repair may transfer a file, so it * gets the smallest batch; the others only exchange short messages. */ -export const SWEEP_BATCHES = { repair: 50, demote: 200, rescue: 50 } +export const SWEEP_BATCHES = { repair: 50, demote: 200 } export type SweepName = keyof typeof SWEEP_BATCHES @@ -33,7 +33,11 @@ export function sweepBatch( cursor?: string ): SweepBatch { const size = SWEEP_BATCHES[sweep] - const ordered = [...records].sort((left, right) => left.cid.localeCompare(right.cid)) + // Cursor lookup below uses JavaScript code-unit ordering. Keep sorting on the + // same deterministic relation instead of the host's locale-dependent ICU rules. + const ordered = [...records].sort((left, right) => + left.cid < right.cid ? -1 : left.cid > right.cid ? 1 : 0 + ) let start = 0 if (cursor !== undefined) { @@ -67,7 +71,7 @@ export function sweepBatch( * it. Candidates are sorted by CID first: resume compares CIDs lexicographically, * and datastore listing order is not sorted. A cursor pointing at a record that * has since gone resumes at the first remaining CID greater than it, so a - * successful demote/rescue of the last batch element does not rewind to the head. + * successful demotion of the last batch element does not rewind to the head. * * @param sweep Which sweep is asking; each keeps its own position * @param records Every candidate; order is ignored and replaced by CID order diff --git a/src/utils/downloadResponse.ts b/src/utils/downloadResponse.ts index ebfbbfa..12348a3 100644 --- a/src/utils/downloadResponse.ts +++ b/src/utils/downloadResponse.ts @@ -6,6 +6,31 @@ export type DownloadMetadata = { fileSize: bigint } +/** Match a request validator against the strong CID ETag emitted by this node. */ +export function matchesDownloadEtag(value: string | undefined, cid: string): boolean { + if (value === undefined) return false + const expected = `"${cid}"` + return value + .split(',') + .map((candidate) => candidate.trim().replace(/^W\//, '')) + .some((candidate) => candidate === '*' || candidate === expected) +} + +/** Apply the stable cache and content negotiation headers for a CID response. */ +export function setDownloadHeaders(res: Response, metadata: DownloadMetadata): void { + if (res.headersSent) return + + res.set('Content-Type', 'application/octet-stream') + res.set('Content-Length', metadata.fileSize.toString()) + res.set('Content-Disposition', `attachment; filename="${metadata.cid}"`) + res.set('X-Content-Type-Options', 'nosniff') + res.set('ETag', `"${metadata.cid}"`) + // Files may be explicitly released, so shared immutable caching would outlive + // the node's serving policy. Private caches revalidate after a bounded hour. + res.set('Cache-Control', 'private, max-age=3600, must-revalidate') + res.set('Accept-Ranges', 'none') +} + /** * Pipe a file stream while preserving a controlled JSON error response before * the first byte and terminating an incomplete binary response afterwards. @@ -26,22 +51,10 @@ export function sendDownloadStream( onDisconnect: () => void = () => undefined ): void { let clientDisconnected = false - const setDownloadHeaders = (): void => { - if (res.headersSent) { - return - } - - res.set('Content-Type', 'application/octet-stream') - res.set('Content-Length', metadata.fileSize.toString()) - res.set('Content-Disposition', `attachment; filename="${metadata.cid}"`) - res.set('X-Content-Type-Options', 'nosniff') - res.set('ETag', `"${metadata.cid}"`) - res.set('Cache-Control', 'public, max-age=31536000, immutable') - res.set('Accept-Ranges', 'none') - } + const applyHeaders = (): void => setDownloadHeaders(res, metadata) - stream.once('data', setDownloadHeaders) - stream.once('end', setDownloadHeaders) + stream.once('data', applyHeaders) + stream.once('end', applyHeaders) stream.once('error', (error: Error) => { stream.unpipe(res) if (clientDisconnected) { diff --git a/src/utils/file.ts b/src/utils/file.ts index e400a48..9ec8bc7 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -22,7 +22,8 @@ export async function getFileStats(cid: CID, externalSignal?: AbortSignal) { : abortController.signal const stats = await ifs.stat(cid, { signal }) return stats - } catch { + } catch (error) { + if (externalSignal?.aborted) throw externalSignal.reason ?? error throw new FileNotFoundError('Cannot find requested CID. Request timed out.') } finally { if (timeout !== undefined) { @@ -32,16 +33,29 @@ export async function getFileStats(cid: CID, externalSignal?: AbortSignal) { } /** - * Return a file stream by CID. - * Throws a timeout error if the file is not found. + * Return a file stream with idle and size-aware complete-transfer deadlines. + * + * @param cid Content identifier to retrieve + * @param fileSize Stat result used to derive a deadline that permits large active transfers + * @param options Request cancellation and test/operator timeout overrides */ export function downloadFile( cid: CID, - options: { signal?: AbortSignal; timeoutMs?: number } = {} + fileSize: bigint, + options: { signal?: AbortSignal; idleTimeoutMs?: number; totalTimeoutMs?: number } = {} ): TimedReadable { + const idleTimeoutMs = options.idleTimeoutMs ?? config.downloadIdleTimeout + const sizeAwareTimeoutMs = + Math.ceil(Number(fileSize) / config.downloadMinBytesPerSecond) * 1_000 + idleTimeoutMs + return createTimedReadable( (signal) => ifs.cat(cid, { signal }), - options.timeoutMs ?? config.findFileTimeout, + { + idleTimeoutMs, + totalTimeoutMs: + options.totalTimeoutMs ?? + Math.min(2_147_483_647, Math.max(idleTimeoutMs, sizeAwareTimeoutMs)) + }, () => new FileNotFoundError('Unable to retrieve the file. Request timed out.'), options.signal ) diff --git a/src/utils/timedStream.ts b/src/utils/timedStream.ts index 27bfdfb..26239fd 100644 --- a/src/utils/timedStream.ts +++ b/src/utils/timedStream.ts @@ -5,6 +5,13 @@ export interface TimedReadable { abort: (reason?: Error) => void } +export interface StreamTimeouts { + /** Maximum time without receiving another chunk. */ + idleTimeoutMs: number + /** Maximum time for the complete iteration. */ + totalTimeoutMs: number +} + /** * Create a readable whose deadline covers the complete iteration. * @@ -13,7 +20,7 @@ export interface TimedReadable { */ export function createTimedReadable( source: (signal: AbortSignal) => AsyncIterable, - timeoutMs: number, + timeouts: StreamTimeouts, timeoutError: () => Error, externalSignal?: AbortSignal ): TimedReadable { @@ -30,15 +37,22 @@ export function createTimedReadable( ? externalSignal.reason : new Error('Stream request was cancelled') ) - externalSignal?.addEventListener('abort', externalAbort, { once: true }) - const timer = setTimeout(abort, timeoutMs) + if (externalSignal?.aborted) externalAbort() + else externalSignal?.addEventListener('abort', externalAbort, { once: true }) + const totalTimer = setTimeout(abort, timeouts.totalTimeoutMs) + let idleTimer = setTimeout(abort, timeouts.idleTimeoutMs) const guardedSource = async function* (): AsyncGenerator { if (controller.signal.aborted) throw controller.signal.reason - yield* source(controller.signal) + for await (const chunk of source(controller.signal)) { + clearTimeout(idleTimer) + idleTimer = setTimeout(abort, timeouts.idleTimeoutMs) + yield chunk + } } const stream = Readable.from(guardedSource()) const cleanup = (): void => { - clearTimeout(timer) + clearTimeout(totalTimer) + clearTimeout(idleTimer) externalSignal?.removeEventListener('abort', externalAbort) } stream.once('end', cleanup) diff --git a/test/config.test.ts b/test/config.test.ts index c912827..44bf5c9 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -52,6 +52,8 @@ describe('validateConfig', () => { assert.equal(parsed.prettyLogs, false) assert.equal(parsed.health.checkpointIntervalMs, 60_000) assert.equal(parsed.health.requiredPeerCount, 0) + assert.equal(parsed.downloadIdleTimeout, 20_000) + assert.equal(parsed.downloadMinBytesPerSecond, 32 * 1024) }) it('ignores unknown keys so deployments can carry extras', () => { @@ -123,6 +125,20 @@ describe('validateConfig', () => { raw.findFileTimeout = -1 }, /findFileTimeout/ + ], + [ + 'download idle timeout is invalid', + (raw) => { + raw.downloadIdleTimeout = 0 + }, + /downloadIdleTimeout/ + ], + [ + 'download minimum throughput is invalid', + (raw) => { + raw.downloadMinBytesPerSecond = 0 + }, + /downloadMinBytesPerSecond/ ] ] diff --git a/test/downloadResponse.test.ts b/test/downloadResponse.test.ts index a7d2020..3824722 100644 --- a/test/downloadResponse.test.ts +++ b/test/downloadResponse.test.ts @@ -4,10 +4,18 @@ import { Readable } from 'node:stream' import { describe, it } from 'node:test' import express, { type Express } from 'express' import { getPublicError } from '../src/security/errors.js' -import { sendDownloadStream } from '../src/utils/downloadResponse.js' +import { matchesDownloadEtag, sendDownloadStream } from '../src/utils/downloadResponse.js' import { FileNotFoundError } from '../src/utils/fileErrors.js' describe('download response streaming', () => { + it('matches strong, weak, list, and wildcard validators for the CID ETag', () => { + assert.equal(matchesDownloadEtag('"bafytest"', 'bafytest'), true) + assert.equal(matchesDownloadEtag('W/"bafytest"', 'bafytest'), true) + assert.equal(matchesDownloadEtag('"other", "bafytest"', 'bafytest'), true) + assert.equal(matchesDownloadEtag('*', 'bafytest'), true) + assert.equal(matchesDownloadEtag('"other"', 'bafytest'), false) + }) + it('keeps an early stream error as a controlled JSON response', async () => { const app = express() app.get('/file', (req, res, next) => { @@ -73,7 +81,7 @@ describe('download response streaming', () => { assert.equal(response.headers.get('content-disposition'), 'attachment; filename="bafytest"') assert.equal(response.headers.get('etag'), '"bafytest"') assert.equal(response.headers.get('accept-ranges'), 'none') - assert.match(response.headers.get('cache-control') ?? '', /immutable/) + assert.equal(response.headers.get('cache-control'), 'private, max-age=3600, must-revalidate') await assert.rejects(response.arrayBuffer()) assert.equal(lateErrors, 1) } finally { diff --git a/test/healthState.test.ts b/test/healthState.test.ts index ef58446..f8494f0 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -1,7 +1,8 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' import type { HealthConfig } from '../src/config.js' -import { checkpointRound, evaluateHealth } from '../src/health/state.js' +import { isCompatibleRound } from '../src/health/protocol.js' +import { checkpointRound, evaluateHealth, refreshHealthSnapshot } from '../src/health/state.js' const policy: HealthConfig = { checkpointIntervalMs: 1_000, @@ -31,6 +32,11 @@ const healthy = (now: number) => ({ }) describe('health checkpoint state', () => { + it('accepts adjacent fixed rounds during tolerated boundary skew', () => { + assert.equal(isCompatibleRound(12_000, 13_000, 1_000), true) + assert.equal(isCompatibleRound(12_000, 14_000, 1_000), false) + }) + it('uses a fixed Unix-millisecond round rather than the request timestamp', () => { assert.equal(checkpointRound(12_345, 1_000), 12_000) const result = evaluateHealth(policy, healthy(12_345)) @@ -40,6 +46,7 @@ describe('health checkpoint state', () => { assert.equal(result.snapshot.timestamp, 12_345) assert.equal(result.snapshot.checkpoint.observedAt, 12_345) assert.equal(result.snapshot.storage.measurementAgeMs, 0) + assert.equal(result.snapshot.storage.reserveHealthy, true) assert.equal(result.snapshot.replication.backlog, 0) }) @@ -93,4 +100,13 @@ describe('health checkpoint state', () => { assert.equal(result.snapshot.checks.repairFresh, false) assert.equal(result.completed, undefined) }) + + it('reports a cached ready checkpoint as stale when its age expires', () => { + const ready = evaluateHealth(policy, healthy(12_345)).snapshot + const refreshed = refreshHealthSnapshot(ready, 16_000) + + assert.equal(refreshed.state, 'stale') + assert.equal(refreshed.checkpoint.ageMs, 3_655) + assert.equal(refreshed.height, ready.height) + }) }) diff --git a/test/integration/replicationProtocol.test.ts b/test/integration/replicationProtocol.test.ts index 97be2fa..90d3265 100644 --- a/test/integration/replicationProtocol.test.ts +++ b/test/integration/replicationProtocol.test.ts @@ -136,6 +136,21 @@ describe('health attestations over libp2p', () => { ) }) + it('accepts an adjacent fixed round from an authorized peer', async () => { + const localRound = Math.floor(HEALTH_NOW / 60_000) * 60_000 + + await requestHealthAttestation( + sender.node, + holder.node.libp2p.getMultiaddrs()[0], + { + round: localRound - 60_000, + timestamp: HEALTH_NOW, + membershipVersion: HEALTH_MEMBERSHIP + }, + { timeoutMs: 1_000, clockSkewToleranceMs: 10_000 } + ) + }) + it('rejects a different membership version', async () => { await assert.rejects( requestHealthAttestation( diff --git a/test/nodeRoutes.test.ts b/test/nodeRoutes.test.ts new file mode 100644 index 0000000..7076a1d --- /dev/null +++ b/test/nodeRoutes.test.ts @@ -0,0 +1,105 @@ +import assert from 'node:assert/strict' +import type { AddressInfo } from 'node:net' +import { after, before, describe, it } from 'node:test' +import express from 'express' +import { createNodeRouters } from '../src/api/nodeRoutes.js' +import type { HealthSnapshot } from '../src/health/state.js' + +const health: HealthSnapshot = { + state: 'ready', + height: 12_000, + timestamp: 12_345, + checkpoint: { intervalMs: 1_000, observedAt: 12_345, ageMs: 0, maxAgeMs: 3_000 }, + membership: { version: 'a'.repeat(64), requiredPeers: 1, attestedPeers: 1 }, + startup: { complete: true, healthy: true }, + storage: { measuredAt: 12_345, measurementAgeMs: 0, reserveHealthy: true }, + replication: { repairRequired: true, lastCompleteAt: 12_345, ageMs: 0, backlog: 0 }, + checks: { + helia: true, + startupReconciliation: true, + storageFresh: true, + storageReserve: true, + repairFresh: true, + peerAttestations: true + } +} + +describe('node HTTP contract', () => { + let url = '' + let close: (() => Promise) | undefined + let limited = 0 + + before(async () => { + const routers = createNodeRouters({ + version: '1.2.3', + now: () => 20_000, + uptimeMs: () => 5_000, + readLimiter: (req, res, next) => { + void req + void res + limited += 1 + next() + }, + getHeliaStatus: () => 'started', + getPeerId: () => 'peer-id', + getMultiAddresses: () => ['/ip4/127.0.0.1/tcp/4001'], + getDiskUsageStats: () => ({ + blockstoreSizeMb: 1, + datastoreSizeMb: 2, + availableSizeInMb: 3 + }), + getStorageMetrics: () => ({ + pinnedBytes: 4, + reclaimableBytes: 5, + availableBytes: 6, + reservedBytes: 7 + }), + getHealthSnapshot: () => health, + getHttpMetrics: () => ({ requests: 1 }) + }) + const app = express() + app.use('/api/node', routers.publicNodeRouter) + app.use('/api/node', routers.nodeRouter) + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + url = `http://127.0.0.1:${address.port}` + close = () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + }) + + after(async () => close?.()) + + it('serves the always-200 health schema without a rate-limit status override', async () => { + const response = await fetch(`${url}/api/node/health`) + const body = (await response.json()) as Record + + assert.equal(response.status, 200) + assert.equal(body.version, '1.2.3') + assert.equal(body.state, 'ready') + assert.equal(body.height, 12_000) + assert.equal(limited, 0) + }) + + it('rate-limits the legacy public info route', async () => { + assert.equal((await fetch(`${url}/api/node/info`)).status, 200) + assert.equal(limited, 1) + }) + + it('serializes the operator identity and embeds the complete health schema', async () => { + const response = await fetch(`${url}/api/node/details`) + const body = (await response.json()) as { + peerId: string + multiAddresses: string[] + health: { version: string; uptimeMs: number } + } + + assert.equal(body.peerId, 'peer-id') + assert.deepEqual(body.multiAddresses, ['/ip4/127.0.0.1/tcp/4001']) + assert.equal(body.health.version, '1.2.3') + assert.equal(body.health.uptimeMs, 5_000) + }) +}) diff --git a/test/replication.test.ts b/test/replication.test.ts index 64cf63a..754fba6 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -25,7 +25,8 @@ const baseConfig: ReplicationConfig = { requireQuorumOnUpload: false, requestTimeoutMs: 1000, repairEnabled: true, - repairSchedule: '0 * * * * *' + repairSchedule: '0 * * * * *', + repairBatchDelayMs: 1_000 } const peer = (name: string): ReplicationPeer => ({ diff --git a/test/retrieval.test.ts b/test/retrieval.test.ts index ce08a2a..c4394d6 100644 --- a/test/retrieval.test.ts +++ b/test/retrieval.test.ts @@ -21,7 +21,8 @@ const config: ReplicationConfig = { requireQuorumOnUpload: false, requestTimeoutMs: 1000, repairEnabled: true, - repairSchedule: '0 * * * * *' + repairSchedule: '0 * * * * *', + repairBatchDelayMs: 1_000 } const peer = (name: string): ReplicationPeer => ({ diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 4259b0a..01f3653 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -156,4 +156,9 @@ describe('resolveReplicationConfig', () => { /ackQuorum/ ) }) + + it('validates the pause between bounded repair passes', () => { + assert.equal(resolveReplicationConfig({ repairBatchDelayMs: 0 }).repairBatchDelayMs, 0) + assert.throws(() => resolveReplicationConfig({ repairBatchDelayMs: -1 }), /repairBatchDelayMs/) + }) }) diff --git a/test/sweepBatch.test.ts b/test/sweepBatch.test.ts index 344d564..6328824 100644 --- a/test/sweepBatch.test.ts +++ b/test/sweepBatch.test.ts @@ -25,17 +25,17 @@ describe('nextSweepBatch', () => { it('returns everything when the set fits in one pass', () => { const all = records(3) - assert.deepEqual(nextSweepBatch('rescue', all), all) + assert.deepEqual(nextSweepBatch('repair', all), all) }) it('covers every record across passes instead of repeating the first ones', () => { - const size = SWEEP_BATCHES.rescue + const size = SWEEP_BATCHES.repair const all = records(size * 2 + 7) const seen = new Set() // Three passes is more than enough to walk a set of this size for (let pass = 0; pass < 3; pass += 1) { - for (const record of nextSweepBatch('rescue', all)) { + for (const record of nextSweepBatch('repair', all)) { seen.add(record.cid) } } @@ -53,7 +53,7 @@ describe('nextSweepBatch', () => { const all = records(SWEEP_BATCHES.repair + 5) const firstRepair = nextSweepBatch('repair', all) - nextSweepBatch('rescue', all) + nextSweepBatch('demote', all) const secondRepair = nextSweepBatch('repair', all) assert.notDeepEqual(firstRepair[0], secondRepair[0]) @@ -70,14 +70,14 @@ describe('nextSweepBatch', () => { }) it('starts over when the record it stopped at is gone', () => { - const all = records(SWEEP_BATCHES.rescue + 3) - nextSweepBatch('rescue', all) + const all = records(SWEEP_BATCHES.demote + 3) + nextSweepBatch('demote', all) - const replaced = records(SWEEP_BATCHES.rescue + 3).map((record) => ({ + const replaced = records(SWEEP_BATCHES.demote + 3).map((record) => ({ cid: `${record.cid}-new` })) - assert.equal(nextSweepBatch('rescue', replaced).length, SWEEP_BATCHES.rescue) + assert.equal(nextSweepBatch('demote', replaced).length, SWEEP_BATCHES.demote) }) it('does not rewind when the last batch CID left the candidate set', () => { @@ -106,4 +106,14 @@ describe('nextSweepBatch', () => { assert.ok(second[0].cid > cursor) }) + + it('uses the same code-unit ordering for sorting and cursor resume', () => { + const all = [{ cid: 'a' }, { cid: 'B' }, { cid: 'b' }, { cid: 'A' }] + const first = sweepBatch('repair', all, 'A') + + assert.deepEqual( + first.records.map((record) => record.cid), + ['B', 'a', 'b'] + ) + }) }) diff --git a/test/timedStream.test.ts b/test/timedStream.test.ts index 5a5fc9c..89e3a9d 100644 --- a/test/timedStream.test.ts +++ b/test/timedStream.test.ts @@ -11,7 +11,11 @@ describe('complete stream deadline', () => { }) } - const { stream } = createTimedReadable(source, 20, () => new Error('complete timeout')) + const { stream } = createTimedReadable( + source, + { idleTimeoutMs: 20, totalTimeoutMs: 100 }, + () => new Error('complete timeout') + ) const chunks: Buffer[] = [] await assert.rejects(async () => { @@ -31,7 +35,7 @@ describe('complete stream deadline', () => { } const { stream } = createTimedReadable( source, - 10_000, + { idleTimeoutMs: 10_000, totalTimeoutMs: 10_000 }, () => new Error('timeout'), controller.signal ) @@ -41,4 +45,38 @@ describe('complete stream deadline', () => { for await (const unused of stream) void unused }, /client left/) }) + + it('propagates an already-aborted external signal', async () => { + const controller = new AbortController() + controller.abort(new Error('already gone')) + const { stream } = createTimedReadable( + async function* () { + yield Buffer.from('unexpected') + }, + { idleTimeoutMs: 10_000, totalTimeoutMs: 10_000 }, + () => new Error('timeout'), + controller.signal + ) + + await assert.rejects(async () => { + for await (const unused of stream) void unused + }, /already gone/) + }) + + it('resets the idle deadline while chunks continue arriving', async () => { + const { stream } = createTimedReadable( + async function* () { + for (let index = 0; index < 3; index += 1) { + await new Promise((resolve) => setTimeout(resolve, 10)) + yield Buffer.from(String(index)) + } + }, + { idleTimeoutMs: 20, totalTimeoutMs: 100 }, + () => new Error('timeout') + ) + + const chunks: Buffer[] = [] + for await (const chunk of stream) chunks.push(Buffer.from(chunk)) + assert.equal(Buffer.concat(chunks).toString(), '012') + }) }) From 6781a0489586629ea4024466bc0f5a3c703cd05e Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 31 Aug 2026 18:10:20 +0300 Subject: [PATCH 139/167] feat: add download concurrency limits and related configurations --- README.md | 18 ++++-- config.default.json5 | 5 +- docs/openapi.yaml | 6 +- docs/storage-lifecycle.md | 19 +++--- src/api/file.ts | 3 +- src/api/nodeRoutes.ts | 10 ++- src/api/storage.ts | 5 +- src/config.ts | 12 ++++ src/health/service.ts | 4 +- src/health/state.ts | 50 ++++++++++++--- src/middleware/downloadAdmission.ts | 27 ++++++++ src/middleware/downloadGuards.ts | 5 ++ src/observability.ts | 3 +- src/replication.cron.ts | 52 +++++++-------- src/storage/config.ts | 20 +++++- src/storage/limits.ts | 4 +- src/storage/repairCycleDriver.ts | 98 +++++++++++++++++++++++++++++ src/storage/service.ts | 5 +- src/storage/state.ts | 3 + src/storage/sweep.ts | 6 +- src/utils/file.ts | 6 +- test/config.test.ts | 8 +++ test/downloadAdmission.test.ts | 50 +++++++++++++++ test/healthState.test.ts | 23 ++++++- test/nodeRoutes.test.ts | 26 ++++++-- test/repairCycleDriver.test.ts | 63 +++++++++++++++++++ test/replication.test.ts | 3 +- test/retrieval.test.ts | 3 +- test/storageConfig.test.ts | 9 +++ 29 files changed, 468 insertions(+), 78 deletions(-) create mode 100644 src/middleware/downloadAdmission.ts create mode 100644 src/middleware/downloadGuards.ts create mode 100644 src/storage/repairCycleDriver.ts create mode 100644 test/downloadAdmission.test.ts create mode 100644 test/repairCycleDriver.test.ts diff --git a/README.md b/README.md index 2c953ad..096c5ff 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "findFileTimeout": 20000, "downloadIdleTimeout": 20000, "downloadMinBytesPerSecond": 32768, + "downloadMaxDurationMs": 14400000, "cors": { "allowedOrigins": ["https://adm.im", "https://*.adamant.im", "http://localhost:8080"] }, @@ -67,6 +68,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "storage": { "maxRequestSizeBytes": 536870912, "maxConcurrentUploads": 32, + "maxConcurrentDownloads": 64, "diskReserveBytes": 5368709120, "confirmationRequired": false, "temporaryTtlMs": 86400000, @@ -89,7 +91,8 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "requestTimeoutMs": 30000, "repairEnabled": true, "repairSchedule": "0 */30 * * * *", - "repairBatchDelayMs": 1000 + "repairBatchDelayMs": 1000, + "repairProbeConcurrency": 4 }, "health": { "checkpointIntervalMs": 60000, @@ -105,8 +108,11 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, `findFileTimeout` bounds discovery, while `downloadIdleTimeout` (defaulting to `findFileTimeout`) bounds a stalled transfer. `downloadMinBytesPerSecond` -(default `32768`) derives a complete-transfer deadline from the file size, so a -large active download is not cut off by the short discovery timeout. +(default `32768`) derives a complete-transfer deadline from the file size, while +`downloadMaxDurationMs` (default four hours) provides an absolute ceiling. +`storage.maxConcurrentDownloads` (default `64`) bounds sockets and UnixFS +iterators globally. Discovery, stat, and streaming have separate bounds, so a +failed request may spend more than one `findFileTimeout` before returning `408`. `storage`, `replication`, and `health` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Storage and replication are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. @@ -127,6 +133,7 @@ Set the generated value as `adminApiKey`. A missing or empty key fails closed: a - Use exact origins such as `https://adm.im` or any-depth subdomain suffix wildcards such as `https://*.adamant.im` - Set `adminApiKey` before using any administrative API - Leave `trustProxy` as `false` for direct connections; configure exact proxy addresses, CIDR ranges, or a verified hop count behind a proxy +- Migrate operator scripts and dashboards from the former detailed `GET /api/node/info` response to authenticated `GET /api/node/details`; `/info` is now the public legacy PWA/iOS contract - Set `enableDebugApi: true` only when the authenticated debug route is operationally required - Tune the endpoint-specific `rateLimits` for the deployment perimeter - Review `storage.diskReserveBytes` and `storage.maxRequestSizeBytes` for the deployment volume; the defaults suit a dedicated disk @@ -403,6 +410,7 @@ Example response: "backlog": 0 }, "checks": { + "checkpointFresh": true, "helia": true, "startupReconciliation": true, "storageFresh": true, @@ -417,7 +425,9 @@ The endpoint always returns `200`; consumers must inspect `state`. `height` is a `membership.version` identifies the configured peer-set epoch. Changing the node list resets the persisted checkpoint for that node: `height` remains `0` until the first valid checkpoint under the new membership. Clients must compare heights only when membership versions match and treat a version change as a new epoch. -Repair runs in bounded 50-record passes. A scheduled run starts a full cycle immediately and continues its remaining passes after `replication.repairBatchDelayMs`; `repairSchedule` controls when a new completed cycle is refreshed. Set `health.repairMaxAgeMs` longer than the largest expected full-cycle duration plus the schedule interval. An incomplete or backlogged cycle intentionally keeps health degraded. +During a staged deployment, older peers do not implement the health protocol. Set `health.requiredPeerCount` to `0` on the transitioning fleet, then raise it after every required peer is upgraded; otherwise prolonged `degraded` health is the expected fail-safe result. + +Repair runs in bounded 50-record passes. The same selected batch feeds both local-holder repair and released-record rescue. A scheduled run starts a full cycle immediately and continues its remaining passes after `replication.repairBatchDelayMs`; this delay and `replication.repairProbeConcurrency` control peer load, while `repairSchedule` controls when a new completed cycle is refreshed. Set `health.repairMaxAgeMs` longer than the largest expected full-cycle duration plus the schedule interval. An incomplete or backlogged cycle intentionally keeps health degraded. ### Get legacy client node information diff --git a/config.default.json5 b/config.default.json5 index 9e9312c..323a99d 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -36,6 +36,7 @@ // A transfer may run longer than discovery while chunks keep arriving. downloadIdleTimeout: 20000, downloadMinBytesPerSecond: 32768, + downloadMaxDurationMs: 14400000, // 4 h absolute ceiling for one response cors: { // Exact origins and left-most subdomain wildcards are supported. allowedOrigins: [ @@ -64,6 +65,7 @@ storage: { maxRequestSizeBytes: 536870912, // 512 MiB. Combined size of all files in one request maxConcurrentUploads: 32, // Uploads writing into the blockstore at the same time; over this, 429 + maxConcurrentDownloads: 64, // Downloads holding a socket and UnixFS iterator; over this, 429 diskReserveBytes: 5368709120, // 5 GiB of free space uploads must never consume // true keeps an upload temporary until POST /api/file/:cid/confirm. // Enabling it is a protocol change: clients must perform that call. @@ -101,7 +103,8 @@ repairEnabled: true, repairSchedule: '0 */30 * * * *', // Bounded passes continue until a complete repair cycle is recorded. - repairBatchDelayMs: 1000 + repairBatchDelayMs: 1000, + repairProbeConcurrency: 4 }, health: { checkpointIntervalMs: 60000, diff --git a/docs/openapi.yaml b/docs/openapi.yaml index cfa1222..4a8c16a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -99,7 +99,7 @@ paths: '408': description: Discovery or complete retrieval timed out '429': - description: Read rate limit exceeded + description: Read rate or global download concurrency limit exceeded /api/file/{cid}/status: parameters: - $ref: '#/components/parameters/Cid' @@ -195,6 +195,7 @@ components: state: type: string enum: [starting, ready, stale, degraded] + description: Stale only after checkpoint age exceeds maxAgeMs; the exact limit is valid height: type: integer minimum: 0 @@ -210,6 +211,7 @@ components: maxAgeMs: { type: integer, minimum: 1000 } membership: type: object + description: Peer evidence captured when checkpoint.observedAt was evaluated required: [version, requiredPeers, attestedPeers] properties: version: @@ -220,6 +222,7 @@ components: attestedPeers: { type: integer, minimum: 0 } startup: type: object + description: Startup evidence captured when checkpoint.observedAt was evaluated required: [complete, healthy] properties: complete: { type: boolean } @@ -241,6 +244,7 @@ components: backlog: { type: integer, minimum: 0 } checks: type: object + description: Storage and repair freshness are updated at read time; other checks describe checkpoint.observedAt additionalProperties: { type: boolean } HttpMetrics: type: object diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 24ff9e3..2ba290f 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -13,13 +13,14 @@ default, so a configuration file written before this feature keeps working. See An upload is refused before a single block reaches the blockstore when any of these limits is exceeded. -| Limit | Option | Response | -| ---------------------- | ------------------------------ | -------- | -| Concurrent uploads | `storage.maxConcurrentUploads` | `429` | -| Aggregate request size | `storage.maxRequestSizeBytes` | `413` | -| Disk reserve | `storage.diskReserveBytes` | `507` | -| Files per request | `maxFileCount` | `400` | -| Single file size | `uploadLimitSizeBytes` | `400` | +| Limit | Option | Response | +| ---------------------- | -------------------------------- | -------- | +| Concurrent uploads | `storage.maxConcurrentUploads` | `429` | +| Concurrent downloads | `storage.maxConcurrentDownloads` | `429` | +| Aggregate request size | `storage.maxRequestSizeBytes` | `413` | +| Disk reserve | `storage.diskReserveBytes` | `507` | +| Files per request | `maxFileCount` | `400` | +| Single file size | `uploadLimitSizeBytes` | `400` | The first three are checked by the upload guard before the multipart parser runs. The last two are enforced by the parser itself through @@ -458,7 +459,9 @@ The repair job (`replication.repairSchedule`, or `POST /api/storage/repair`) lists confirmed files this node holds whose designated peers have not all acknowledged, and places the missing copies again. -Each scheduled pass remains bounded to 50 confirmed records. Its sorted cursor and +Each scheduled pass remains bounded to 50 confirmed records shared by local-holder +repair and released-record rescue. The inter-pass delay and probe concurrency +bound sustained peer load. Its sorted cursor and aggregate result are persisted under the node datastore, so restart resumes the same cycle. A repair checkpoint is recorded only after a pass reaches the end of the candidate set; a pass over one batch is not presented as full-fleet diff --git a/src/api/file.ts b/src/api/file.ts index 2b951e4..e1ccacb 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -3,6 +3,7 @@ import { config } from '../config.js' import { helia } from '../helia.js' import { admitUpload, getUploadSession } from '../middleware/uploadGuards.js' import { readLimiter, uploadLimiter } from '../middleware/rateLimiter.js' +import { admitDownload } from '../middleware/downloadGuards.js' import { multerStorage } from '../multer.js' import { abortUploadedReplicas, @@ -99,7 +100,7 @@ router.get('/:cid/status', readLimiter, async (req, res, next) => { } }) -router.get('/:cid', readLimiter, async (req, res, next) => { +router.get('/:cid', readLimiter, admitDownload, async (req, res, next) => { const requestController = new AbortController() res.once('close', () => { if (!res.writableEnded) { diff --git a/src/api/nodeRoutes.ts b/src/api/nodeRoutes.ts index d98be12..a7aafab 100644 --- a/src/api/nodeRoutes.ts +++ b/src/api/nodeRoutes.ts @@ -33,8 +33,7 @@ export function createNodeRouters(deps: NodeRouteDependencies): { const publicNodeRouter = Router() const nodeRouter = Router() - publicNodeRouter.get('/health', (req, res, next) => { - void req + publicNodeRouter.get('/health', (_req, res, next) => { try { res.status(200).send({ version: deps.version, @@ -47,8 +46,7 @@ export function createNodeRouters(deps: NodeRouteDependencies): { }) /** Legacy PWA/iOS contract. Keep public and free of node identity or operator data. */ - publicNodeRouter.get('/info', deps.readLimiter, (req, res, next) => { - void req + publicNodeRouter.get('/info', deps.readLimiter, (_req, res, next) => { try { res.send({ version: deps.version, @@ -62,8 +60,7 @@ export function createNodeRouters(deps: NodeRouteDependencies): { }) /** Detailed node identity and storage report for authenticated operators. */ - nodeRouter.get('/details', (req, res, next) => { - void req + nodeRouter.get('/details', (_req, res, next) => { try { const storage = deps.getStorageMetrics() res.send({ @@ -79,6 +76,7 @@ export function createNodeRouters(deps: NodeRouteDependencies): { availableBytes: storage.availableBytes, reservedBytes: storage.reservedBytes, health: { + // Keep the complete public Health schema so the OpenAPI $ref remains exact. version: deps.version, uptimeMs: deps.uptimeMs(), ...deps.getHealthSnapshot() diff --git a/src/api/storage.ts b/src/api/storage.ts index 3d9e22d..8ffc46e 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -8,7 +8,7 @@ import { import { readLimiter } from '../middleware/rateLimiter.js' import { getReplicationState, - repairUnderReplicatedFiles, + runManualReplicationRepair, ReplicationRepairBusyError } from '../replication.cron.js' import { getStorageMetrics } from '../storage/metrics.js' @@ -45,6 +45,7 @@ router.get('/policy', readLimiter, (req, res) => { uploadLimitSizeBytes: config.uploadLimitSizeBytes, maxRequestSizeBytes: config.storage.maxRequestSizeBytes, maxConcurrentUploads: config.storage.maxConcurrentUploads, + maxConcurrentDownloads: config.storage.maxConcurrentDownloads, confirmationRequired: config.storage.confirmationRequired, temporaryTtlMs: config.storage.temporaryTtlMs, durability: config.replication.enabled @@ -105,7 +106,7 @@ storageAdminRouter.post('/gc', async (req, res, next) => { /** Detect and repair under-replicated durable content on demand. */ storageAdminRouter.post('/repair', async (req, res, next) => { try { - res.send(await repairUnderReplicatedFiles()) + res.send(await runManualReplicationRepair()) } catch (err) { if (err instanceof ReplicationRepairBusyError) { return res.status(409).send({ error: 'Replication repair is already running' }) diff --git a/src/config.ts b/src/config.ts index 5a47b0c..35f4d47 100644 --- a/src/config.ts +++ b/src/config.ts @@ -105,6 +105,8 @@ export interface Config { downloadIdleTimeout: number /** Minimum sustained rate used to derive a size-aware complete-transfer deadline. */ downloadMinBytesPerSecond: number + /** Absolute ceiling for one complete download response. */ + downloadMaxDurationMs: number cors: { /** Exact origins and left-most subdomain wildcards; see `src/security/cors.ts`. */ allowedOrigins: string[] @@ -342,6 +344,15 @@ export function validateConfig(raw: unknown): Config { 32 * 1024, 1 ) + const downloadMaxDurationMs = optionalInteger( + root.downloadMaxDurationMs, + 'downloadMaxDurationMs', + 4 * 60 * 60 * 1_000, + 1 + ) + if (downloadMaxDurationMs < downloadIdleTimeout) { + fail('downloadMaxDurationMs', 'must be greater than or equal to downloadIdleTimeout') + } const bootstrap = requireStringArray(peerDiscovery.bootstrap, 'peerDiscovery.bootstrap') // Owns cors, trustProxy, adminApiKey, enableDebugApi, rateLimits, @@ -388,6 +399,7 @@ export function validateConfig(raw: unknown): Config { findFileTimeout, downloadIdleTimeout, downloadMinBytesPerSecond, + downloadMaxDurationMs, cors: { allowedOrigins: cors.allowedOrigins as string[] }, trustProxy: (root.trustProxy ?? false) as TrustProxySetting, rateLimits: root.rateLimits as Config['rateLimits'], diff --git a/src/health/service.ts b/src/health/service.ts index 3f363d6..2617c19 100644 --- a/src/health/service.ts +++ b/src/health/service.ts @@ -73,7 +73,7 @@ async function countAttestations(now: number): Promise { export function runHealthCheckpoint(): Promise { if (inFlight !== undefined) { rerunRequested = true - return Promise.resolve(snapshot) + return Promise.resolve(refreshHealthSnapshot(snapshot, Date.now(), config.health)) } const run = (async () => { @@ -178,5 +178,5 @@ export async function stopHealthService(): Promise { /** Cached, read-only response for the always-200 health endpoint. */ export function getHealthSnapshot(): HealthSnapshot { - return refreshHealthSnapshot(snapshot, Date.now()) + return refreshHealthSnapshot(snapshot, Date.now(), config.health) } diff --git a/src/health/state.ts b/src/health/state.ts index a632869..839756a 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -59,6 +59,7 @@ export interface HealthSnapshot { backlog: number } checks: { + checkpointFresh: boolean helia: boolean startupReconciliation: boolean storageFresh: boolean @@ -88,7 +89,7 @@ export function evaluateHealth( policy: HealthConfig, input: HealthInputs ): { snapshot: HealthSnapshot; completed?: HealthCheckpoint } { - const checks = { + const prerequisiteChecks = { helia: input.heliaReady, startupReconciliation: input.startupComplete && input.startupHealthy, storageFresh: @@ -103,7 +104,7 @@ export function evaluateHealth( input.now - input.repairCompletedAt <= policy.repairMaxAgeMs), peerAttestations: input.attestedPeers >= policy.requiredPeerCount } - const complete = Object.values(checks).every(Boolean) + const complete = Object.values(prerequisiteChecks).every(Boolean) const previousAge = input.previous === null ? null : input.now - input.previous.completedAt const state: HealthState = complete ? 'ready' @@ -123,6 +124,13 @@ export function evaluateHealth( attestedPeers: input.attestedPeers } : undefined + const checkpointObservedAt = completed?.completedAt ?? input.previous?.completedAt ?? null + const checks = { + checkpointFresh: + checkpointObservedAt !== null && + input.now - checkpointObservedAt <= policy.maxCheckpointAgeMs, + ...prerequisiteChecks + } return { snapshot: { @@ -131,8 +139,8 @@ export function evaluateHealth( timestamp: input.now, checkpoint: { intervalMs: policy.checkpointIntervalMs, - observedAt: completed?.completedAt ?? input.previous?.completedAt ?? null, - ageMs: age(input.now, completed?.completedAt ?? input.previous?.completedAt ?? null), + observedAt: checkpointObservedAt, + ageMs: age(input.now, checkpointObservedAt), maxAgeMs: policy.maxCheckpointAgeMs }, membership: { @@ -161,9 +169,28 @@ export function evaluateHealth( } } -/** Refresh elapsed ages and stale state without performing checkpoint I/O. */ -export function refreshHealthSnapshot(current: HealthSnapshot, timestamp: number): HealthSnapshot { +/** Refresh elapsed ages and freshness checks without performing checkpoint I/O. */ +export function refreshHealthSnapshot( + current: HealthSnapshot, + timestamp: number, + policy: Pick +): HealthSnapshot { const checkpointAge = age(timestamp, current.checkpoint.observedAt) + const storageAge = age(timestamp, current.storage.measuredAt) + const repairAge = age(timestamp, current.replication.lastCompleteAt) + const checks = { + ...current.checks, + checkpointFresh: checkpointAge !== null && checkpointAge <= current.checkpoint.maxAgeMs, + storageFresh: + current.checks.storageFresh && storageAge !== null && storageAge <= policy.storageMaxAgeMs, + repairFresh: + !current.replication.repairRequired || + (current.checks.repairFresh && + current.replication.backlog === 0 && + repairAge !== null && + repairAge <= policy.repairMaxAgeMs) + } + const freshnessFailed = !checks.storageFresh || !checks.repairFresh return { ...current, @@ -172,7 +199,9 @@ export function refreshHealthSnapshot(current: HealthSnapshot, timestamp: number checkpointAge !== null && checkpointAge > current.checkpoint.maxAgeMs ? 'stale' - : current.state, + : current.state === 'ready' && freshnessFailed + ? 'degraded' + : current.state, timestamp, checkpoint: { ...current.checkpoint, @@ -180,11 +209,12 @@ export function refreshHealthSnapshot(current: HealthSnapshot, timestamp: number }, storage: { ...current.storage, - measurementAgeMs: age(timestamp, current.storage.measuredAt) + measurementAgeMs: storageAge }, replication: { ...current.replication, - ageMs: age(timestamp, current.replication.lastCompleteAt) - } + ageMs: repairAge + }, + checks } } diff --git a/src/middleware/downloadAdmission.ts b/src/middleware/downloadAdmission.ts new file mode 100644 index 0000000..d573610 --- /dev/null +++ b/src/middleware/downloadAdmission.ts @@ -0,0 +1,27 @@ +import type { RequestHandler } from 'express' + +export interface DownloadConcurrencyLimiter { + tryAcquire(): boolean + release(): void +} + +/** Build a global download concurrency guard that releases slots on every response exit. */ +export function createDownloadAdmission(limiter: DownloadConcurrencyLimiter): RequestHandler { + return (_req, res, next) => { + if (!limiter.tryAcquire()) { + res.set('Retry-After', '5') + res.status(429).send({ error: 'Too many concurrent downloads. Please try again later.' }) + return + } + + let released = false + const release = (): void => { + if (released) return + released = true + limiter.release() + } + res.once('finish', release) + res.once('close', release) + next() + } +} diff --git a/src/middleware/downloadGuards.ts b/src/middleware/downloadGuards.ts new file mode 100644 index 0000000..2fbefda --- /dev/null +++ b/src/middleware/downloadGuards.ts @@ -0,0 +1,5 @@ +import { downloadLimiter } from '../storage/state.js' +import { createDownloadAdmission } from './downloadAdmission.js' + +/** Production global concurrency guard for public file retrieval. */ +export const admitDownload = createDownloadAdmission(downloadLimiter) diff --git a/src/observability.ts b/src/observability.ts index f6a1a1c..b450ec9 100644 --- a/src/observability.ts +++ b/src/observability.ts @@ -17,8 +17,7 @@ const metrics: HttpMetrics = { } /** Bounded HTTP counters with no path, CID, IP, or user-controlled labels. */ -export const collectHttpMetrics: RequestHandler = (req, res, next) => { - void req +export const collectHttpMetrics: RequestHandler = (_req, res, next) => { const started = performance.now() let settled = false metrics.requests += 1 diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 48935ca..89dacae 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -7,6 +7,7 @@ import { datastore } from './store.js' import { Key } from 'interface-datastore' import { createHash } from 'node:crypto' import { membershipVersion } from './health/membership.js' +import { RepairCycleDriver, type RepairTrigger } from './storage/repairCycleDriver.js' let running = false let lastReport: RepairReport | null = null @@ -164,46 +165,45 @@ export async function repairUnderReplicatedFiles(): Promise { } export const replicationRepairCron = new CronJob(config.replication.repairSchedule, () => { - void runScheduledRepair() + repairCycleDriver.triggerSchedule() }) -let stopping = false -let continuationTimer: NodeJS.Timeout | undefined -let scheduledInFlight: Promise | undefined +function logRepairPass(trigger: RepairTrigger): void { + logger.info( + { + event: 'replication_repair_pass', + trigger, + cursor: evidence?.cursor ?? null + }, + trigger === 'continuation' + ? 'Continuing replication repair cycle' + : 'Starting replication repair cycle' + ) +} -async function runScheduledRepair(): Promise { - if (stopping || running || continuationTimer !== undefined) return +const repairCycleDriver = new RepairCycleDriver({ + delayMs: config.replication.repairBatchDelayMs, + runPass: repairUnderReplicatedFiles, + busyError: () => new ReplicationRepairBusyError(), + onStart: logRepairPass, + onError: (err) => logger.error(`${err.message}\n${err.stack}`) +}) - logger.info('[Cron] Running "replicationRepair" cronjob.') - const run = repairUnderReplicatedFiles() - .then((report) => { - if (stopping || report.cycleCompleted) return - continuationTimer = setTimeout(() => { - continuationTimer = undefined - void runScheduledRepair() - }, config.replication.repairBatchDelayMs) - continuationTimer.unref() - }) - .catch((err: Error) => logger.error(`${err.message}\n${err.stack}`)) - scheduledInFlight = run - await run - if (scheduledInFlight === run) scheduledInFlight = undefined +/** Run one manual pass without interrupting automatic cycle continuation. */ +export function runManualReplicationRepair(): Promise { + return repairCycleDriver.runManual() } /** Start the periodic repair job and immediately begin the first full cycle. */ export function startReplicationRepair(): void { - stopping = false replicationRepairCron.start() - void runScheduledRepair() + repairCycleDriver.start() } /** Stop scheduled repair work and wait for the active bounded pass. */ export async function stopReplicationRepair(): Promise { - stopping = true replicationRepairCron.stop() - if (continuationTimer !== undefined) clearTimeout(continuationTimer) - continuationTimer = undefined - await scheduledInFlight + await repairCycleDriver.stop() } export function getReplicationState() { diff --git a/src/storage/config.ts b/src/storage/config.ts index a0ce99e..bad2411 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -43,6 +43,8 @@ export interface StorageConfig { * `429` for a person who did nothing wrong. */ maxConcurrentUploads: number + /** Maximum number of file responses retrieving content at once. */ + maxConcurrentDownloads: number /** Free space on the blockstore filesystem that uploads must never consume. */ diskReserveBytes: number /** When true, an upload stays temporary until an authorized confirmation. */ @@ -85,11 +87,14 @@ export interface ReplicationConfig { repairSchedule: string /** Pause between bounded passes while completing one full repair cycle. */ repairBatchDelayMs: number + /** Released records probed concurrently during one repair pass. */ + repairProbeConcurrency: number } export const DEFAULT_STORAGE_CONFIG: StorageConfig = { maxRequestSizeBytes: 512 * MiB, maxConcurrentUploads: 32, + maxConcurrentDownloads: 64, diskReserveBytes: 5 * GiB, confirmationRequired: false, temporaryTtlMs: 24 * 60 * 60 * 1000, @@ -113,7 +118,8 @@ export const DEFAULT_REPLICATION_CONFIG: ReplicationConfig = { requestTimeoutMs: 30000, repairEnabled: true, repairSchedule: '0 */30 * * * *', - repairBatchDelayMs: 1_000 + repairBatchDelayMs: 1_000, + repairProbeConcurrency: 4 } function fail(path: string, expectation: string): never { @@ -211,6 +217,12 @@ export function resolveStorageConfig(raw: unknown, uploadLimitSizeBytes: number) defaults.maxConcurrentUploads, 1 ), + maxConcurrentDownloads: optionalInteger( + input.maxConcurrentDownloads, + 'storage.maxConcurrentDownloads', + defaults.maxConcurrentDownloads, + 1 + ), diskReserveBytes: optionalInteger( input.diskReserveBytes, 'storage.diskReserveBytes', @@ -325,6 +337,12 @@ export function resolveReplicationConfig(raw: unknown): ReplicationConfig { 'replication.repairBatchDelayMs', defaults.repairBatchDelayMs, 0 + ), + repairProbeConcurrency: optionalInteger( + input.repairProbeConcurrency, + 'replication.repairProbeConcurrency', + defaults.repairProbeConcurrency, + 1 ) } diff --git a/src/storage/limits.ts b/src/storage/limits.ts index cd4d68b..4d1e3ea 100644 --- a/src/storage/limits.ts +++ b/src/storage/limits.ts @@ -1,11 +1,11 @@ /** - * Pure upload admission helpers. + * Pure storage admission helpers. * * They are deliberately free of Express, Helia and configuration imports so * that every boundary they guard can be exercised in isolation. */ -/** Counts uploads whose HTTP request has not finished yet. */ +/** Counts operations whose request or transfer has not finished yet. */ export class ConcurrencyLimiter { private inFlight = 0 diff --git a/src/storage/repairCycleDriver.ts b/src/storage/repairCycleDriver.ts new file mode 100644 index 0000000..82582b3 --- /dev/null +++ b/src/storage/repairCycleDriver.ts @@ -0,0 +1,98 @@ +export type RepairTrigger = 'startup' | 'schedule' | 'continuation' | 'manual' + +export interface RepairPassResult { + cycleCompleted: boolean +} + +export interface RepairCycleDriverOptions { + delayMs: number + runPass: () => Promise + busyError: () => Error + onStart?: (trigger: RepairTrigger) => void + onError?: (error: Error) => void +} + +/** Coordinate scheduled, continued, and manual passes of one repair cycle. */ +export class RepairCycleDriver { + private active: Promise | undefined + private continuationTimer: NodeJS.Timeout | undefined + private stopped = true + + constructor(private readonly options: RepairCycleDriverOptions) {} + + /** Start periodic-cycle ownership and immediately run the first bounded pass. */ + start(): void { + this.stopped = false + void this.runAutomatic('startup') + } + + /** Start a pass for a cron schedule tick unless a cycle is already continuing. */ + triggerSchedule(): void { + void this.runAutomatic('schedule') + } + + /** Run an operator-requested pass through the same continuation driver. */ + runManual(): Promise { + if (this.active !== undefined) return Promise.reject(this.options.busyError()) + return this.run('manual') + } + + /** Stop future passes and wait for the active pass to finish. */ + async stop(): Promise { + this.stopped = true + if (this.continuationTimer !== undefined) clearTimeout(this.continuationTimer) + this.continuationTimer = undefined + await this.active + } + + private async runAutomatic(trigger: Exclude): Promise { + if (this.stopped) return + if (this.continuationTimer !== undefined && trigger !== 'continuation') return + if (this.active !== undefined) { + if (trigger === 'continuation') this.scheduleContinuation() + return + } + + try { + await this.run(trigger) + } catch (error) { + this.options.onError?.(error as Error) + } + } + + private run(trigger: RepairTrigger): Promise { + this.options.onStart?.(trigger) + const pass = this.options.runPass() + this.active = pass + + void pass.then( + (report) => { + if (report.cycleCompleted) this.cancelContinuation() + else this.scheduleContinuation() + }, + () => undefined + ) + void pass.then(this.clearActive(pass), this.clearActive(pass)) + return pass + } + + private clearActive(pass: Promise): () => void { + return () => { + if (this.active === pass) this.active = undefined + } + } + + private scheduleContinuation(): void { + if (this.stopped || this.continuationTimer !== undefined) return + this.continuationTimer = setTimeout(() => { + this.continuationTimer = undefined + void this.runAutomatic('continuation') + }, this.options.delayMs) + this.continuationTimer.unref() + } + + private cancelContinuation(): void { + if (this.continuationTimer !== undefined) clearTimeout(this.continuationTimer) + this.continuationTimer = undefined + } +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 9416da1..d740bb0 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -937,7 +937,10 @@ async function rescueOrphanedFiles( } await Promise.all( - Array.from({ length: Math.min(4, candidates.length) }, async () => processNext()) + Array.from( + { length: Math.min(config.replication.repairProbeConcurrency, candidates.length) }, + async () => processNext() + ) ) rescued.sort() diff --git a/src/storage/state.ts b/src/storage/state.ts index b8a8a0a..ec6622a 100644 --- a/src/storage/state.ts +++ b/src/storage/state.ts @@ -16,6 +16,9 @@ export const storageOperationLock = new StorageOperationLock() /** Guards how many uploads may write into the blockstore at the same time. */ export const uploadLimiter = new ConcurrencyLimiter(config.storage.maxConcurrentUploads) +/** Guards how many public downloads may hold a socket and UnixFS iterator. */ +export const downloadLimiter = new ConcurrencyLimiter(config.storage.maxConcurrentDownloads) + /** * Share of the upload limit that incoming copies may occupy. * diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index 2e4af17..a746891 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -1,9 +1,9 @@ /** * How many files each periodic sweep looks at per pass. * - * Every sweep asks peers something, so an unbounded one turns a node with many - * records into a source of steady chatter. Repair may transfer a file, so it - * gets the smallest batch; the others only exchange short messages. + * The batch caps memory, fan-out, and work claimed by one pass. Repair chains + * passes until a cycle completes; `repairBatchDelayMs` and probe concurrency + * separately control its sustained peer traffic. */ export const SWEEP_BATCHES = { repair: 50, demote: 200 } diff --git a/src/utils/file.ts b/src/utils/file.ts index 9ec8bc7..b9cc579 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -54,7 +54,11 @@ export function downloadFile( idleTimeoutMs, totalTimeoutMs: options.totalTimeoutMs ?? - Math.min(2_147_483_647, Math.max(idleTimeoutMs, sizeAwareTimeoutMs)) + Math.min( + 2_147_483_647, + config.downloadMaxDurationMs, + Math.max(idleTimeoutMs, sizeAwareTimeoutMs) + ) }, () => new FileNotFoundError('Unable to retrieve the file. Request timed out.'), options.signal diff --git a/test/config.test.ts b/test/config.test.ts index 44bf5c9..03e326b 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -54,6 +54,7 @@ describe('validateConfig', () => { assert.equal(parsed.health.requiredPeerCount, 0) assert.equal(parsed.downloadIdleTimeout, 20_000) assert.equal(parsed.downloadMinBytesPerSecond, 32 * 1024) + assert.equal(parsed.downloadMaxDurationMs, 4 * 60 * 60 * 1_000) }) it('ignores unknown keys so deployments can carry extras', () => { @@ -139,6 +140,13 @@ describe('validateConfig', () => { raw.downloadMinBytesPerSecond = 0 }, /downloadMinBytesPerSecond/ + ], + [ + 'download maximum duration is below the idle timeout', + (raw) => { + raw.downloadMaxDurationMs = 1 + }, + /downloadMaxDurationMs/ ] ] diff --git a/test/downloadAdmission.test.ts b/test/downloadAdmission.test.ts new file mode 100644 index 0000000..34b6dc1 --- /dev/null +++ b/test/downloadAdmission.test.ts @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict' +import type { AddressInfo } from 'node:net' +import { after, before, describe, it } from 'node:test' +import express from 'express' +import { createDownloadAdmission } from '../src/middleware/downloadAdmission.js' +import { ConcurrencyLimiter } from '../src/storage/limits.js' + +describe('download concurrency admission', () => { + let url = '' + let close: (() => Promise) | undefined + let finishFirst!: () => void + + before(async () => { + const limiter = new ConcurrencyLimiter(1) + const app = express() + let requests = 0 + app.get('/file', createDownloadAdmission(limiter), (_req, res) => { + requests += 1 + if (requests === 1) { + finishFirst = () => res.send('first') + return + } + res.send('next') + }) + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + url = `http://127.0.0.1:${address.port}` + close = () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + }) + + after(async () => close?.()) + + it('returns 429 while the global slot is occupied and releases it on completion', async () => { + const first = fetch(`${url}/file`) + while (finishFirst === undefined) await new Promise((resolve) => setImmediate(resolve)) + + const rejected = await fetch(`${url}/file`) + assert.equal(rejected.status, 429) + assert.equal(rejected.headers.get('retry-after'), '5') + + finishFirst() + assert.equal((await first).status, 200) + assert.equal((await fetch(`${url}/file`)).status, 200) + }) +}) diff --git a/test/healthState.test.ts b/test/healthState.test.ts index f8494f0..2a16115 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -103,10 +103,31 @@ describe('health checkpoint state', () => { it('reports a cached ready checkpoint as stale when its age expires', () => { const ready = evaluateHealth(policy, healthy(12_345)).snapshot - const refreshed = refreshHealthSnapshot(ready, 16_000) + const refreshed = refreshHealthSnapshot(ready, 16_000, policy) assert.equal(refreshed.state, 'stale') + assert.equal(refreshed.checks.checkpointFresh, false) assert.equal(refreshed.checkpoint.ageMs, 3_655) assert.equal(refreshed.height, ready.height) }) + + it('expires cached storage and repair checks before the next checkpoint tick', () => { + const ready = evaluateHealth(policy, healthy(12_345)).snapshot + const storageExpired = refreshHealthSnapshot(ready, 14_500, policy) + const repairExpired = refreshHealthSnapshot(ready, 17_500, policy) + + assert.equal(storageExpired.state, 'degraded') + assert.equal(storageExpired.checks.storageFresh, false) + assert.equal(storageExpired.checks.repairFresh, true) + assert.equal(repairExpired.state, 'stale') + assert.equal(repairExpired.checks.repairFresh, false) + }) + + it('keeps the maximum checkpoint age inclusive', () => { + const ready = evaluateHealth(policy, healthy(12_345)).snapshot + const boundary = refreshHealthSnapshot(ready, 15_345, policy) + + assert.notEqual(boundary.state, 'stale') + assert.equal(boundary.checkpoint.ageMs, boundary.checkpoint.maxAgeMs) + }) }) diff --git a/test/nodeRoutes.test.ts b/test/nodeRoutes.test.ts index 7076a1d..2082154 100644 --- a/test/nodeRoutes.test.ts +++ b/test/nodeRoutes.test.ts @@ -3,6 +3,7 @@ import type { AddressInfo } from 'node:net' import { after, before, describe, it } from 'node:test' import express from 'express' import { createNodeRouters } from '../src/api/nodeRoutes.js' +import { refreshHealthSnapshot } from '../src/health/state.js' import type { HealthSnapshot } from '../src/health/state.js' const health: HealthSnapshot = { @@ -15,6 +16,7 @@ const health: HealthSnapshot = { storage: { measuredAt: 12_345, measurementAgeMs: 0, reserveHealthy: true }, replication: { repairRequired: true, lastCompleteAt: 12_345, ageMs: 0, backlog: 0 }, checks: { + checkpointFresh: true, helia: true, startupReconciliation: true, storageFresh: true, @@ -28,15 +30,14 @@ describe('node HTTP contract', () => { let url = '' let close: (() => Promise) | undefined let limited = 0 + let healthNow = 12_345 before(async () => { const routers = createNodeRouters({ version: '1.2.3', now: () => 20_000, uptimeMs: () => 5_000, - readLimiter: (req, res, next) => { - void req - void res + readLimiter: (_req, _res, next) => { limited += 1 next() }, @@ -54,7 +55,11 @@ describe('node HTTP contract', () => { availableBytes: 6, reservedBytes: 7 }), - getHealthSnapshot: () => health, + getHealthSnapshot: () => + refreshHealthSnapshot(health, healthNow, { + storageMaxAgeMs: 2_000, + repairMaxAgeMs: 5_000 + }), getHttpMetrics: () => ({ requests: 1 }) }) const app = express() @@ -81,9 +86,22 @@ describe('node HTTP contract', () => { assert.equal(body.version, '1.2.3') assert.equal(body.state, 'ready') assert.equal(body.height, 12_000) + assert.equal('peerId' in body, false) + assert.equal('multiAddresses' in body, false) + assert.equal('http' in body, false) assert.equal(limited, 0) }) + it('downgrades the cached response to stale on an HTTP read', async () => { + healthNow = 16_000 + const response = await fetch(`${url}/api/node/health`) + const body = (await response.json()) as { state: string; checkpoint: { ageMs: number } } + + assert.equal(response.status, 200) + assert.equal(body.state, 'stale') + assert.equal(body.checkpoint.ageMs, 3_655) + }) + it('rate-limits the legacy public info route', async () => { assert.equal((await fetch(`${url}/api/node/info`)).status, 200) assert.equal(limited, 1) diff --git a/test/repairCycleDriver.test.ts b/test/repairCycleDriver.test.ts new file mode 100644 index 0000000..986fdc1 --- /dev/null +++ b/test/repairCycleDriver.test.ts @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { RepairCycleDriver } from '../src/storage/repairCycleDriver.js' + +function deferred(): { + promise: Promise + resolve: (value: T) => void +} { + let resolve!: (value: T) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + +describe('RepairCycleDriver', () => { + it('retries a continuation that fires during a manual pass', async () => { + const startupDone = deferred<{ cycleCompleted: boolean }>() + const manualDone = deferred<{ cycleCompleted: boolean }>() + const continuationDone = deferred() + let passes = 0 + const driver = new RepairCycleDriver({ + delayMs: 20, + busyError: () => new Error('busy'), + runPass: () => { + passes += 1 + if (passes === 1) return startupDone.promise + if (passes === 2) return manualDone.promise + continuationDone.resolve() + return Promise.resolve({ cycleCompleted: true }) + } + }) + + driver.start() + startupDone.resolve({ cycleCompleted: false }) + await startupDone.promise + await new Promise((resolve) => setImmediate(resolve)) + + const manual = driver.runManual() + // Let the pending automatic continuation fire while the manual pass owns the driver. + await new Promise((resolve) => setTimeout(resolve, 25)) + manualDone.resolve({ cycleCompleted: false }) + await manual + await continuationDone.promise + + assert.equal(passes, 3) + await driver.stop() + }) + + it('rejects a manual pass while automatic repair is active', async () => { + const active = deferred<{ cycleCompleted: boolean }>() + const driver = new RepairCycleDriver({ + delayMs: 1, + busyError: () => new Error('busy'), + runPass: () => active.promise + }) + + driver.start() + await assert.rejects(driver.runManual(), /busy/) + active.resolve({ cycleCompleted: true }) + await driver.stop() + }) +}) diff --git a/test/replication.test.ts b/test/replication.test.ts index 754fba6..e940a77 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -26,7 +26,8 @@ const baseConfig: ReplicationConfig = { requestTimeoutMs: 1000, repairEnabled: true, repairSchedule: '0 * * * * *', - repairBatchDelayMs: 1_000 + repairBatchDelayMs: 1_000, + repairProbeConcurrency: 4 } const peer = (name: string): ReplicationPeer => ({ diff --git a/test/retrieval.test.ts b/test/retrieval.test.ts index c4394d6..b56b0c9 100644 --- a/test/retrieval.test.ts +++ b/test/retrieval.test.ts @@ -22,7 +22,8 @@ const config: ReplicationConfig = { requestTimeoutMs: 1000, repairEnabled: true, repairSchedule: '0 * * * * *', - repairBatchDelayMs: 1_000 + repairBatchDelayMs: 1_000, + repairProbeConcurrency: 4 } const peer = (name: string): ReplicationPeer => ({ diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 01f3653..03a34e1 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -46,6 +46,7 @@ describe('resolveStorageConfig', () => { it('rejects limits that would disable a guarantee', () => { assert.throws(() => resolveStorageConfig({ maxConcurrentUploads: 0 }, UPLOAD_LIMIT)) + assert.throws(() => resolveStorageConfig({ maxConcurrentDownloads: 0 }, UPLOAD_LIMIT)) assert.throws(() => resolveStorageConfig({ temporaryTtlMs: 0 }, UPLOAD_LIMIT)) assert.throws(() => resolveStorageConfig({ diskReserveBytes: -1 }, UPLOAD_LIMIT)) assert.throws(() => resolveStorageConfig({ confirmationRequired: 'yes' }, UPLOAD_LIMIT)) @@ -161,4 +162,12 @@ describe('resolveReplicationConfig', () => { assert.equal(resolveReplicationConfig({ repairBatchDelayMs: 0 }).repairBatchDelayMs, 0) assert.throws(() => resolveReplicationConfig({ repairBatchDelayMs: -1 }), /repairBatchDelayMs/) }) + + it('validates repair probe concurrency', () => { + assert.equal(resolveReplicationConfig({ repairProbeConcurrency: 8 }).repairProbeConcurrency, 8) + assert.throws( + () => resolveReplicationConfig({ repairProbeConcurrency: 0 }), + /repairProbeConcurrency/ + ) + }) }) From a143b9183320e011d0349d39d3d3f8d3a5dfbc6f Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 31 Aug 2026 18:54:37 +0300 Subject: [PATCH 140/167] fix: address review findings in repair driver, download limits, and health contract Refuse a manual repair pass while the cycle driver is stopped. An admin request arriving on a keep-alive connection after stopReplicationRepair had awaited the active pass could otherwise write cycle evidence nothing waits for, against a closing datastore. Retry a failed pass through the continuation timer instead of abandoning the cycle. The pass writes no cursor, so a retry resumes where it began, while dropping the cycle costs a whole schedule interval and health.repairMaxAgeMs is measured from cycle completion. A bounded failure budget stops the retry loop and reports the abandonment. Derive the pass log wording from the persisted cursor rather than the trigger, so it cannot contradict the cursor beside it. Add storage.maxConcurrentDownloadsPerClient so one address cannot hold every download slot for the length of its transfers, and refuse a downloadMaxDurationMs below the time uploadLimitSizeBytes needs at downloadMinBytesPerSecond, which would cut the largest files off mid-stream. Report admission-limiter occupancy on authenticated GET /api/node/details, so a capacity 429 is distinguishable from a rate-limit 429. Document the health snapshot read-time asymmetry, the adjacent-round tolerance, and what requiredPeerCount 0 costs during a staged rollout. Correct the checkpoint persistence wording, which claimed atomicity the datastore does not provide across keys. Cover the new behaviour with tests, including an end-to-end /api/node/details assertion through mountApiRoutes and createApiKeyAuth. Co-Authored-By: Claude Opus 5 --- README.md | 16 +++- config.default.json5 | 2 + docs/openapi.yaml | 41 ++++++++- src/api/node.ts | 4 +- src/api/nodeRoutes.ts | 5 +- src/config.ts | 14 +++ src/health/persistence.ts | 8 +- src/middleware/downloadAdmission.ts | 53 ++++++++++- src/middleware/downloadGuards.ts | 7 +- src/observability.ts | 9 ++ src/replication.cron.ts | 27 ++++-- src/storage/config.ts | 21 +++++ src/storage/repairCycleDriver.ts | 51 ++++++++++- src/storage/state.ts | 28 ++++++ test/config.test.ts | 9 ++ test/downloadAdmission.test.ts | 53 +++++++++++ test/nodeRoutes.test.ts | 133 +++++++++++++++++++++++++++- test/repairCycleDriver.test.ts | 75 ++++++++++++++++ test/storageConfig.test.ts | 9 ++ 19 files changed, 539 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 096c5ff..646b573 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, "maxRequestSizeBytes": 536870912, "maxConcurrentUploads": 32, "maxConcurrentDownloads": 64, + "maxConcurrentDownloadsPerClient": 8, "diskReserveBytes": 5368709120, "confirmationRequired": false, "temporaryTtlMs": 86400000, @@ -111,8 +112,15 @@ The whole configuration is validated at startup: a missing file, invalid JSON5, (default `32768`) derives a complete-transfer deadline from the file size, while `downloadMaxDurationMs` (default four hours) provides an absolute ceiling. `storage.maxConcurrentDownloads` (default `64`) bounds sockets and UnixFS -iterators globally. Discovery, stat, and streaming have separate bounds, so a -failed request may spend more than one `findFileTimeout` before returning `408`. +iterators globally, and `storage.maxConcurrentDownloadsPerClient` (default `8`) +keeps one address from holding every slot. Discovery, stat, and streaming have +separate bounds, so a failed request may spend more than one `findFileTimeout` +before returning `408`. + +Startup refuses a `downloadMaxDurationMs` below the time `uploadLimitSizeBytes` +needs at `downloadMinBytesPerSecond`: the ceiling wins over the size-aware +deadline, so a lower value would cut the largest permitted files off mid-stream +and report them as retrieval timeouts. `storage`, `replication`, and `health` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Storage and replication are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. @@ -423,9 +431,11 @@ Example response: The endpoint always returns `200`; consumers must inspect `state`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle with no known backlog, and the configured peer attestations all pass. It freezes on failure. Observation timestamps and ages let clients reject an absolutely stale cluster even when every node reports the same height. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. +State changes are deliberately asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` between checkpoints. Recovery is never decided on a read: returning to `ready` requires a successful checkpoint, so expect up to `health.checkpointIntervalMs` of lag after the underlying fault clears. `checkpointFresh`, `storageFresh`, and `repairFresh` follow the same rule; every other entry in `checks`, along with `membership` and `startup`, describes the moment identified by `checkpoint.observedAt` and not the moment of the request. Peers may attest an adjacent round across a boundary, so two healthy nodes can briefly report heights one `checkpointIntervalMs` apart. + `membership.version` identifies the configured peer-set epoch. Changing the node list resets the persisted checkpoint for that node: `height` remains `0` until the first valid checkpoint under the new membership. Clients must compare heights only when membership versions match and treat a version change as a new epoch. -During a staged deployment, older peers do not implement the health protocol. Set `health.requiredPeerCount` to `0` on the transitioning fleet, then raise it after every required peer is upgraded; otherwise prolonged `degraded` health is the expected fail-safe result. +During a staged deployment, older peers do not implement the health protocol. Set `health.requiredPeerCount` to `0` on the transitioning fleet, then raise it after every required peer is upgraded; otherwise prolonged `degraded` health is the expected fail-safe result. While it is `0`, `checks.peerAttestations` is always `true` and the checkpoint proves only this node's own prerequisites: network coverage is not validated until the threshold is raised, so treat the transition window as unverified for routing decisions that depend on it. Repair runs in bounded 50-record passes. The same selected batch feeds both local-holder repair and released-record rescue. A scheduled run starts a full cycle immediately and continues its remaining passes after `replication.repairBatchDelayMs`; this delay and `replication.repairProbeConcurrency` control peer load, while `repairSchedule` controls when a new completed cycle is refreshed. Set `health.repairMaxAgeMs` longer than the largest expected full-cycle duration plus the schedule interval. An incomplete or backlogged cycle intentionally keeps health degraded. diff --git a/config.default.json5 b/config.default.json5 index 323a99d..59f4753 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -66,6 +66,8 @@ maxRequestSizeBytes: 536870912, // 512 MiB. Combined size of all files in one request maxConcurrentUploads: 32, // Uploads writing into the blockstore at the same time; over this, 429 maxConcurrentDownloads: 64, // Downloads holding a socket and UnixFS iterator; over this, 429 + // Share of the above one client address may hold, so nobody can take every slot + maxConcurrentDownloadsPerClient: 8, diskReserveBytes: 5368709120, // 5 GiB of free space uploads must never consume // true keeps an upload temporary until POST /api/file/:cid/confirm. // Enabling it is a protocol change: clients must perform that call. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 4a8c16a..4288fd0 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -99,7 +99,7 @@ paths: '408': description: Discovery or complete retrieval timed out '429': - description: Read rate or global download concurrency limit exceeded + description: Read rate, per-client, or global download concurrency limit exceeded /api/file/{cid}/status: parameters: - $ref: '#/components/parameters/Cid' @@ -195,11 +195,17 @@ components: state: type: string enum: [starting, ready, stale, degraded] - description: Stale only after checkpoint age exceeds maxAgeMs; the exact limit is valid + description: >- + Downgrades happen at read time; recovery to ready needs a successful + checkpoint, so expect up to checkpoint.intervalMs of lag. Stale only + after checkpoint age exceeds maxAgeMs; the exact limit is valid height: type: integer minimum: 0 - description: Persisted Unix milliseconds at the start of the last valid fixed round + description: >- + Persisted Unix milliseconds at the start of the last valid fixed round. + Peers may attest an adjacent round across a boundary, so two healthy + nodes can report heights one intervalMs apart at a round change timestamp: { type: integer } checkpoint: type: object @@ -244,10 +250,11 @@ components: backlog: { type: integer, minimum: 0 } checks: type: object - description: Storage and repair freshness are updated at read time; other checks describe checkpoint.observedAt + description: Checkpoint, storage, and repair freshness are recomputed at read time; every other check describes checkpoint.observedAt additionalProperties: { type: boolean } HttpMetrics: type: object + description: Counters accumulated since process start, not a sliding window required: - requests - responses @@ -286,6 +293,7 @@ components: - reservedBytes - health - http + - concurrency properties: version: { type: string } timestamp: { type: integer } @@ -303,6 +311,31 @@ components: reservedBytes: { type: integer, minimum: 0 } health: { $ref: '#/components/schemas/Health' } http: { $ref: '#/components/schemas/HttpMetrics' } + concurrency: { $ref: '#/components/schemas/ConcurrencyMetrics' } + ConcurrencyMetrics: + type: object + description: Admission-limiter occupancy, so a capacity 429 is distinguishable from a rate-limit 429 + required: [uploads, incomingCopies, downloads] + properties: + uploads: + type: object + required: [active, limit] + properties: + active: { type: integer, minimum: 0 } + limit: { type: integer, minimum: 1 } + incomingCopies: + type: object + required: [active, limit] + properties: + active: { type: integer, minimum: 0 } + limit: { type: integer, minimum: 1 } + downloads: + type: object + required: [active, limit, perClientLimit] + properties: + active: { type: integer, minimum: 0 } + limit: { type: integer, minimum: 1 } + perClientLimit: { type: integer, minimum: 1 } LegacyNodeInfo: type: object required: diff --git a/src/api/node.ts b/src/api/node.ts index 7dd43a5..b379e16 100644 --- a/src/api/node.ts +++ b/src/api/node.ts @@ -2,6 +2,7 @@ import { packageJson } from '../config.js' import { getDiskUsageStats } from '../disk-usage.cron.js' import { helia } from '../helia.js' import { getStorageMetrics } from '../storage/metrics.js' +import { getConcurrencyMetrics } from '../storage/state.js' import { getHealthSnapshot } from '../health/service.js' import { getHttpMetrics } from '../observability.js' import { readLimiter } from '../middleware/rateLimiter.js' @@ -18,7 +19,8 @@ const routers = createNodeRouters({ getDiskUsageStats, getStorageMetrics, getHealthSnapshot, - getHttpMetrics + getHttpMetrics, + getConcurrencyMetrics }) export const publicNodeRouter = routers.publicNodeRouter diff --git a/src/api/nodeRoutes.ts b/src/api/nodeRoutes.ts index a7aafab..b7d3066 100644 --- a/src/api/nodeRoutes.ts +++ b/src/api/nodeRoutes.ts @@ -23,6 +23,8 @@ export interface NodeRouteDependencies { } getHealthSnapshot: () => HealthSnapshot getHttpMetrics: () => unknown + /** Admission-limiter occupancy; operator-only, like the byte-accurate figures. */ + getConcurrencyMetrics: () => unknown } /** Build public and administrative node routers from explicit runtime dependencies. */ @@ -81,7 +83,8 @@ export function createNodeRouters(deps: NodeRouteDependencies): { uptimeMs: deps.uptimeMs(), ...deps.getHealthSnapshot() }, - http: deps.getHttpMetrics() + http: deps.getHttpMetrics(), + concurrency: deps.getConcurrencyMetrics() }) } catch (err) { next(err) diff --git a/src/config.ts b/src/config.ts index 35f4d47..fa6bae4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -359,6 +359,20 @@ export function validateConfig(raw: unknown): Config { // uploadLimitSizeBytes and maxFileCount. Throws with its own message. validateSecurityConfig(root) + // The ceiling wins over the size-aware deadline, so one set below the time + // the largest permitted file needs would cut those transfers off mid-stream + // and report the generic retrieval timeout. Refuse it instead of silently + // making large files undownloadable. + const largestFileTransferMs = + Math.ceil((root.uploadLimitSizeBytes as number) / downloadMinBytesPerSecond) * 1_000 + if (downloadMaxDurationMs < largestFileTransferMs) { + fail( + 'downloadMaxDurationMs', + `must be at least ${largestFileTransferMs}, the time an ${String(root.uploadLimitSizeBytes)} ` + + `byte file needs at downloadMinBytesPerSecond ${downloadMinBytesPerSecond}` + ) + } + const cors = requireObject(root.cors, 'cors') // Owns the storage lifecycle and replication policy. Both sections are diff --git a/src/health/persistence.ts b/src/health/persistence.ts index ac545d6..5425d7a 100644 --- a/src/health/persistence.ts +++ b/src/health/persistence.ts @@ -34,7 +34,13 @@ export async function loadHealthCheckpoint(store: Datastore): Promise { +export interface DownloadAdmissionOptions { + /** + * In-flight downloads one client address may hold at once. + * + * The global limiter alone bounds what the node spends, not who spends it: + * a download slot is held for as long as the transfer runs, so without a + * per-client share one address can take every slot and leave the rest of + * the network with `429` until those transfers end. + */ + perClientLimit?: number + /** Client identity; defaults to the address Express resolved under `trust proxy`. */ + clientKey?: (req: Request) => string +} + +/** Build a download concurrency guard that releases slots on every response exit. */ +export function createDownloadAdmission( + limiter: DownloadConcurrencyLimiter, + options: DownloadAdmissionOptions = {} +): RequestHandler { + const { perClientLimit } = options + const clientKey = options.clientKey ?? ((req: Request) => req.ip ?? 'unknown') + + // An entry exists only while its client holds a global slot, so this map is + // bounded by the global limit and never accumulates addresses. + const heldPerClient = new Map() + + return (req, res, next) => { + const key = clientKey(req) + const held = heldPerClient.get(key) ?? 0 + + if (perClientLimit !== undefined && held >= perClientLimit) { + res.set('Retry-After', '5') + res + .status(429) + .send({ error: 'Too many concurrent downloads from this client. Please try again later.' }) + return + } + if (!limiter.tryAcquire()) { res.set('Retry-After', '5') res.status(429).send({ error: 'Too many concurrent downloads. Please try again later.' }) return } + heldPerClient.set(key, held + 1) + let released = false const release = (): void => { if (released) return released = true + + const remaining = (heldPerClient.get(key) ?? 1) - 1 + if (remaining > 0) { + heldPerClient.set(key, remaining) + } else { + heldPerClient.delete(key) + } + limiter.release() } res.once('finish', release) diff --git a/src/middleware/downloadGuards.ts b/src/middleware/downloadGuards.ts index 2fbefda..acd6d1f 100644 --- a/src/middleware/downloadGuards.ts +++ b/src/middleware/downloadGuards.ts @@ -1,5 +1,8 @@ +import { config } from '../config.js' import { downloadLimiter } from '../storage/state.js' import { createDownloadAdmission } from './downloadAdmission.js' -/** Production global concurrency guard for public file retrieval. */ -export const admitDownload = createDownloadAdmission(downloadLimiter) +/** Production concurrency guard for public file retrieval, global and per client. */ +export const admitDownload = createDownloadAdmission(downloadLimiter, { + perClientLimit: config.storage.maxConcurrentDownloadsPerClient +}) diff --git a/src/observability.ts b/src/observability.ts index b450ec9..a8cea8e 100644 --- a/src/observability.ts +++ b/src/observability.ts @@ -1,5 +1,14 @@ import type { RequestHandler } from 'express' +/** + * Counters accumulated since process start. + * + * They are deliberately not a sliding window: a window needs retained samples, + * and the point of this module is a fixed-size report. `averageResponseTimeMs` + * is therefore a lifetime figure, useful as a trend and not as a current + * latency reading. A collector scraping `GET /api/node/details` derives rates + * by differencing consecutive samples. + */ interface HttpMetrics { requests: number responses: Record<'2xx' | '3xx' | '4xx' | '5xx', number> diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 89dacae..4db25d9 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -168,16 +168,23 @@ export const replicationRepairCron = new CronJob(config.replication.repairSchedu repairCycleDriver.triggerSchedule() }) +/** + * Report one bounded pass. + * + * Whether a pass starts a cycle is decided by the persisted cursor, not by the + * trigger: a manual pass, and a schedule tick that follows an abandoned cycle, + * both resume the cursor a previous pass left behind. + */ function logRepairPass(trigger: RepairTrigger): void { + const cursor = evidence?.cursor ?? null + logger.info( { event: 'replication_repair_pass', trigger, - cursor: evidence?.cursor ?? null + cursor }, - trigger === 'continuation' - ? 'Continuing replication repair cycle' - : 'Starting replication repair cycle' + cursor === null ? 'Starting replication repair cycle' : 'Continuing replication repair cycle' ) } @@ -186,7 +193,17 @@ const repairCycleDriver = new RepairCycleDriver({ runPass: repairUnderReplicatedFiles, busyError: () => new ReplicationRepairBusyError(), onStart: logRepairPass, - onError: (err) => logger.error(`${err.message}\n${err.stack}`) + onError: (err) => logger.error(`${err.message}\n${err.stack}`), + onAbandon: (err, failures) => + logger.error( + { + event: 'replication_repair_cycle_abandoned', + failures, + cursor: evidence?.cursor ?? null, + err + }, + 'Replication repair cycle abandoned after repeated failures; it resumes on the next schedule' + ) }) /** Run one manual pass without interrupting automatic cycle continuation. */ diff --git a/src/storage/config.ts b/src/storage/config.ts index bad2411..c47f4b1 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -45,6 +45,13 @@ export interface StorageConfig { maxConcurrentUploads: number /** Maximum number of file responses retrieving content at once. */ maxConcurrentDownloads: number + /** + * Share of {@link maxConcurrentDownloads} one client address may hold. + * + * The global bound protects the node; this one protects everybody else from + * a single address holding every slot for the length of its transfers. + */ + maxConcurrentDownloadsPerClient: number /** Free space on the blockstore filesystem that uploads must never consume. */ diskReserveBytes: number /** When true, an upload stays temporary until an authorized confirmation. */ @@ -95,6 +102,7 @@ export const DEFAULT_STORAGE_CONFIG: StorageConfig = { maxRequestSizeBytes: 512 * MiB, maxConcurrentUploads: 32, maxConcurrentDownloads: 64, + maxConcurrentDownloadsPerClient: 8, diskReserveBytes: 5 * GiB, confirmationRequired: false, temporaryTtlMs: 24 * 60 * 60 * 1000, @@ -223,6 +231,12 @@ export function resolveStorageConfig(raw: unknown, uploadLimitSizeBytes: number) defaults.maxConcurrentDownloads, 1 ), + maxConcurrentDownloadsPerClient: optionalInteger( + input.maxConcurrentDownloadsPerClient, + 'storage.maxConcurrentDownloadsPerClient', + defaults.maxConcurrentDownloadsPerClient, + 1 + ), diskReserveBytes: optionalInteger( input.diskReserveBytes, 'storage.diskReserveBytes', @@ -250,6 +264,13 @@ export function resolveStorageConfig(raw: unknown, uploadLimitSizeBytes: number) ) } + if (storage.maxConcurrentDownloadsPerClient > storage.maxConcurrentDownloads) { + fail( + 'storage.maxConcurrentDownloadsPerClient', + 'must not exceed storage.maxConcurrentDownloads, otherwise the per-client share is never applied' + ) + } + return storage } diff --git a/src/storage/repairCycleDriver.ts b/src/storage/repairCycleDriver.ts index 82582b3..e6728cf 100644 --- a/src/storage/repairCycleDriver.ts +++ b/src/storage/repairCycleDriver.ts @@ -8,21 +8,35 @@ export interface RepairCycleDriverOptions { delayMs: number runPass: () => Promise busyError: () => Error + /** Consecutive failed passes tolerated before the current cycle is given up. */ + maxConsecutiveFailures?: number onStart?: (trigger: RepairTrigger) => void onError?: (error: Error) => void + /** Reports a cycle ended by repeated failures rather than by completing. */ + onAbandon?: (error: Error, failures: number) => void } +/** + * Retries tolerated before a cycle is given up. + * + * A failed pass is retried because the persisted cursor is untouched, but an + * endpoint that keeps failing must not retry every `delayMs` forever. + */ +const DEFAULT_MAX_CONSECUTIVE_FAILURES = 3 + /** Coordinate scheduled, continued, and manual passes of one repair cycle. */ export class RepairCycleDriver { private active: Promise | undefined private continuationTimer: NodeJS.Timeout | undefined private stopped = true + private consecutiveFailures = 0 constructor(private readonly options: RepairCycleDriverOptions) {} /** Start periodic-cycle ownership and immediately run the first bounded pass. */ start(): void { this.stopped = false + this.consecutiveFailures = 0 void this.runAutomatic('startup') } @@ -31,9 +45,14 @@ export class RepairCycleDriver { void this.runAutomatic('schedule') } - /** Run an operator-requested pass through the same continuation driver. */ + /** + * Run an operator-requested pass through the same continuation driver. + * + * Refused while stopped: a pass admitted after shutdown began would write + * cycle evidence that nothing waits for, against a closing datastore. + */ runManual(): Promise { - if (this.active !== undefined) return Promise.reject(this.options.busyError()) + if (this.stopped || this.active !== undefined) return Promise.reject(this.options.busyError()) return this.run('manual') } @@ -61,21 +80,47 @@ export class RepairCycleDriver { } private run(trigger: RepairTrigger): Promise { + // Only a continuation inherits the failure budget; every other trigger + // takes ownership of the cycle and starts counting again. + if (trigger !== 'continuation') this.consecutiveFailures = 0 + this.options.onStart?.(trigger) const pass = this.options.runPass() this.active = pass void pass.then( (report) => { + this.consecutiveFailures = 0 if (report.cycleCompleted) this.cancelContinuation() else this.scheduleContinuation() }, - () => undefined + (error: Error) => this.onPassFailed(error) ) void pass.then(this.clearActive(pass), this.clearActive(pass)) return pass } + /** + * Keep a cycle alive across a failed pass. + * + * The pass wrote no cursor, so a retry resumes exactly where it began. + * Dropping the cycle instead costs a whole schedule interval, and + * `health.repairMaxAgeMs` is measured from cycle completion, so a transient + * datastore error would otherwise be enough to expire node readiness. + */ + private onPassFailed(error: Error): void { + this.consecutiveFailures += 1 + + const limit = this.options.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES + if (this.consecutiveFailures >= limit) { + this.cancelContinuation() + this.options.onAbandon?.(error, this.consecutiveFailures) + return + } + + this.scheduleContinuation() + } + private clearActive(pass: Promise): () => void { return () => { if (this.active === pass) this.active = undefined diff --git a/src/storage/state.ts b/src/storage/state.ts index ec6622a..ae420c9 100644 --- a/src/storage/state.ts +++ b/src/storage/state.ts @@ -52,3 +52,31 @@ export const COPY_INTAKE_FLOOR = 4 export const incomingCopyLimiter = new ConcurrencyLimiter( Math.max(COPY_INTAKE_FLOOR, Math.floor(config.storage.maxConcurrentUploads / COPY_INTAKE_SHARE)) ) + +/** Occupancy of each admission limiter. */ +export interface ConcurrencyMetrics { + uploads: { active: number; limit: number } + incomingCopies: { active: number; limit: number } + downloads: { active: number; limit: number; perClientLimit: number } +} + +/** + * Report how full the admission limiters are. + * + * A limiter refusal and a rate-limit refusal are both `429`, so without the + * occupancy an operator cannot tell a client that requests too fast from a + * node that has run out of slots — opposite responses. Kept off the public + * storage report because it describes node capacity, like the byte-accurate + * figures on `GET /api/node/details`. + */ +export function getConcurrencyMetrics(): ConcurrencyMetrics { + return { + uploads: { active: uploadLimiter.active, limit: uploadLimiter.limit }, + incomingCopies: { active: incomingCopyLimiter.active, limit: incomingCopyLimiter.limit }, + downloads: { + active: downloadLimiter.active, + limit: downloadLimiter.limit, + perClientLimit: config.storage.maxConcurrentDownloadsPerClient + } + } +} diff --git a/test/config.test.ts b/test/config.test.ts index 03e326b..8744269 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -55,6 +55,7 @@ describe('validateConfig', () => { assert.equal(parsed.downloadIdleTimeout, 20_000) assert.equal(parsed.downloadMinBytesPerSecond, 32 * 1024) assert.equal(parsed.downloadMaxDurationMs, 4 * 60 * 60 * 1_000) + assert.equal(parsed.storage.maxConcurrentDownloadsPerClient, 8) }) it('ignores unknown keys so deployments can carry extras', () => { @@ -147,6 +148,14 @@ describe('validateConfig', () => { raw.downloadMaxDurationMs = 1 }, /downloadMaxDurationMs/ + ], + [ + 'download ceiling cannot carry the largest permitted file', + (raw) => { + // 256 MiB at 32 KiB/s needs 8192 s; a one-minute ceiling would cut it off. + raw.downloadMaxDurationMs = 60_000 + }, + /downloadMaxDurationMs/ ] ] diff --git a/test/downloadAdmission.test.ts b/test/downloadAdmission.test.ts index 34b6dc1..31c8bcf 100644 --- a/test/downloadAdmission.test.ts +++ b/test/downloadAdmission.test.ts @@ -48,3 +48,56 @@ describe('download concurrency admission', () => { assert.equal((await fetch(`${url}/file`)).status, 200) }) }) + +describe('per-client download share', () => { + let url = '' + let close: (() => Promise) | undefined + let release!: () => void + let client = 'first' + + before(async () => { + // Room to spare globally, so only the per-client share can refuse a request. + const limiter = new ConcurrencyLimiter(8) + const app = express() + let held = 0 + app.get( + '/file', + createDownloadAdmission(limiter, { perClientLimit: 1, clientKey: () => client }), + (_req, res) => { + held += 1 + if (held === 1) { + release = () => res.send('held') + return + } + res.send('next') + } + ) + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + url = `http://127.0.0.1:${address.port}` + close = () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + }) + + after(async () => close?.()) + + it('refuses one address beyond its share while others still get slots', async () => { + const held = fetch(`${url}/file`) + while (release === undefined) await new Promise((resolve) => setImmediate(resolve)) + + assert.equal((await fetch(`${url}/file`)).status, 429) + + // A different address is unaffected: the global limiter still has room. + client = 'second' + assert.equal((await fetch(`${url}/file`)).status, 200) + + client = 'first' + release() + assert.equal((await held).status, 200) + assert.equal((await fetch(`${url}/file`)).status, 200) + }) +}) diff --git a/test/nodeRoutes.test.ts b/test/nodeRoutes.test.ts index 2082154..253f338 100644 --- a/test/nodeRoutes.test.ts +++ b/test/nodeRoutes.test.ts @@ -1,8 +1,10 @@ import assert from 'node:assert/strict' import type { AddressInfo } from 'node:net' import { after, before, describe, it } from 'node:test' -import express from 'express' +import express, { Router, type Express } from 'express' import { createNodeRouters } from '../src/api/nodeRoutes.js' +import { mountApiRoutes } from '../src/security/accessPolicy.js' +import { createApiKeyAuth } from '../src/security/apiKey.js' import { refreshHealthSnapshot } from '../src/health/state.js' import type { HealthSnapshot } from '../src/health/state.js' @@ -60,7 +62,12 @@ describe('node HTTP contract', () => { storageMaxAgeMs: 2_000, repairMaxAgeMs: 5_000 }), - getHttpMetrics: () => ({ requests: 1 }) + getHttpMetrics: () => ({ requests: 1 }), + getConcurrencyMetrics: () => ({ + uploads: { active: 1, limit: 32 }, + incomingCopies: { active: 0, limit: 8 }, + downloads: { active: 2, limit: 64, perClientLimit: 8 } + }) }) const app = express() app.use('/api/node', routers.publicNodeRouter) @@ -120,4 +127,126 @@ describe('node HTTP contract', () => { assert.equal(body.health.version, '1.2.3') assert.equal(body.health.uptimeMs, 5_000) }) + + it('reports admission-limiter occupancy so a capacity 429 is attributable', async () => { + const body = (await (await fetch(`${url}/api/node/details`)).json()) as { + concurrency: { downloads: { active: number; limit: number; perClientLimit: number } } + } + + assert.deepEqual(body.concurrency.downloads, { active: 2, limit: 64, perClientLimit: 8 }) + }) +}) + +/** + * The router-level suite above mounts `/details` without the admin boundary. + * This one wires the real routers through `mountApiRoutes` and the real + * key middleware, so the payload and the boundary are asserted together. + */ +describe('node routes behind the mounted access policy', () => { + const key = 'c'.repeat(64) + let url = '' + let close: (() => Promise) | undefined + + before(async () => { + const routers = createNodeRouters({ + version: '1.2.3', + now: () => 20_000, + uptimeMs: () => 5_000, + readLimiter: (_req, _res, next) => next(), + getHeliaStatus: () => 'started', + getPeerId: () => 'peer-id', + getMultiAddresses: () => ['/ip4/127.0.0.1/tcp/4001'], + getDiskUsageStats: () => ({ + blockstoreSizeMb: 1, + datastoreSizeMb: 2, + availableSizeInMb: 3 + }), + getStorageMetrics: () => ({ + pinnedBytes: 4, + reclaimableBytes: 5, + availableBytes: 6, + reservedBytes: 7 + }), + getHealthSnapshot: () => health, + getHttpMetrics: () => ({ requests: 1 }), + getConcurrencyMetrics: () => ({ + uploads: { active: 1, limit: 32 }, + incomingCopies: { active: 0, limit: 8 }, + downloads: { active: 2, limit: 64, perClientLimit: 8 } + }) + }) + const app = express() + const empty = (): Router => Router() + + mountApiRoutes( + app, + { + file: empty(), + fileAdminRouter: empty(), + publicNodeRouter: routers.publicNodeRouter, + node: routers.nodeRouter, + helia: empty(), + libp2p: empty(), + debug: empty(), + storage: empty(), + storageAdminRouter: empty() + }, + createApiKeyAuth(key), + false + ) + + const server = await startServer(app) + url = server.url + close = server.close + }) + + after(async () => close?.()) + + it('serves the public routes without a key', async () => { + assert.equal((await fetch(`${url}/api/node/health`)).status, 200) + assert.equal((await fetch(`${url}/api/node/info`)).status, 200) + }) + + it('serves the complete operator payload only with a matching key', async () => { + assert.equal((await fetch(`${url}/api/node/details`)).status, 401) + + const response = await fetch(`${url}/api/node/details`, { headers: { 'x-api-key': key } }) + const body = (await response.json()) as Record + + assert.equal(response.status, 200) + for (const field of [ + 'version', + 'timestamp', + 'heliaStatus', + 'peerId', + 'multiAddresses', + 'blockstoreSizeMb', + 'datastoreSizeMb', + 'availableSizeInMb', + 'pinnedBytes', + 'reclaimableBytes', + 'availableBytes', + 'reservedBytes', + 'health', + 'http', + 'concurrency' + ]) { + assert.ok(field in body, `missing ${field}`) + } + }) }) + +async function startServer(app: Express): Promise<{ url: string; close: () => Promise }> { + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + + return { + url: `http://127.0.0.1:${address.port}`, + close: () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + } +} diff --git a/test/repairCycleDriver.test.ts b/test/repairCycleDriver.test.ts index 986fdc1..2ccaa56 100644 --- a/test/repairCycleDriver.test.ts +++ b/test/repairCycleDriver.test.ts @@ -60,4 +60,79 @@ describe('RepairCycleDriver', () => { active.resolve({ cycleCompleted: true }) await driver.stop() }) + + it('refuses a manual pass once the driver is stopped', async () => { + let passes = 0 + const driver = new RepairCycleDriver({ + delayMs: 1, + busyError: () => new Error('busy'), + runPass: () => { + passes += 1 + return Promise.resolve({ cycleCompleted: true }) + } + }) + + driver.start() + await driver.stop() + + // Shutdown already awaited the active pass; a pass admitted now would write + // cycle evidence against a closing datastore with nothing waiting for it. + await assert.rejects(driver.runManual(), /busy/) + assert.equal(passes, 1) + }) + + it('retries a failed pass instead of dropping the cycle', async () => { + const results: Array> = [ + Promise.reject(new Error('datastore unavailable')), + Promise.resolve({ cycleCompleted: true }) + ] + results[0].catch(() => undefined) + const completed = deferred() + let passes = 0 + const driver = new RepairCycleDriver({ + delayMs: 5, + busyError: () => new Error('busy'), + runPass: () => { + passes += 1 + const next = results.shift() + if (next === undefined) return Promise.resolve({ cycleCompleted: true }) + if (passes === 2) void next.then(() => completed.resolve()) + return next + }, + onError: () => undefined + }) + + driver.start() + await completed.promise + + assert.equal(passes, 2) + await driver.stop() + }) + + it('abandons the cycle after repeated failures instead of retrying forever', async () => { + const abandoned = deferred() + let passes = 0 + const driver = new RepairCycleDriver({ + delayMs: 1, + maxConsecutiveFailures: 2, + busyError: () => new Error('busy'), + runPass: () => { + passes += 1 + return Promise.reject(new Error('still failing')) + }, + onError: () => undefined, + onAbandon: (unused, failures) => { + void unused + abandoned.resolve(failures) + } + }) + + driver.start() + assert.equal(await abandoned.promise, 2) + + // The budget is spent, so nothing keeps retrying until a new trigger. + await new Promise((resolve) => setTimeout(resolve, 20)) + assert.equal(passes, 2) + await driver.stop() + }) }) diff --git a/test/storageConfig.test.ts b/test/storageConfig.test.ts index 03a34e1..9bce9b6 100644 --- a/test/storageConfig.test.ts +++ b/test/storageConfig.test.ts @@ -47,6 +47,15 @@ describe('resolveStorageConfig', () => { it('rejects limits that would disable a guarantee', () => { assert.throws(() => resolveStorageConfig({ maxConcurrentUploads: 0 }, UPLOAD_LIMIT)) assert.throws(() => resolveStorageConfig({ maxConcurrentDownloads: 0 }, UPLOAD_LIMIT)) + assert.throws(() => resolveStorageConfig({ maxConcurrentDownloadsPerClient: 0 }, UPLOAD_LIMIT)) + assert.throws( + () => + resolveStorageConfig( + { maxConcurrentDownloads: 4, maxConcurrentDownloadsPerClient: 5 }, + UPLOAD_LIMIT + ), + /maxConcurrentDownloadsPerClient/ + ) assert.throws(() => resolveStorageConfig({ temporaryTtlMs: 0 }, UPLOAD_LIMIT)) assert.throws(() => resolveStorageConfig({ diskReserveBytes: -1 }, UPLOAD_LIMIT)) assert.throws(() => resolveStorageConfig({ confirmationRequired: 'yes' }, UPLOAD_LIMIT)) From 7468f4c768b661d816f33446477db2ff8f0579f7 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 1 Sep 2026 06:28:25 +0300 Subject: [PATCH 141/167] fix: enhance health monitoring and logging with evaluatedAt timestamps and request serialization --- README.md | 3 +- docs/openapi.yaml | 16 +++++--- docs/storage-lifecycle.md | 34 +++++++++++----- src/api/storage.ts | 1 + src/config.ts | 39 ++++++++++-------- src/health/state.ts | 11 +++++ src/storage/sweep.ts | 10 ++++- src/utils/logger.ts | 85 ++++++++++++++++++++++++++++++++++++++- test/config.test.ts | 11 +++++ test/healthState.test.ts | 20 +++++++++ test/logger.test.ts | 48 ++++++++++++++++++++++ test/nodeRoutes.test.ts | 1 + 12 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 test/logger.test.ts diff --git a/README.md b/README.md index 646b573..2dbe7d9 100644 --- a/README.md +++ b/README.md @@ -394,6 +394,7 @@ Example response: "state": "ready", "height": 1720614960000, "timestamp": 1720614998797, + "evaluatedAt": 1720614998700, "checkpoint": { "intervalMs": 60000, "observedAt": 1720614998700, @@ -431,7 +432,7 @@ Example response: The endpoint always returns `200`; consumers must inspect `state`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle with no known backlog, and the configured peer attestations all pass. It freezes on failure. Observation timestamps and ages let clients reject an absolutely stale cluster even when every node reports the same height. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. -State changes are deliberately asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` between checkpoints. Recovery is never decided on a read: returning to `ready` requires a successful checkpoint, so expect up to `health.checkpointIntervalMs` of lag after the underlying fault clears. `checkpointFresh`, `storageFresh`, and `repairFresh` follow the same rule; every other entry in `checks`, along with `membership` and `startup`, describes the moment identified by `checkpoint.observedAt` and not the moment of the request. Peers may attest an adjacent round across a boundary, so two healthy nodes can briefly report heights one `checkpointIntervalMs` apart. +State changes are deliberately asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` between checkpoints. Recovery is never decided on a read: returning to `ready` requires a successful checkpoint, so expect up to `health.checkpointIntervalMs` of lag after the underlying fault clears. `checkpointFresh`, `storageFresh`, and `repairFresh` follow the same rule; every other entry in `checks`, along with `membership` and `startup`, describes `evaluatedAt` — the last checkpoint attempt, which may be a failed one — and not the moment of the request. `checkpoint.observedAt` dates the last attempt that _succeeded_, so the two differ whenever the most recent attempt failed. Peers may attest an adjacent round across a boundary, so two healthy nodes can briefly report heights one `checkpointIntervalMs` apart. `membership.version` identifies the configured peer-set epoch. Changing the node list resets the persisted checkpoint for that node: `height` remains `0` until the first valid checkpoint under the new membership. Clients must compare heights only when membership versions match and treat a version change as a new epoch. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 4288fd0..09248be 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -13,7 +13,7 @@ paths: summary: Read the network-aware node checkpoint responses: '200': - description: The current state; inspect status because this route is always 200 + description: The current state; inspect the state field because this route is always 200 content: application/json: schema: @@ -183,6 +183,7 @@ components: - state - height - timestamp + - evaluatedAt - checkpoint - membership - startup @@ -206,7 +207,12 @@ components: Persisted Unix milliseconds at the start of the last valid fixed round. Peers may attest an adjacent round across a boundary, so two healthy nodes can report heights one intervalMs apart at a round change - timestamp: { type: integer } + timestamp: { type: integer, description: When this response was produced } + evaluatedAt: + type: integer + description: >- + When the checkpoint attempt behind this snapshot ran; membership, + startup, and the non-age checks are dated by this, not by checkpoint.observedAt checkpoint: type: object required: [intervalMs, observedAt, ageMs, maxAgeMs] @@ -217,7 +223,7 @@ components: maxAgeMs: { type: integer, minimum: 1000 } membership: type: object - description: Peer evidence captured when checkpoint.observedAt was evaluated + description: Peer evidence from the attempt at evaluatedAt, which may be a failed one required: [version, requiredPeers, attestedPeers] properties: version: @@ -228,7 +234,7 @@ components: attestedPeers: { type: integer, minimum: 0 } startup: type: object - description: Startup evidence captured when checkpoint.observedAt was evaluated + description: Startup evidence from the attempt at evaluatedAt, which may be a failed one required: [complete, healthy] properties: complete: { type: boolean } @@ -250,7 +256,7 @@ components: backlog: { type: integer, minimum: 0 } checks: type: object - description: Checkpoint, storage, and repair freshness are recomputed at read time; every other check describes checkpoint.observedAt + description: Checkpoint, storage, and repair freshness are recomputed at read time; every other check describes evaluatedAt additionalProperties: { type: boolean } HttpMetrics: type: object diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 2ba290f..cc26f8b 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -13,20 +13,34 @@ default, so a configuration file written before this feature keeps working. See An upload is refused before a single block reaches the blockstore when any of these limits is exceeded. -| Limit | Option | Response | -| ---------------------- | -------------------------------- | -------- | -| Concurrent uploads | `storage.maxConcurrentUploads` | `429` | -| Concurrent downloads | `storage.maxConcurrentDownloads` | `429` | -| Aggregate request size | `storage.maxRequestSizeBytes` | `413` | -| Disk reserve | `storage.diskReserveBytes` | `507` | -| Files per request | `maxFileCount` | `400` | -| Single file size | `uploadLimitSizeBytes` | `400` | +| Limit | Option | Response | +| ---------------------- | ------------------------------ | -------- | +| Concurrent uploads | `storage.maxConcurrentUploads` | `429` | +| Aggregate request size | `storage.maxRequestSizeBytes` | `413` | +| Disk reserve | `storage.diskReserveBytes` | `507` | +| Files per request | `maxFileCount` | `400` | +| Single file size | `uploadLimitSizeBytes` | `400` | The first three are checked by the upload guard before the multipart parser runs. The last two are enforced by the parser itself through `createMultipartLimits`, which aborts the request before handing an over-limit part to the storage engine. +## Download admission + +Retrieval has its own guard on `GET /api/file/:cid`, checked before the handler +runs rather than by the upload guard above. + +| Limit | Option | Response | +| ------------------------------ | ----------------------------------------- | -------- | +| Concurrent downloads, node | `storage.maxConcurrentDownloads` | `429` | +| Concurrent downloads, a client | `storage.maxConcurrentDownloadsPerClient` | `429` | + +The per-client share is checked first, so an address already at its limit is +refused without consuming a node slot. A slot is held for the whole response, +which is why the global bound alone would let one address occupy every one of +them for the length of its transfers. + The aggregate size is checked twice: against `Content-Length` before parsing, and against the bytes actually streamed, because a chunked request declares no size. @@ -465,7 +479,9 @@ bound sustained peer load. Its sorted cursor and aggregate result are persisted under the node datastore, so restart resumes the same cycle. A repair checkpoint is recorded only after a pass reaches the end of the candidate set; a pass over one batch is not presented as full-fleet -evidence. A cycle containing an unresolved shortfall is complete but unhealthy, +evidence. A completed cycle covers the records that were confirmed when it +began: content admitted mid-cycle is placed by the upload path and verified by +the next cycle. A cycle containing an unresolved shortfall is complete but unhealthy, and therefore cannot advance the node health checkpoint. Replication needs the peers to be connected over libp2p, because the copy itself diff --git a/src/api/storage.ts b/src/api/storage.ts index 8ffc46e..10874e2 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -46,6 +46,7 @@ router.get('/policy', readLimiter, (req, res) => { maxRequestSizeBytes: config.storage.maxRequestSizeBytes, maxConcurrentUploads: config.storage.maxConcurrentUploads, maxConcurrentDownloads: config.storage.maxConcurrentDownloads, + maxConcurrentDownloadsPerClient: config.storage.maxConcurrentDownloadsPerClient, confirmationRequired: config.storage.confirmationRequired, temporaryTtlMs: config.storage.temporaryTtlMs, durability: config.replication.enabled diff --git a/src/config.ts b/src/config.ts index fa6bae4..4919355 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,6 +42,9 @@ const currDir = dirname(fileURLToPath(import.meta.url)) /** Redial unconnected ADAMANT nodes every half minute unless configured otherwise. */ const DEFAULT_PEERING_SCHEDULE = '*/30 * * * * *' +/** Floor for the absolute download ceiling; the upload limit can raise it. */ +const DEFAULT_DOWNLOAD_MAX_DURATION_MS = 4 * 60 * 60 * 1_000 + /** Log levels accepted by `pino`, ordered from least to most verbose. */ const LOG_LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'] as const @@ -344,32 +347,36 @@ export function validateConfig(raw: unknown): Config { 32 * 1024, 1 ) + const bootstrap = requireStringArray(peerDiscovery.bootstrap, 'peerDiscovery.bootstrap') + + // Owns cors, trustProxy, adminApiKey, enableDebugApi, rateLimits, + // uploadLimitSizeBytes and maxFileCount. Throws with its own message. + validateSecurityConfig(root) + + // `downloadFile` clamps its size-aware deadline to this ceiling, so the + // ceiling has to carry the largest file this node accepts, with the same idle + // headroom that deadline adds. The default is derived from the existing + // upload limit so a configuration written before this option stays valid; a + // ceiling the operator set explicitly is still refused when it would cut + // those transfers off mid-stream and report them as retrieval timeouts. + const largestDownloadMs = + Math.ceil((root.uploadLimitSizeBytes as number) / downloadMinBytesPerSecond) * 1_000 + + downloadIdleTimeout const downloadMaxDurationMs = optionalInteger( root.downloadMaxDurationMs, 'downloadMaxDurationMs', - 4 * 60 * 60 * 1_000, + Math.max(DEFAULT_DOWNLOAD_MAX_DURATION_MS, largestDownloadMs), 1 ) if (downloadMaxDurationMs < downloadIdleTimeout) { fail('downloadMaxDurationMs', 'must be greater than or equal to downloadIdleTimeout') } - const bootstrap = requireStringArray(peerDiscovery.bootstrap, 'peerDiscovery.bootstrap') - - // Owns cors, trustProxy, adminApiKey, enableDebugApi, rateLimits, - // uploadLimitSizeBytes and maxFileCount. Throws with its own message. - validateSecurityConfig(root) - - // The ceiling wins over the size-aware deadline, so one set below the time - // the largest permitted file needs would cut those transfers off mid-stream - // and report the generic retrieval timeout. Refuse it instead of silently - // making large files undownloadable. - const largestFileTransferMs = - Math.ceil((root.uploadLimitSizeBytes as number) / downloadMinBytesPerSecond) * 1_000 - if (downloadMaxDurationMs < largestFileTransferMs) { + if (root.downloadMaxDurationMs !== undefined && downloadMaxDurationMs < largestDownloadMs) { fail( 'downloadMaxDurationMs', - `must be at least ${largestFileTransferMs}, the time an ${String(root.uploadLimitSizeBytes)} ` + - `byte file needs at downloadMinBytesPerSecond ${downloadMinBytesPerSecond}` + `must be at least ${largestDownloadMs}, the time an ${String(root.uploadLimitSizeBytes)} ` + + `byte file needs at downloadMinBytesPerSecond ${downloadMinBytesPerSecond} ` + + `plus the ${downloadIdleTimeout} ms idle allowance` ) } diff --git a/src/health/state.ts b/src/health/state.ts index 839756a..0b462da 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -31,7 +31,17 @@ export interface HealthSnapshot { state: HealthState /** Monotonic checkpoint height, never a request timestamp. */ height: number + /** Time this response was produced. */ timestamp: number + /** + * Time the checkpoint attempt behind this snapshot ran. + * + * A failed attempt leaves `checkpoint.observedAt` on the last successful + * round while `membership`, `startup`, and the non-age checks come from the + * attempt that just failed, so those fields are dated by this value and not + * by `checkpoint.observedAt`. + */ + evaluatedAt: number checkpoint: { intervalMs: number observedAt: number | null @@ -137,6 +147,7 @@ export function evaluateHealth( state, height: completed?.height ?? input.previous?.height ?? 0, timestamp: input.now, + evaluatedAt: input.now, checkpoint: { intervalMs: policy.checkpointIntervalMs, observedAt: checkpointObservedAt, diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index a746891..cc0dfce 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -25,7 +25,15 @@ export interface SweepBatch { * * A persisted cursor may be supplied after restart. A batch never mixes the * tail of one cycle with the head of the next, so completing a batch proves - * that every candidate present in that ordered cycle was visited. + * that every candidate present **when the cycle began** was visited: CIDs are + * immutable, so a record that existed then either sorts before the cursor and + * was already visited, or sorts after it and still will be. + * + * It proves nothing about records admitted mid-cycle. One that sorts before the + * current cursor is not visited until the next cycle, which is the right cost: + * binding completion to a generation of the candidate set would stop a node + * with steady uploads from ever completing a cycle, and repair freshness gates + * node health. */ export function sweepBatch( sweep: SweepName, diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 067da7f..ebd33ed 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -2,14 +2,82 @@ import { pino } from 'pino' import { pinoHttp } from 'pino-http' import { config } from '../config.js' +/** + * Path segments that belong to the API surface rather than to a request. + * + * Every variable segment of this API is a CID, so anything outside this set is + * replaced before a request is logged. A CID identifies stored user content and + * has no place in output a collector retains. + */ +const ROUTE_SEGMENTS = new Set([ + 'api', + 'file', + 'node', + 'storage', + 'helia', + 'libp2p', + 'debug', + 'upload', + 'status', + 'confirm', + 'unpin', + 'health', + 'info', + 'details', + 'metrics', + 'policy', + 'gc', + 'repair', + 'autopeering', + 'peers', + 'pin', + 'pins', + 'isPinned' +]) + +/** + * Reduce a request target to its route shape. + * + * Drops the query string and masks every segment that is not part of a known + * route, so `/api/file/bafy.../status` is logged as `/api/file/:param/status`. + * + * @param url raw request target + * @returns route shape carrying no user-supplied value + */ +export function routeShape(url: string | undefined): string { + if (url === undefined || url === '') { + return '/' + } + + const path = url.split('?')[0] + const shape = path + .split('/') + .map((segment) => (segment === '' || ROUTE_SEGMENTS.has(segment) ? segment : ':param')) + .join('/') + + return shape === '' ? '/' : shape +} + /** * Application logger. * * Production output is newline-delimited JSON. Pretty output is opt-in because * formatting destroys fields that log collectors use for querying and alerts. + * + * `redact` is a second line of defense for call sites that log a request or + * response object directly; the HTTP serializers below already drop headers. */ export const logger = pino({ level: config.logLevel, + redact: { + paths: [ + 'req.headers["x-api-key"]', + 'req.headers.authorization', + 'req.headers.cookie', + 'res.headers["set-cookie"]' + ], + censor: '[redacted]' + }, transport: config.prettyLogs ? { target: 'pino-pretty', @@ -27,8 +95,23 @@ export const logger = pino({ * * Request logs are emitted at `debug` level to keep them out of the default * `info` output while still sharing the application logger's transport. + * + * The default serializers copy every request header and the raw target, which + * writes the administrative `x-api-key` verbatim and puts the served CID in the + * log — including through the `ETag` and `Content-Disposition` response headers. + * Only the method, the route shape, and the status are recorded instead, so the + * log level is not the only thing standing between a request and a secret. */ +export const httpLogSerializers = { + req: (req: { method?: string; url?: string }) => ({ + method: req.method, + url: routeShape(req.url) + }), + res: (res: { statusCode?: number }) => ({ statusCode: res.statusCode }) +} + export const httpLogger = pinoHttp({ logger, - useLevel: 'debug' + useLevel: 'debug', + serializers: httpLogSerializers }) diff --git a/test/config.test.ts b/test/config.test.ts index 8744269..6f1efec 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -58,6 +58,17 @@ describe('validateConfig', () => { assert.equal(parsed.storage.maxConcurrentDownloadsPerClient, 8) }) + it('keeps a configuration written before the download options valid', () => { + // 512 MiB at the fallback 32 KiB/s needs more than the four-hour floor, so + // the ceiling has to follow the existing upload limit rather than reject it. + const raw = validRaw() + raw.uploadLimitSizeBytes = 536_870_912 + raw.storage = { maxRequestSizeBytes: 536_870_912 } + const parsed = validateConfig(raw) + + assert.equal(parsed.downloadMaxDurationMs, Math.ceil(536_870_912 / 32_768) * 1_000 + 20_000) + }) + it('ignores unknown keys so deployments can carry extras', () => { assert.doesNotThrow(() => validateConfig({ ...validRaw(), someFutureOption: 42 })) }) diff --git a/test/healthState.test.ts b/test/healthState.test.ts index 2a16115..fe11857 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -123,6 +123,26 @@ describe('health checkpoint state', () => { assert.equal(repairExpired.checks.repairFresh, false) }) + it('dates non-checkpoint evidence by the attempt, not by the last success', () => { + const prior = evaluateHealth(policy, healthy(12_345)).completed! + const failed = evaluateHealth(policy, { + ...healthy(13_000), + attestedPeers: 0, + previous: prior + }).snapshot + + // The attempt at 13_000 produced membership and checks; observedAt still + // points at the successful round, so the two must not share a timestamp. + assert.equal(failed.evaluatedAt, 13_000) + assert.equal(failed.checkpoint.observedAt, 12_345) + assert.equal(failed.membership.attestedPeers, 0) + + // A read moves `timestamp` but must not move the evaluation time with it. + const read = refreshHealthSnapshot(failed, 13_500, policy) + assert.equal(read.timestamp, 13_500) + assert.equal(read.evaluatedAt, 13_000) + }) + it('keeps the maximum checkpoint age inclusive', () => { const ready = evaluateHealth(policy, healthy(12_345)).snapshot const boundary = refreshHealthSnapshot(ready, 15_345, policy) diff --git a/test/logger.test.ts b/test/logger.test.ts new file mode 100644 index 0000000..0ffcd05 --- /dev/null +++ b/test/logger.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { httpLogSerializers, routeShape } from '../src/utils/logger.js' + +const CID = 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' + +describe('request logging', () => { + it('masks every segment that is not part of a route', () => { + assert.equal(routeShape(`/api/file/${CID}`), '/api/file/:param') + assert.equal(routeShape(`/api/file/${CID}/status`), '/api/file/:param/status') + assert.equal(routeShape(`/api/helia/pins/isPinned/${CID}`), '/api/helia/pins/isPinned/:param') + }) + + it('keeps static routes readable', () => { + assert.equal(routeShape('/api/node/health'), '/api/node/health') + assert.equal(routeShape('/api/storage/policy'), '/api/storage/policy') + assert.equal(routeShape('/'), '/') + assert.equal(routeShape(undefined), '/') + }) + + it('drops the query string, which is user input like any other', () => { + assert.equal(routeShape('/api/storage/gc?dryRun=true'), '/api/storage/gc') + assert.equal(routeShape(`/api/file/${CID}?token=secret`), '/api/file/:param') + }) + + it('serializes a request without its headers or CID', () => { + const serialized = httpLogSerializers.req({ + method: 'GET', + url: `/api/file/${CID}`, + // Present on the real request object; must not reach the log. + headers: { 'x-api-key': 'a'.repeat(64), authorization: 'Bearer token' } + } as unknown as { method?: string; url?: string }) + + assert.deepEqual(serialized, { method: 'GET', url: '/api/file/:param' }) + assert.equal(JSON.stringify(serialized).includes('a'.repeat(64)), false) + assert.equal(JSON.stringify(serialized).includes(CID), false) + }) + + it('serializes a response without the CID-bearing headers', () => { + const serialized = httpLogSerializers.res({ + statusCode: 200, + headers: { etag: `"${CID}"`, 'content-disposition': `attachment; filename="${CID}"` } + } as unknown as { statusCode?: number }) + + assert.deepEqual(serialized, { statusCode: 200 }) + assert.equal(JSON.stringify(serialized).includes(CID), false) + }) +}) diff --git a/test/nodeRoutes.test.ts b/test/nodeRoutes.test.ts index 253f338..0ba6948 100644 --- a/test/nodeRoutes.test.ts +++ b/test/nodeRoutes.test.ts @@ -12,6 +12,7 @@ const health: HealthSnapshot = { state: 'ready', height: 12_000, timestamp: 12_345, + evaluatedAt: 12_345, checkpoint: { intervalMs: 1_000, observedAt: 12_345, ageMs: 0, maxAgeMs: 3_000 }, membership: { version: 'a'.repeat(64), requiredPeers: 1, attestedPeers: 1 }, startup: { complete: true, healthy: true }, From d92ad75ab7446095f9bbdc9d95472a7aa1b4515b Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 1 Sep 2026 07:45:09 +0300 Subject: [PATCH 142/167] fix: sanitize logs, bound repair cycle coverage, and validate persisted health Replace the default pino serializers and add a central scrubbing hook, so content identifiers, peer identities, multiaddrs and stack traces cannot reach the log from any call site. The HTTP serializers alone could not: application logging is template strings built where they are emitted. Errors are logged structurally without a stack, and pino takes msg straight from an Error before serializers run, so the hook hands it a scrubbed copy. The repair pass, the rescue and demote paths, and the upload route report bounded events instead of cursors, CIDs and original filenames. Read the repair candidate list once per cycle instead of once per pass. A cycle over N records scanned and sorted the whole registry N / 50 times; it now costs two scans. Committing to a list also makes coverage decidable: one scan at the end names records admitted while the cycle walked. Those are appended and covered by the same cycle for a bounded number of catch-up rounds, because discarding a finished cycle on every upload would freeze repair freshness and degrade a busy node permanently. A cycle that exhausts its rounds still finishes, but leaves the last fully covered cycle as the durability evidence. Count a response in its status family only when the body finished. A download sends 200 with its first chunk and can still fail mid-stream; that close now increments a separate aborted counter instead of reporting a truncated transfer as a success. Reject a persisted health checkpoint this node could not have written: negative counts and timestamps, a membership version that is not a digest, a height above the attempt that completed it, and a checkpoint from ahead of the clock. The height relationship matters most, because evaluateHealth carries a height forward with Math.max and one bad value would be advertised indefinitely. Co-Authored-By: Claude Opus 5 --- docs/openapi.yaml | 5 ++ docs/storage-lifecycle.md | 12 ++- src/api/uploadRoute.ts | 3 +- src/disk-usage.cron.ts | 4 +- src/gc.cron.ts | 4 +- src/health/persistence.ts | 66 ++++++++++++-- src/observability.ts | 27 ++++-- src/peering.cron.ts | 2 +- src/replication.cron.ts | 58 +++++++++--- src/storage/repairCycle.ts | 132 ++++++++++++++++++++++++++++ src/storage/service.ts | 46 +++++++--- src/storage/sweep.ts | 13 +-- src/utils/logger.ts | 145 +++++++++++++++++++++++++----- test/healthPersistence.test.ts | 104 ++++++++++++++++++++++ test/logCapture.test.ts | 102 +++++++++++++++++++++ test/repairCycle.test.ts | 156 +++++++++++++++++++++++++++++++++ 16 files changed, 799 insertions(+), 80 deletions(-) create mode 100644 src/storage/repairCycle.ts create mode 100644 test/healthPersistence.test.ts create mode 100644 test/logCapture.test.ts create mode 100644 test/repairCycle.test.ts diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 09248be..e682cff 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -264,6 +264,7 @@ components: required: - requests - responses + - aborted - inFlight - totalResponseTimeMs - maxResponseTimeMs @@ -278,6 +279,10 @@ components: 3xx: { type: integer, minimum: 0 } 4xx: { type: integer, minimum: 0 } 5xx: { type: integer, minimum: 0 } + aborted: + type: integer + minimum: 0 + description: Responses whose connection closed before the body finished, counted apart from the status families inFlight: { type: integer, minimum: 0 } totalResponseTimeMs: { type: number, minimum: 0 } maxResponseTimeMs: { type: number, minimum: 0 } diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index cc26f8b..d212f9b 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -479,9 +479,15 @@ bound sustained peer load. Its sorted cursor and aggregate result are persisted under the node datastore, so restart resumes the same cycle. A repair checkpoint is recorded only after a pass reaches the end of the candidate set; a pass over one batch is not presented as full-fleet -evidence. A completed cycle covers the records that were confirmed when it -began: content admitted mid-cycle is placed by the upload path and verified by -the next cycle. A cycle containing an unresolved shortfall is complete but unhealthy, +evidence. + +A cycle commits to a candidate list read once at its start, so the registry is +scanned per cycle rather than per pass. One more scan at the end names records +admitted while the cycle was walking; those are appended and covered by the same +cycle, for a bounded number of catch-up rounds. A cycle that runs out of rounds +still finishes and hands its cursor back, but it is not published as durability +evidence: the last cycle that covered its whole set keeps that role, so +readiness ages out on its own schedule instead of advancing on a coverage gap. A cycle containing an unresolved shortfall is complete but unhealthy, and therefore cannot advance the node health checkpoint. Replication needs the peers to be connected over libp2p, because the copy itself diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index cccb35c..ce2455a 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -261,7 +261,8 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ } const files = flatFiles(req.files as UnixFsMulterFile[]) - log.info(`req.files: ${JSON.stringify(files.map((item) => item.originalname))}`) + // Original names belong to the uploader, so only how many arrived is logged. + log.info(`Upload request carries ${files.length} file(s)`) // Hold every CID this request touches while its pin and its record are // written, and no longer than that. Sorting happens inside the registry, diff --git a/src/disk-usage.cron.ts b/src/disk-usage.cron.ts index ce026dd..1ff1a15 100644 --- a/src/disk-usage.cron.ts +++ b/src/disk-usage.cron.ts @@ -10,7 +10,7 @@ export const diskUsageCron = new CronJob(config.diskUsageScanPeriod, () => { if (!started) { started = true scan() - .catch((err) => logger.error(`${err.message}\n${err.stack}`)) + .catch((err: Error) => logger.error({ err }, 'Disk usage scan failed')) .finally(() => (started = false)) } }) @@ -25,7 +25,7 @@ async function scan() { logger.info(`Check folder size took ${Date.now() - start} ms.`) } -scan().catch((err) => logger.error(`${err.message}\n${err.stack}`)) +scan().catch((err: Error) => logger.error({ err }, 'Disk usage scan failed')) /** * Disk usage in megabytes, kept for the existing `/api/node/info` payload. diff --git a/src/gc.cron.ts b/src/gc.cron.ts index 55eb2c9..77a6705 100644 --- a/src/gc.cron.ts +++ b/src/gc.cron.ts @@ -71,7 +71,7 @@ export const garbageCollectionCron = new CronJob(config.storage.gc.schedule, () } logger.info('[Cron] Running "garbageCollection" cronjob.') - collectGarbage().catch((err) => logger.error(`${err.message}\n${err.stack}`)) + collectGarbage().catch((err: Error) => logger.error({ err }, 'Garbage collection failed')) }) /** @@ -82,7 +82,7 @@ export const garbageCollectionCron = new CronJob(config.storage.gc.schedule, () */ export const admissionRecoveryCron = new CronJob(config.storage.gc.schedule, () => { logger.info('[Cron] Running "admissionRecovery" cronjob.') - recoverStaleAdmissions().catch((err) => logger.error(`${err.message}\n${err.stack}`)) + recoverStaleAdmissions().catch((err: Error) => logger.error({ err }, 'Admission recovery failed')) }) async function recoverStaleAdmissions(): Promise { diff --git a/src/health/persistence.ts b/src/health/persistence.ts index 5425d7a..d7cc9bd 100644 --- a/src/health/persistence.ts +++ b/src/health/persistence.ts @@ -6,26 +6,74 @@ const CHECKPOINT_KEY = new Key('/adm/health/checkpoint') const encoder = new TextEncoder() const decoder = new TextDecoder() -function parseCheckpoint(value: unknown): HealthCheckpoint | null { +/** Shape of a membership version, matching the digest {@link membershipVersion} emits. */ +const MEMBERSHIP_VERSION = /^[0-9a-f]{64}$/ + +/** + * Clock movement tolerated between writing a checkpoint and reading it back. + * + * A checkpoint dated after the current time is only possible if the clock went + * backwards, which invalidates the round it claims. + */ +const CLOCK_TOLERANCE_MS = 60_000 + +function isCount(value: unknown): value is number { + return Number.isSafeInteger(value) && (value as number) >= 0 +} + +/** + * Accept only a checkpoint this node could have written. + * + * `Number.isSafeInteger` alone admits negative timestamps and peer counts, and + * a height arbitrarily far in the future. The last of those is not merely + * cosmetic: `evaluateHealth` carries a height forward with `Math.max`, so one + * bad value would be advertised by a `ready` node for the rest of its life and + * could never be corrected by a genuine round. + */ +export function parseCheckpoint(value: unknown, now: number): HealthCheckpoint | null { + if (typeof value !== 'object' || value === null) { + return null + } + const item = value as Partial if ( - item === null || - !Number.isSafeInteger(item.height) || - !Number.isSafeInteger(item.completedAt) || + !isCount(item.height) || + !isCount(item.completedAt) || + !isCount(item.attestedPeers) || typeof item.membershipVersion !== 'string' || - !Number.isSafeInteger(item.attestedPeers) + !MEMBERSHIP_VERSION.test(item.membershipVersion) || + // The round starts at or before the attempt that completed it. + item.height > item.completedAt || + item.completedAt > now + CLOCK_TOLERANCE_MS ) { return null } - return item as HealthCheckpoint + return { + height: item.height, + completedAt: item.completedAt, + membershipVersion: item.membershipVersion, + attestedPeers: item.attestedPeers + } } -/** Load the last durable checkpoint, ignoring malformed legacy or partial data. */ -export async function loadHealthCheckpoint(store: Datastore): Promise { +/** + * Load the last durable checkpoint, ignoring malformed legacy or partial data. + * + * @param store node datastore + * @param now current time, used to reject a checkpoint from ahead of the clock + * @returns the checkpoint, or `null` when there is none this node can trust + */ +export async function loadHealthCheckpoint( + store: Datastore, + now: number = Date.now() +): Promise { try { - return parseCheckpoint(JSON.parse(decoder.decode(await store.get(CHECKPOINT_KEY))) as unknown) + return parseCheckpoint( + JSON.parse(decoder.decode(await store.get(CHECKPOINT_KEY))) as unknown, + now + ) } catch (err) { if ((err as { code?: string }).code === 'ERR_NOT_FOUND' || err instanceof SyntaxError) { return null diff --git a/src/observability.ts b/src/observability.ts index a8cea8e..fae799c 100644 --- a/src/observability.ts +++ b/src/observability.ts @@ -12,6 +12,8 @@ import type { RequestHandler } from 'express' interface HttpMetrics { requests: number responses: Record<'2xx' | '3xx' | '4xx' | '5xx', number> + /** Responses whose connection closed before the body was finished. */ + aborted: number inFlight: number totalResponseTimeMs: number maxResponseTimeMs: number @@ -20,6 +22,7 @@ interface HttpMetrics { const metrics: HttpMetrics = { requests: 0, responses: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0 }, + aborted: 0, inFlight: 0, totalResponseTimeMs: 0, maxResponseTimeMs: 0 @@ -32,32 +35,40 @@ export const collectHttpMetrics: RequestHandler = (_req, res, next) => { metrics.requests += 1 metrics.inFlight += 1 - const settle = (): void => { + const settle = (completed: boolean): void => { if (settled) return settled = true metrics.inFlight -= 1 const elapsed = performance.now() - started metrics.totalResponseTimeMs += elapsed metrics.maxResponseTimeMs = Math.max(metrics.maxResponseTimeMs, elapsed) + + // A download sends `200` with its first chunk and can still fail or be + // cancelled mid-body. Counting that close in the status family would report + // a truncated transfer as a success and hide the late failures this report + // exists to surface, so only a finished body reaches a family counter. + if (!completed) { + metrics.aborted += 1 + return + } + const group = `${Math.floor(res.statusCode / 100)}xx` as keyof HttpMetrics['responses'] if (group in metrics.responses) metrics.responses[group] += 1 } - res.once('finish', settle) - res.once('close', settle) + res.once('finish', () => settle(true)) + res.once('close', () => settle(res.writableFinished)) next() } /** Snapshot suitable for the authenticated operator details endpoint. */ export function getHttpMetrics(): HttpMetrics & { averageResponseTimeMs: number } { - const completedResponses = Object.values(metrics.responses).reduce( - (total, count) => total + count, - 0 - ) + const settledResponses = + Object.values(metrics.responses).reduce((total, count) => total + count, 0) + metrics.aborted return { ...metrics, responses: { ...metrics.responses }, averageResponseTimeMs: - completedResponses === 0 ? 0 : metrics.totalResponseTimeMs / completedResponses + settledResponses === 0 ? 0 : metrics.totalResponseTimeMs / settledResponses } } diff --git a/src/peering.cron.ts b/src/peering.cron.ts index 6d8dd2f..df7f1c3 100644 --- a/src/peering.cron.ts +++ b/src/peering.cron.ts @@ -55,7 +55,7 @@ export const peeringCron = new CronJob(config.peeringSchedule, () => { running = true peerWithKnownNodes() - .catch((err: Error) => logger.error(`${err.message}\n${err.stack}`)) + .catch((err: Error) => logger.error({ err }, 'Peering cycle failed')) .finally(() => (running = false)) }) diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 4db25d9..9d679b4 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -20,6 +20,8 @@ interface RepairCycleEvidence { policyVersion: string cursor?: string startedAt: number + /** Records were admitted behind the cursor, so this cycle cannot cover them. */ + superseded: boolean examined: number checked: number underReplicated: number @@ -42,6 +44,7 @@ function emptyEvidence(now = Date.now()): RepairCycleEvidence { membershipVersion: membershipVersion(config.nodes), policyVersion: currentPolicyVersion(), startedAt: now, + superseded: false, examined: 0, checked: 0, underReplicated: 0, @@ -72,6 +75,7 @@ async function loadEvidence(): Promise { (parsed.cursor === undefined || typeof parsed.cursor === 'string') && Number.isSafeInteger(parsed.startedAt) && (parsed.examined === undefined || Number.isSafeInteger(parsed.examined)) && + (parsed.superseded === undefined || typeof parsed.superseded === 'boolean') && Number.isSafeInteger(parsed.checked) && Number.isSafeInteger(parsed.underReplicated) && Number.isSafeInteger(parsed.repaired) && @@ -80,7 +84,11 @@ async function loadEvidence(): Promise { (parsed.lastCompletedAt === null || Number.isSafeInteger(parsed.lastCompletedAt)) && typeof parsed.lastCompletedSuccessfully === 'boolean' && Number.isSafeInteger(parsed.lastCompletedBacklog) - ? ({ ...parsed, examined: parsed.examined ?? parsed.checked } as RepairCycleEvidence) + ? ({ + ...parsed, + examined: parsed.examined ?? parsed.checked, + superseded: parsed.superseded ?? false + } as RepairCycleEvidence) : emptyEvidence() } catch (err) { if ((err as { code?: string }).code !== 'ERR_NOT_FOUND') throw err @@ -131,26 +139,47 @@ export async function repairUnderReplicatedFiles(): Promise { stillMissing: current.stillMissing + lastReport.stillMissing.length, unrecoverable: current.unrecoverable + lastReport.unrecoverable.length } - const completedAt = lastReport.cycleCompleted ? Date.now() : current.lastCompletedAt - const completedSuccessfully = lastReport.cycleCompleted - ? aggregate.stillMissing === 0 && aggregate.unrecoverable === 0 - : current.lastCompletedSuccessfully + + // A cycle whose catch-up rounds ran out ended without covering everything + // that arrived while it walked. It still finishes and hands the cursor back, + // but it is not published as fresh durability evidence: the last genuinely + // complete cycle stays in place instead, so readiness decays on its own + // schedule rather than advancing on a coverage gap. + const superseded = current.superseded || lastReport.uncovered > 0 + const publishes = lastReport.cycleCompleted && !superseded await saveEvidence( lastReport.cycleCompleted ? { ...emptyEvidence(), - lastCompletedAt: completedAt, - lastCompletedSuccessfully: completedSuccessfully, - lastCompletedBacklog: aggregate.stillMissing + aggregate.unrecoverable + lastCompletedAt: publishes ? Date.now() : current.lastCompletedAt, + lastCompletedSuccessfully: publishes + ? aggregate.stillMissing === 0 && aggregate.unrecoverable === 0 + : current.lastCompletedSuccessfully, + lastCompletedBacklog: publishes + ? aggregate.stillMissing + aggregate.unrecoverable + : current.lastCompletedBacklog } : { ...current, + superseded, cursor: lastReport.nextCursor, ...aggregate } ) + if (lastReport.cycleCompleted && superseded) { + logger.warn( + { + event: 'replication_repair_cycle_superseded', + examined: aggregate.examined, + uncovered: lastReport.uncovered + }, + 'Repair cycle finished without covering records admitted while it ran; ' + + 'keeping the previous completed cycle as evidence' + ) + } + if (lastReport.underReplicated > 0) { logger.info( `Replication repair: ${lastReport.repaired.length} of ${lastReport.underReplicated} ` + @@ -176,15 +205,17 @@ export const replicationRepairCron = new CronJob(config.replication.repairSchedu * both resume the cursor a previous pass left behind. */ function logRepairPass(trigger: RepairTrigger): void { - const cursor = evidence?.cursor ?? null + const resuming = evidence?.cursor !== undefined logger.info( { event: 'replication_repair_pass', trigger, - cursor + resuming, + // Bounded progress instead of the cursor, which is a CID. + examined: evidence?.examined ?? 0 }, - cursor === null ? 'Starting replication repair cycle' : 'Continuing replication repair cycle' + resuming ? 'Continuing replication repair cycle' : 'Starting replication repair cycle' ) } @@ -193,13 +224,13 @@ const repairCycleDriver = new RepairCycleDriver({ runPass: repairUnderReplicatedFiles, busyError: () => new ReplicationRepairBusyError(), onStart: logRepairPass, - onError: (err) => logger.error(`${err.message}\n${err.stack}`), + onError: (err) => logger.error({ err }, 'Replication repair pass failed'), onAbandon: (err, failures) => logger.error( { event: 'replication_repair_cycle_abandoned', failures, - cursor: evidence?.cursor ?? null, + examined: evidence?.examined ?? 0, err }, 'Replication repair cycle abandoned after repeated failures; it resumes on the next schedule' @@ -238,6 +269,7 @@ export function getReplicationState() { cycle: evidence ? { startedAt: evidence.startedAt, + superseded: evidence.superseded, examined: evidence.examined, checked: evidence.checked, underReplicated: evidence.underReplicated, diff --git a/src/storage/repairCycle.ts b/src/storage/repairCycle.ts new file mode 100644 index 0000000..e580425 --- /dev/null +++ b/src/storage/repairCycle.ts @@ -0,0 +1,132 @@ +import { isLifecycleBusy, type FileRecord, type FileRegistry } from './registry.js' +import { SWEEP_BATCHES } from './sweep.js' + +/** + * Catch-up rounds a cycle may append before it stops trying to converge. + * + * Each round only has to cover what arrived during the previous one, so the + * work shrinks quickly. The bound is what stops a node under sustained upload + * load from extending one cycle indefinitely. + */ +const MAX_CATCH_UP_ROUNDS = 3 + +export interface RepairCycleBatch { + /** CIDs this pass should visit, in cycle order. */ + cids: string[] + /** True when this pass ended a cycle, covered or not. */ + cycleCompleted: boolean + /** Cursor to persist so a restart can resume this cycle. */ + nextCursor?: string + /** + * Candidates admitted during the cycle that it could not cover. + * + * Non-zero only when the catch-up rounds ran out, which is the caller's + * signal that the finished cycle is not evidence about the whole set. + */ + uncovered: number +} + +interface RepairCycle { + /** Candidate CIDs this cycle committed to covering, in visit order. */ + cids: string[] + /** How many of them have been handed out. */ + index: number + refreshes: number +} + +/** + * Candidate list of the cycle in progress. + * + * Held in memory rather than persisted: at production sizes the list is far too + * large for a datastore value, and losing it costs one rebuild. A restart + * rebuilds it and resumes from the persisted cursor. + */ +let cycle: RepairCycle | undefined + +/** Forget the cycle in progress, so the next pass starts a new one. */ +export function resetRepairCycle(): void { + cycle = undefined +} + +function isCandidate(record: FileRecord): boolean { + return record.state === 'confirmed' && !isLifecycleBusy(record) +} + +/** Read the current candidate set, ordered the way a cycle walks it. */ +async function candidateCids(registry: FileRegistry): Promise { + const cids = (await registry.all()).filter(isCandidate).map((record) => record.cid) + + // Code-unit ordering, matching the cursor comparison in `resumeIndex`. + return cids.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)) +} + +/** Locate the persisted cursor in a freshly built list, after a restart. */ +function resumeIndex(cids: string[], cursor: string | undefined): number { + if (cursor === undefined) { + return 0 + } + + const exact = cids.indexOf(cursor) + if (exact >= 0) { + return exact + 1 + } + + // The cursor record left the candidate set. Resume at the first CID strictly + // greater so the pass does not rewind to the head. + const next = cids.findIndex((cid) => cid > cursor) + return next >= 0 ? next : cids.length +} + +/** + * Take the next bounded batch of the repair cycle. + * + * The candidate set is read once per cycle instead of once per pass: a full + * cycle over `N` records used to scan and sort the whole registry `N / 50` + * times. Slicing a committed list also makes coverage decidable — reaching the + * end means every CID the cycle committed to was handed out. + * + * Records that arrived while the cycle was walking are not in that list, so one + * scan at the end names them. They are appended and covered by the same cycle + * where possible, because discarding a finished cycle on every upload would + * stop `repairFresh` from ever advancing on a busy node. + * + * @param registry lifecycle registry to read candidates from + * @param cursor CID the previous pass stopped at, or `undefined` to start a cycle + * @returns the batch, the cycle boundary, and any candidates left uncovered + */ +export async function nextRepairCycleBatch( + registry: FileRegistry, + cursor: string | undefined +): Promise { + if (cycle === undefined || cursor === undefined) { + const cids = await candidateCids(registry) + cycle = { cids, index: resumeIndex(cids, cursor), refreshes: 0 } + } + + const batch = cycle.cids.slice(cycle.index, cycle.index + SWEEP_BATCHES.repair) + cycle.index += batch.length + + if (cycle.index < cycle.cids.length) { + return { cids: batch, cycleCompleted: false, nextCursor: batch.at(-1) ?? cursor, uncovered: 0 } + } + + // End of the committed list. One scan decides whether the cycle covered the + // set it set out to cover, or whether records arrived behind it. + const committed = new Set(cycle.cids) + const fresh = (await candidateCids(registry)).filter((cid) => !committed.has(cid)) + + if (fresh.length === 0) { + cycle = undefined + return { cids: batch, cycleCompleted: true, uncovered: 0 } + } + + if (cycle.refreshes < MAX_CATCH_UP_ROUNDS) { + cycle.cids.push(...fresh) + cycle.refreshes += 1 + + return { cids: batch, cycleCompleted: false, nextCursor: batch.at(-1) ?? cursor, uncovered: 0 } + } + + cycle = undefined + return { cids: batch, cycleCompleted: true, uncovered: fresh.length } +} diff --git a/src/storage/service.ts b/src/storage/service.ts index d740bb0..8c59d2c 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -56,7 +56,8 @@ import { } from './meter.js' import { claimedBytes, claimSpace, type Claim } from './reservation.js' import { fileRegistry, incomingCopyLimiter, storageOperationLock } from './state.js' -import { nextSweepBatch, sweepBatch } from './sweep.js' +import { nextSweepBatch } from './sweep.js' +import { nextRepairCycleBatch } from './repairCycle.js' import { claimRepairRecord, releaseRepairRecord } from './repairClaim.js' const callOptions = (): ReplicationCallOptions => ({ @@ -636,7 +637,10 @@ export function createReplicationHandlers(): ReplicationHandlers { cacheCopy: cacheFileLocally, onError: (message) => logger.warn(message), onRefused: (peerId, op) => - logger.info(`Refused "${op}" from an unknown peer ${peerId}; offering a cached copy instead`) + logger.info( + { event: 'replication_refused_unknown_peer', op }, + 'Refused an operation from an unknown peer; offering a cached copy instead' + ) } } @@ -807,6 +811,8 @@ export interface RepairReport { unrecoverable: string[] /** This pass reached the end of a complete bounded repair cycle. */ cycleCompleted: boolean + /** Candidates admitted during the cycle that its catch-up rounds could not cover. */ + uncovered: number /** Durable resume cursor for the next pass. */ nextCursor?: string } @@ -875,7 +881,12 @@ async function rescueOrphanedFiles( if (!(await helia.blockstore.has(cid))) { unrecoverable.push(record.cid) - logger.error(`No configured node reports holding ${record.cid}`) + // The CID stays out of the log; the repair report returns the full + // `unrecoverable` list on the authenticated endpoint that asked for it. + logger.error( + { event: 'replication_unrecoverable_record' }, + 'No configured node holds a confirmed record' + ) continue } @@ -928,7 +939,10 @@ async function rescueOrphanedFiles( } rescued.push(record.cid) - logger.warn(`No node was holding ${record.cid}; kept the local copy instead`) + logger.warn( + { event: 'replication_rescued_record' }, + 'No node was holding a confirmed record; kept the local copy' + ) } catch { // The blocks are only partly here, so there is nothing to rescue continue @@ -965,7 +979,8 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom stillMissing: [], rescued: [], unrecoverable: [], - cycleCompleted: true + cycleCompleted: true, + uncovered: 0 } if (!config.replication.enabled) { @@ -974,15 +989,17 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom const peers = getReplicationPeers() const self = selfPeerId() - const records = await fileRegistry.all() - const selected = sweepBatch( - 'repair', - records.filter((record) => record.state === 'confirmed' && !isLifecycleBusy(record)), - options.cursor - ) - const candidates = selected.records + const selected = await nextRepairCycleBatch(fileRegistry, options.cursor) report.cycleCompleted = selected.cycleCompleted report.nextCursor = selected.nextCursor + report.uncovered = selected.uncovered + + // The cycle committed to these CIDs one scan ago, so each record is read + // again here rather than trusted from that list. A record that has since been + // released or deleted simply drops out of the pass. + const candidates = ( + await Promise.all(selected.cids.map(async (cid) => fileRegistry.get(cid))) + ).filter((record): record is FileRecord => record !== undefined) report.examined = candidates.length @@ -1155,7 +1172,10 @@ export async function demoteReleasableCopies( if (demoted) { report.demoted.push(record.cid) - logger.info(`Released the local copy of ${record.cid}; ${holders.length} peers hold it`) + logger.info( + { event: 'storage_demoted_record', holders: holders.length }, + 'Released a local copy held by peers' + ) } } diff --git a/src/storage/sweep.ts b/src/storage/sweep.ts index cc0dfce..7063cf1 100644 --- a/src/storage/sweep.ts +++ b/src/storage/sweep.ts @@ -29,11 +29,14 @@ export interface SweepBatch { * immutable, so a record that existed then either sorts before the cursor and * was already visited, or sorts after it and still will be. * - * It proves nothing about records admitted mid-cycle. One that sorts before the - * current cursor is not visited until the next cycle, which is the right cost: - * binding completion to a generation of the candidate set would stop a node - * with steady uploads from ever completing a cycle, and repair freshness gates - * node health. + * It proves nothing about records admitted mid-cycle. Replication repair needs + * that guarantee and tracks it in `repairCycle.ts`, which commits to a candidate + * list once and reports what arrived behind it; this sweep stays the simple + * position-keeping used by demotion. + * + * @param sweep which sweep is asking; selects the batch size + * @param records every current candidate + * @param cursor CID the previous pass of this cycle stopped at */ export function sweepBatch( sweep: SweepName, diff --git a/src/utils/logger.ts b/src/utils/logger.ts index ebd33ed..93f1b31 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,4 +1,5 @@ import { pino } from 'pino' +import type { DestinationStream, LogFn, Logger } from 'pino' import { pinoHttp } from 'pino-http' import { config } from '../config.js' @@ -58,37 +59,135 @@ export function routeShape(url: string | undefined): string { return shape === '' ? '/' : shape } +/** + * Values that must never appear in output a collector retains. + * + * Multiaddrs are matched first because one embeds a peer id, and the stack + * pattern last because it is the only multi-line rule. Each entry is anchored + * on a shape that cannot occur in prose, so an operational message keeps its + * meaning while the identifier inside it does not survive. + */ +const LOG_SCRUBBERS: Array<[RegExp, string]> = [ + [/\/(?:ip4|ip6|dns|dns4|dns6|dnsaddr)\/[^\s"',)]+/g, '[multiaddr]'], + [/\b12D3Koo[1-9A-HJ-NP-Za-km-z]{40,}\b/g, '[peer]'], + [/\bQm[1-9A-HJ-NP-Za-km-z]{44}\b/g, '[cid]'], + [/\bb[a-z2-7]{58,}\b/g, '[cid]'], + [/\n\s+at\s+[\s\S]*$/, ' [stack omitted]'] +] + +/** + * Remove content identifiers, peer identities, and stack traces from a message. + * + * The HTTP serializers cannot reach these: they sanitize request and response + * objects, while most application logging is a template string built at the + * call site. Scrubbing centrally means a call site added later cannot reopen + * the hole, which a per-site convention would not guarantee. + * + * @param value message or string field about to be logged + * @returns the same text with every identifier replaced by a fixed placeholder + */ +export function scrubLogValue(value: string): string { + return LOG_SCRUBBERS.reduce((text, [pattern, placeholder]) => { + // A global regex carries `lastIndex` between calls; reset before reuse. + pattern.lastIndex = 0 + return text.replace(pattern, placeholder) + }, value) +} + +/** Apply {@link scrubLogValue} through the strings of one log argument. */ +function scrubLogArgument(value: unknown, depth = 0): unknown { + if (typeof value === 'string') { + return scrubLogValue(value) + } + + // `logger.error(err)` takes the message straight from the error for `msg`, + // before any serializer runs, so the error is replaced by a scrubbed copy + // rather than passed through. The `err` serializer then drops the stack. + if (value instanceof Error) { + const scrubbed: Error & { code?: unknown } = new Error(scrubLogValue(value.message)) + scrubbed.name = value.name + const { code } = value as Error & { code?: unknown } + if (code !== undefined) { + scrubbed.code = code + } + return scrubbed + } + + if (value === null || typeof value !== 'object' || depth > 4) { + return value + } + + if (Array.isArray(value)) { + return value.map((item) => scrubLogArgument(item, depth + 1)) + } + + return Object.fromEntries( + Object.entries(value).map(([key, item]) => [key, scrubLogArgument(item, depth + 1)]) + ) +} + /** * Application logger. * * Production output is newline-delimited JSON. Pretty output is opt-in because * formatting destroys fields that log collectors use for querying and alerts. * - * `redact` is a second line of defense for call sites that log a request or - * response object directly; the HTTP serializers below already drop headers. + * Three controls keep content and topology out of the output: `hooks.logMethod` + * scrubs every message and structured field, the `err` serializer reports an + * error without its stack, and `redact` covers call sites that log a request or + * response object directly. */ -export const logger = pino({ - level: config.logLevel, - redact: { - paths: [ - 'req.headers["x-api-key"]', - 'req.headers.authorization', - 'req.headers.cookie', - 'res.headers["set-cookie"]' - ], - censor: '[redacted]' - }, - transport: config.prettyLogs - ? { - target: 'pino-pretty', - options: { - colorize: true, - translateTime: 'dd.mm.yy HH:MM:ss Z', - ignore: 'pid,hostname' - } +export function createApplicationLogger( + options: { level?: string; destination?: DestinationStream } = {} +): Logger { + const level = options.level ?? config.logLevel + const shared = { + level, + hooks: { + logMethod(this: Logger, args: unknown[], method: LogFn): void { + method.apply(this, args.map((argument) => scrubLogArgument(argument)) as Parameters) } - : undefined -}) + }, + serializers: { + err: (err: Error & { code?: unknown }) => ({ + type: err.name, + message: scrubLogValue(err.message), + ...(typeof err.code === 'string' ? { code: err.code } : {}) + }) + }, + redact: { + paths: [ + 'req.headers["x-api-key"]', + 'req.headers.authorization', + 'req.headers.cookie', + 'res.headers["set-cookie"]' + ], + censor: '[redacted]' + } + } + + // A caller-supplied destination is how the sanitizing wiring is exercised; a + // transport would send the records to another thread instead. + if (options.destination !== undefined) { + return pino(shared, options.destination) + } + + return pino({ + ...shared, + transport: config.prettyLogs + ? { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'dd.mm.yy HH:MM:ss Z', + ignore: 'pid,hostname' + } + } + : undefined + }) +} + +export const logger = createApplicationLogger() /** * Express middleware that logs requests through {@link logger}. diff --git a/test/healthPersistence.test.ts b/test/healthPersistence.test.ts new file mode 100644 index 0000000..7a4833b --- /dev/null +++ b/test/healthPersistence.test.ts @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, describe, it } from 'node:test' +import { Key } from 'interface-datastore' +import { FsDatastore } from 'datastore-fs' +import { loadHealthCheckpoint, saveHealthCheckpoint } from '../src/health/persistence.js' +import type { HealthCheckpoint } from '../src/health/state.js' + +const CHECKPOINT_KEY = new Key('/adm/health/checkpoint') +const MEMBERSHIP = 'a'.repeat(64) +const NOW = 1_720_614_998_797 + +const valid: HealthCheckpoint = { + height: 1_720_614_960_000, + completedAt: 1_720_614_998_700, + membershipVersion: MEMBERSHIP, + attestedPeers: 2 +} + +let directory: string +let datastore: FsDatastore + +/** Write a raw value under the checkpoint key, bypassing the writer's shape. */ +async function writeRaw(value: unknown): Promise { + await datastore.put(CHECKPOINT_KEY, new TextEncoder().encode(JSON.stringify(value))) +} + +before(async () => { + directory = await mkdtemp(join(tmpdir(), 'ipfs-node-health-')) + datastore = new FsDatastore(join(directory, 'datastore')) + await datastore.open() +}) + +after(async () => { + await datastore.close() + await rm(directory, { recursive: true, force: true }) +}) + +describe('health checkpoint persistence', () => { + it('survives a clean restart', async () => { + await saveHealthCheckpoint(datastore, valid) + + assert.deepEqual(await loadHealthCheckpoint(datastore, NOW), valid) + }) + + it('reports no checkpoint when the state was never written', async () => { + await datastore.delete(CHECKPOINT_KEY) + + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + await saveHealthCheckpoint(datastore, valid) + }) + + it('ignores a malformed or truncated record', async () => { + await datastore.put(CHECKPOINT_KEY, new TextEncoder().encode('{"height":')) + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + + await writeRaw('not an object') + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + + await writeRaw(null) + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + }) + + it('rejects negative counts and timestamps', async () => { + for (const broken of [ + { ...valid, height: -1 }, + { ...valid, completedAt: -1 }, + { ...valid, attestedPeers: -1 } + ]) { + await writeRaw(broken) + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + } + }) + + it('rejects a membership version that is not a digest', async () => { + for (const version of ['', 'short', 'A'.repeat(64), 'z'.repeat(64)]) { + await writeRaw({ ...valid, membershipVersion: version }) + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + } + }) + + it('rejects a height that could not belong to the round it claims', async () => { + // A round starts at or before the attempt that completed it. A height above + // it would be carried forward by `Math.max` and advertised indefinitely. + await writeRaw({ ...valid, height: valid.completedAt + 1 }) + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + + await writeRaw({ ...valid, height: Number.MAX_SAFE_INTEGER }) + assert.equal(await loadHealthCheckpoint(datastore, NOW), null) + }) + + it('rejects a checkpoint from ahead of the clock after a rollback', async () => { + await saveHealthCheckpoint(datastore, valid) + + // The clock moved back past the write, so the round it claims never happened + // on this node's current timeline. + assert.equal(await loadHealthCheckpoint(datastore, valid.completedAt - 3_600_000), null) + + // Movement inside the tolerated skew still loads. + assert.deepEqual(await loadHealthCheckpoint(datastore, valid.completedAt - 1_000), valid) + }) +}) diff --git a/test/logCapture.test.ts b/test/logCapture.test.ts new file mode 100644 index 0000000..a32672a --- /dev/null +++ b/test/logCapture.test.ts @@ -0,0 +1,102 @@ +import assert from 'node:assert/strict' +import { Writable } from 'node:stream' +import { describe, it } from 'node:test' +import { createApplicationLogger } from '../src/utils/logger.js' + +const CID_V1 = 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' +const CID_V0 = 'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG' +const PEER_ID = '12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq' +const MULTIADDR = `/ip4/194.163.154.252/tcp/4001/p2p/${PEER_ID}` + +/** Collect what the wired logger actually writes, not what a call site intended. */ +function capture(): { logger: ReturnType; lines: () => string } { + let written = '' + const destination = new Writable({ + write(chunk, unusedEncoding, done) { + written += String(chunk) + done() + } + }) + + return { + logger: createApplicationLogger({ level: 'debug', destination }), + lines: () => written + } +} + +describe('application log sanitization', () => { + it('keeps content identifiers out of a template message', () => { + const { logger, lines } = capture() + + logger.info(`No configured node reports holding ${CID_V1}`) + logger.warn(`No node was holding ${CID_V0}; kept the local copy instead`) + + const output = lines() + assert.equal(output.includes(CID_V1), false) + assert.equal(output.includes(CID_V0), false) + assert.equal(output.includes('[cid]'), true) + // The operational meaning of the message survives the scrub. + assert.equal(output.includes('kept the local copy instead'), true) + }) + + it('keeps peer identities and multiaddrs out of a message', () => { + const { logger, lines } = capture() + + logger.info(`Helia is running! PeerID: ${PEER_ID}`) + logger.info(`Start peering ipfs1 node (${MULTIADDR})...`) + + const output = lines() + assert.equal(output.includes(PEER_ID), false) + assert.equal(output.includes('194.163.154.252'), false) + assert.equal(output.includes('[peer]'), true) + assert.equal(output.includes('[multiaddr]'), true) + }) + + it('scrubs structured fields, not only the message', () => { + const { logger, lines } = capture() + + logger.info({ event: 'replication_repair_pass', cursor: CID_V1, peers: [PEER_ID] }, 'pass') + + const output = lines() + assert.equal(output.includes(CID_V1), false) + assert.equal(output.includes(PEER_ID), false) + assert.equal(output.includes('replication_repair_pass'), true) + }) + + it('reports an error without its stack', () => { + const { logger, lines } = capture() + const failure = Object.assign(new Error(`Cannot pin ${CID_V1}`), { code: 'ERR_NOT_FOUND' }) + + logger.error({ err: failure }, 'Repair pass failed') + logger.error(failure) + + const output = lines() + assert.equal(output.includes(' at '), false) + assert.equal(output.includes(CID_V1), false) + assert.equal(output.includes('ERR_NOT_FOUND'), true) + assert.equal(output.includes('Repair pass failed'), true) + }) + + it('drops a stack that reached the message as text', () => { + const { logger, lines } = capture() + + logger.error(`Scan failed: boom\n at scan (/srv/ipfs-node/dist/scan.js:12:9)`) + + const output = lines() + assert.equal(output.includes(' at scan'), false) + assert.equal(output.includes('[stack omitted]'), true) + assert.equal(output.includes('Scan failed: boom'), true) + }) + + it('leaves ordinary operational text alone', () => { + const { logger, lines } = capture() + + logger.info('Upload request carries 3 file(s)') + logger.info({ event: 'storage_demoted_record', holders: 2 }, 'Released a local copy') + + const output = lines() + assert.equal(output.includes('Upload request carries 3 file(s)'), true) + assert.equal(output.includes('"holders":2'), true) + assert.equal(output.includes('[cid]'), false) + }) +}) diff --git a/test/repairCycle.test.ts b/test/repairCycle.test.ts new file mode 100644 index 0000000..9fcb064 --- /dev/null +++ b/test/repairCycle.test.ts @@ -0,0 +1,156 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { after, before, beforeEach, describe, it } from 'node:test' +import { FsDatastore } from 'datastore-fs' +import { FileRegistry, type FileRecord } from '../src/storage/registry.js' +import { nextRepairCycleBatch, resetRepairCycle } from '../src/storage/repairCycle.js' +import { SWEEP_BATCHES } from '../src/storage/sweep.js' + +let directory: string +let datastore: FsDatastore +let registry: FileRegistry +let prefixCounter = 0 + +const record = (cid: string): FileRecord => ({ + cid, + name: cid, + state: 'confirmed', + createdAt: 1, + expiresAt: null, + confirmedAt: 1, + fileSize: 100, + storedBytes: 120, + protectedBytes: 120, + pinned: true, + heldLocally: true, + replicas: [] +}) + +/** Deterministic CIDs that sort in insertion order. */ +const cid = (index: number) => `cid-${String(index).padStart(5, '0')}` + +async function seed(count: number, offset = 0): Promise { + for (let index = 0; index < count; index += 1) { + await registry.withExclusiveCids([cid(offset + index)], async (locked) => + locked.save(record(cid(offset + index))) + ) + } +} + +/** Walk to the end of the cycle, returning every batch it handed out. */ +async function runCycle(): Promise<{ + visited: string[] + last: Awaited> +}> { + const visited: string[] = [] + let cursor: string | undefined + let last = await nextRepairCycleBatch(registry, cursor) + + for (let pass = 0; pass < 50; pass += 1) { + visited.push(...last.cids) + if (last.cycleCompleted) break + cursor = last.nextCursor + last = await nextRepairCycleBatch(registry, cursor) + } + + return { visited, last } +} + +before(async () => { + directory = await mkdtemp(join(tmpdir(), 'ipfs-node-repair-cycle-')) + datastore = new FsDatastore(join(directory, 'datastore')) + await datastore.open() +}) + +after(async () => { + await datastore.close() + await rm(directory, { recursive: true, force: true }) +}) + +beforeEach(() => { + resetRepairCycle() + prefixCounter += 1 + registry = new FileRegistry(datastore, `/adm/repair-cycle-${prefixCounter}`) +}) + +describe('repair cycle candidate list', () => { + it('completes over a set that does not change', async () => { + await seed(SWEEP_BATCHES.repair + 7) + + const { visited, last } = await runCycle() + + assert.equal(last.cycleCompleted, true) + assert.equal(last.uncovered, 0) + assert.equal(new Set(visited).size, SWEEP_BATCHES.repair + 7) + }) + + it('completes immediately on an empty registry', async () => { + const batch = await nextRepairCycleBatch(registry, undefined) + + assert.deepEqual(batch.cids, []) + assert.equal(batch.cycleCompleted, true) + assert.equal(batch.uncovered, 0) + }) + + it('covers a record admitted behind the cursor before completing', async () => { + await seed(SWEEP_BATCHES.repair + 3, 100) + + const first = await nextRepairCycleBatch(registry, undefined) + assert.equal(first.cycleCompleted, false) + + // Sorts before everything the first pass handed out, so a cursor-only sweep + // would never reach it and would still report a completed cycle. + await seed(1, 0) + + const second = await nextRepairCycleBatch(registry, first.nextCursor) + assert.equal(second.cycleCompleted, false, 'the arrival extends the cycle instead of ending it') + + const third = await nextRepairCycleBatch(registry, second.nextCursor) + assert.equal(third.cids.includes(cid(0)), true) + assert.equal(third.cycleCompleted, true) + assert.equal(third.uncovered, 0) + }) + + it('reads the registry once per cycle rather than once per pass', async () => { + await seed(SWEEP_BATCHES.repair * 2 + 1) + + let scans = 0 + const counted = new Proxy(registry, { + get(target, property, receiver) { + if (property === 'all') { + return async () => { + scans += 1 + return target.all() + } + } + return Reflect.get(target, property, receiver) as unknown + } + }) + + let batch = await nextRepairCycleBatch(counted, undefined) + while (!batch.cycleCompleted) { + batch = await nextRepairCycleBatch(counted, batch.nextCursor) + } + + // One scan to build the list, one at the end to name late arrivals. + assert.equal(scans, 2) + }) + + it('rebuilds and resumes from the persisted cursor after a restart', async () => { + await seed(SWEEP_BATCHES.repair + 5) + + const first = await nextRepairCycleBatch(registry, undefined) + resetRepairCycle() + + const resumed = await nextRepairCycleBatch(registry, first.nextCursor) + + assert.equal(resumed.cids.length, 5) + assert.equal( + resumed.cids.some((item) => first.cids.includes(item)), + false + ) + assert.equal(resumed.cycleCompleted, true) + }) +}) From 66ba71b8826271fafe9413269cd778b0d5261253 Mon Sep 17 00:00:00 2001 From: adamantmm Date: Tue, 1 Sep 2026 09:30:12 +0300 Subject: [PATCH 143/167] fix: enhance health state checks and logging for clock consistency and repair cycle coverage --- README.md | 3 +- docs/storage-lifecycle.md | 7 +++- src/health/state.ts | 13 +++++- src/replication.cron.ts | 49 +++------------------- src/storage/repairCycle.ts | 35 +++++++++++++--- src/storage/repairEvidence.ts | 77 +++++++++++++++++++++++++++++++++++ src/storage/service.ts | 6 ++- src/utils/logger.ts | 31 ++++++++++++-- test/healthState.test.ts | 22 ++++++++++ test/logCapture.test.ts | 13 ++++++ test/nodeRoutes.test.ts | 1 + test/repairCycle.test.ts | 20 +++++++++ test/repairEvidence.test.ts | 76 ++++++++++++++++++++++++++++++++++ 13 files changed, 298 insertions(+), 55 deletions(-) create mode 100644 src/storage/repairEvidence.ts create mode 100644 test/repairEvidence.test.ts diff --git a/README.md b/README.md index 2dbe7d9..de0507a 100644 --- a/README.md +++ b/README.md @@ -420,6 +420,7 @@ Example response: }, "checks": { "checkpointFresh": true, + "clockConsistent": true, "helia": true, "startupReconciliation": true, "storageFresh": true, @@ -432,7 +433,7 @@ Example response: The endpoint always returns `200`; consumers must inspect `state`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle with no known backlog, and the configured peer attestations all pass. It freezes on failure. Observation timestamps and ages let clients reject an absolutely stale cluster even when every node reports the same height. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. -State changes are deliberately asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` between checkpoints. Recovery is never decided on a read: returning to `ready` requires a successful checkpoint, so expect up to `health.checkpointIntervalMs` of lag after the underlying fault clears. `checkpointFresh`, `storageFresh`, and `repairFresh` follow the same rule; every other entry in `checks`, along with `membership` and `startup`, describes `evaluatedAt` — the last checkpoint attempt, which may be a failed one — and not the moment of the request. `checkpoint.observedAt` dates the last attempt that _succeeded_, so the two differ whenever the most recent attempt failed. Peers may attest an adjacent round across a boundary, so two healthy nodes can briefly report heights one `checkpointIntervalMs` apart. +State changes are deliberately asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` between checkpoints. Recovery is never decided on a read: returning to `ready` requires a successful checkpoint, so expect up to `health.checkpointIntervalMs` of lag after the underlying fault clears. A clock that moves behind the checkpoint this node already recorded clears `clockConsistent` and stops advancement until it catches up, rather than persisting a round that starts after it finished. `checkpointFresh`, `storageFresh`, and `repairFresh` follow the same rule; every other entry in `checks`, along with `membership` and `startup`, describes `evaluatedAt` — the last checkpoint attempt, which may be a failed one — and not the moment of the request. `checkpoint.observedAt` dates the last attempt that _succeeded_, so the two differ whenever the most recent attempt failed. Peers may attest an adjacent round across a boundary, so two healthy nodes can briefly report heights one `checkpointIntervalMs` apart. `membership.version` identifies the configured peer-set epoch. Changing the node list resets the persisted checkpoint for that node: `height` remains `0` until the first valid checkpoint under the new membership. Clients must compare heights only when membership versions match and treat a version change as a new epoch. diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index d212f9b..59f7841 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -487,7 +487,12 @@ admitted while the cycle was walking; those are appended and covered by the same cycle, for a bounded number of catch-up rounds. A cycle that runs out of rounds still finishes and hands its cursor back, but it is not published as durability evidence: the last cycle that covered its whole set keeps that role, so -readiness ages out on its own schedule instead of advancing on a coverage gap. A cycle containing an unresolved shortfall is complete but unhealthy, +readiness ages out on its own schedule instead of advancing on a coverage gap. + +A restart resumes the cycle from its persisted cursor, but the rebuilt list +cannot tell a record admitted during the downtime from one the lost passes +already visited. Such a cycle finishes without claiming coverage, and the cycle +after it publishes again. A cycle containing an unresolved shortfall is complete but unhealthy, and therefore cannot advance the node health checkpoint. Replication needs the peers to be connected over libp2p, because the copy itself diff --git a/src/health/state.ts b/src/health/state.ts index 0b462da..585ad61 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -70,6 +70,8 @@ export interface HealthSnapshot { } checks: { checkpointFresh: boolean + /** The clock has not moved behind the checkpoint this node already recorded. */ + clockConsistent: boolean helia: boolean startupReconciliation: boolean storageFresh: boolean @@ -100,6 +102,11 @@ export function evaluateHealth( input: HealthInputs ): { snapshot: HealthSnapshot; completed?: HealthCheckpoint } { const prerequisiteChecks = { + // A clock behind the last checkpoint would keep the old height through + // `Math.max` while stamping a lower `completedAt`, persisting a round that + // starts after it finished — the shape `parseCheckpoint` refuses to load. + // Advancement waits for the clock instead of recording that. + clockConsistent: input.previous === null || input.previous.completedAt <= input.now, helia: input.heliaReady, startupReconciliation: input.startupComplete && input.startupHealthy, storageFresh: @@ -111,6 +118,9 @@ export function evaluateHealth( (input.repairHealthy && input.repairBacklog === 0 && input.repairCompletedAt !== null && + // A completion stamped ahead of the clock is not evidence of freshness; + // without the lower bound its negative age would always pass. + input.now >= input.repairCompletedAt && input.now - input.repairCompletedAt <= policy.repairMaxAgeMs), peerAttestations: input.attestedPeers >= policy.requiredPeerCount } @@ -199,7 +209,8 @@ export function refreshHealthSnapshot( (current.checks.repairFresh && current.replication.backlog === 0 && repairAge !== null && - repairAge <= policy.repairMaxAgeMs) + repairAge <= policy.repairMaxAgeMs && + timestamp >= (current.replication.lastCompleteAt ?? timestamp)) } const freshnessFailed = !checks.storageFresh || !checks.repairFresh diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 9d679b4..1666685 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -8,6 +8,7 @@ import { Key } from 'interface-datastore' import { createHash } from 'node:crypto' import { membershipVersion } from './health/membership.js' import { RepairCycleDriver, type RepairTrigger } from './storage/repairCycleDriver.js' +import { parseEvidence, type RepairCycleEvidence } from './storage/repairEvidence.js' let running = false let lastReport: RepairReport | null = null @@ -15,24 +16,6 @@ const REPAIR_STATE_KEY = new Key('/adm/health/repair-cycle') const encoder = new TextEncoder() const decoder = new TextDecoder() -interface RepairCycleEvidence { - membershipVersion: string - policyVersion: string - cursor?: string - startedAt: number - /** Records were admitted behind the cursor, so this cycle cannot cover them. */ - superseded: boolean - examined: number - checked: number - underReplicated: number - repaired: number - stillMissing: number - unrecoverable: number - lastCompletedAt: number | null - lastCompletedSuccessfully: boolean - lastCompletedBacklog: number -} - let evidence: RepairCycleEvidence | null = null function currentPolicyVersion(): string { @@ -57,7 +40,7 @@ function emptyEvidence(now = Date.now()): RepairCycleEvidence { } } -async function loadEvidence(): Promise { +async function loadEvidence(now: number = Date.now()): Promise { if (evidence !== null) return evidence try { @@ -69,37 +52,17 @@ async function loadEvidence(): Promise { logger.warn('Ignoring malformed persisted repair-cycle evidence') parsed = {} } - evidence = - typeof parsed.membershipVersion === 'string' && - typeof parsed.policyVersion === 'string' && - (parsed.cursor === undefined || typeof parsed.cursor === 'string') && - Number.isSafeInteger(parsed.startedAt) && - (parsed.examined === undefined || Number.isSafeInteger(parsed.examined)) && - (parsed.superseded === undefined || typeof parsed.superseded === 'boolean') && - Number.isSafeInteger(parsed.checked) && - Number.isSafeInteger(parsed.underReplicated) && - Number.isSafeInteger(parsed.repaired) && - Number.isSafeInteger(parsed.stillMissing) && - Number.isSafeInteger(parsed.unrecoverable) && - (parsed.lastCompletedAt === null || Number.isSafeInteger(parsed.lastCompletedAt)) && - typeof parsed.lastCompletedSuccessfully === 'boolean' && - Number.isSafeInteger(parsed.lastCompletedBacklog) - ? ({ - ...parsed, - examined: parsed.examined ?? parsed.checked, - superseded: parsed.superseded ?? false - } as RepairCycleEvidence) - : emptyEvidence() + evidence = parseEvidence(parsed, now) ?? emptyEvidence(now) } catch (err) { if ((err as { code?: string }).code !== 'ERR_NOT_FOUND') throw err - evidence = emptyEvidence() + evidence = emptyEvidence(now) } if ( evidence.membershipVersion !== membershipVersion(config.nodes) || evidence.policyVersion !== currentPolicyVersion() ) { - evidence = emptyEvidence() + evidence = emptyEvidence(now) } return evidence @@ -145,7 +108,7 @@ export async function repairUnderReplicatedFiles(): Promise { // but it is not published as fresh durability evidence: the last genuinely // complete cycle stays in place instead, so readiness decays on its own // schedule rather than advancing on a coverage gap. - const superseded = current.superseded || lastReport.uncovered > 0 + const superseded = current.superseded || lastReport.uncovered > 0 || !lastReport.coverageProven const publishes = lastReport.cycleCompleted && !superseded await saveEvidence( diff --git a/src/storage/repairCycle.ts b/src/storage/repairCycle.ts index e580425..5036039 100644 --- a/src/storage/repairCycle.ts +++ b/src/storage/repairCycle.ts @@ -24,6 +24,15 @@ export interface RepairCycleBatch { * signal that the finished cycle is not evidence about the whole set. */ uncovered: number + /** + * Whether a completed cycle may be claimed to have covered its candidate set. + * + * False once a restart resumed the cycle from its persisted cursor: the + * rebuilt list cannot tell a record admitted during the downtime from one the + * lost passes already visited, so both sit below the cursor and neither shows + * up as a late arrival. + */ + coverageProven: boolean } interface RepairCycle { @@ -32,6 +41,8 @@ interface RepairCycle { /** How many of them have been handed out. */ index: number refreshes: number + /** The list was rebuilt from a cursor, so its prefix is not proven covered. */ + resumed: boolean } /** @@ -100,14 +111,22 @@ export async function nextRepairCycleBatch( ): Promise { if (cycle === undefined || cursor === undefined) { const cids = await candidateCids(registry) - cycle = { cids, index: resumeIndex(cids, cursor), refreshes: 0 } + // Rebuilding from a cursor cannot reconstruct what the lost passes visited. + cycle = { cids, index: resumeIndex(cids, cursor), refreshes: 0, resumed: cursor !== undefined } } + const coverageProven = !cycle.resumed const batch = cycle.cids.slice(cycle.index, cycle.index + SWEEP_BATCHES.repair) cycle.index += batch.length if (cycle.index < cycle.cids.length) { - return { cids: batch, cycleCompleted: false, nextCursor: batch.at(-1) ?? cursor, uncovered: 0 } + return { + cids: batch, + cycleCompleted: false, + nextCursor: batch.at(-1) ?? cursor, + uncovered: 0, + coverageProven + } } // End of the committed list. One scan decides whether the cycle covered the @@ -117,16 +136,22 @@ export async function nextRepairCycleBatch( if (fresh.length === 0) { cycle = undefined - return { cids: batch, cycleCompleted: true, uncovered: 0 } + return { cids: batch, cycleCompleted: true, uncovered: 0, coverageProven } } if (cycle.refreshes < MAX_CATCH_UP_ROUNDS) { cycle.cids.push(...fresh) cycle.refreshes += 1 - return { cids: batch, cycleCompleted: false, nextCursor: batch.at(-1) ?? cursor, uncovered: 0 } + return { + cids: batch, + cycleCompleted: false, + nextCursor: batch.at(-1) ?? cursor, + uncovered: 0, + coverageProven + } } cycle = undefined - return { cids: batch, cycleCompleted: true, uncovered: fresh.length } + return { cids: batch, cycleCompleted: true, uncovered: fresh.length, coverageProven } } diff --git a/src/storage/repairEvidence.ts b/src/storage/repairEvidence.ts new file mode 100644 index 0000000..712ecd3 --- /dev/null +++ b/src/storage/repairEvidence.ts @@ -0,0 +1,77 @@ +/** + * Validation of the persisted replication-repair cycle evidence. + * + * Kept apart from `replication.cron.ts` so the rules can be exercised without + * importing a module that starts a Helia node and opens the datastore. + */ + +export interface RepairCycleEvidence { + membershipVersion: string + policyVersion: string + cursor?: string + startedAt: number + /** The cycle cannot claim to have covered its candidate set. */ + superseded: boolean + examined: number + checked: number + underReplicated: number + repaired: number + stillMissing: number + unrecoverable: number + lastCompletedAt: number | null + lastCompletedSuccessfully: boolean + lastCompletedBacklog: number +} + +function isCount(value: unknown): value is number { + return Number.isSafeInteger(value) && (value as number) >= 0 +} + +/** + * Accept only repair evidence this node could have written. + * + * A negative counter or a completion stamped ahead of the clock is not merely + * malformed: `evaluateHealth` measures repair freshness from `lastCompletedAt`, + * so a future timestamp would read as permanently fresh and let a node with an + * unverified registry advertise itself as ready. + * + * @param parsed value decoded from the datastore + * @param now current time, used to reject state from ahead of the clock + * @returns the evidence, or `null` when it cannot be trusted + */ +export function parseEvidence( + parsed: Partial, + now: number +): RepairCycleEvidence | null { + const valid = + typeof parsed.membershipVersion === 'string' && + typeof parsed.policyVersion === 'string' && + (parsed.cursor === undefined || typeof parsed.cursor === 'string') && + isCount(parsed.startedAt) && + parsed.startedAt <= now && + (parsed.examined === undefined || isCount(parsed.examined)) && + (parsed.superseded === undefined || typeof parsed.superseded === 'boolean') && + isCount(parsed.checked) && + isCount(parsed.underReplicated) && + isCount(parsed.repaired) && + isCount(parsed.stillMissing) && + isCount(parsed.unrecoverable) && + (parsed.lastCompletedAt === null || + (isCount(parsed.lastCompletedAt) && parsed.lastCompletedAt <= now)) && + typeof parsed.lastCompletedSuccessfully === 'boolean' && + isCount(parsed.lastCompletedBacklog) && + // A cycle cannot have checked more records than it visited, and cannot + // report a clean completion while carrying a backlog. + parsed.checked <= (parsed.examined ?? parsed.checked) && + (!parsed.lastCompletedSuccessfully || parsed.lastCompletedBacklog === 0) + + if (!valid) { + return null + } + + return { + ...parsed, + examined: parsed.examined ?? parsed.checked, + superseded: parsed.superseded ?? false + } as RepairCycleEvidence +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 8c59d2c..adbb556 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -813,6 +813,8 @@ export interface RepairReport { cycleCompleted: boolean /** Candidates admitted during the cycle that its catch-up rounds could not cover. */ uncovered: number + /** Whether a completed cycle may be claimed to have covered its candidate set. */ + coverageProven: boolean /** Durable resume cursor for the next pass. */ nextCursor?: string } @@ -980,7 +982,8 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom rescued: [], unrecoverable: [], cycleCompleted: true, - uncovered: 0 + uncovered: 0, + coverageProven: true } if (!config.replication.enabled) { @@ -993,6 +996,7 @@ export async function repairReplication(options: { cursor?: string } = {}): Prom report.cycleCompleted = selected.cycleCompleted report.nextCursor = selected.nextCursor report.uncovered = selected.uncovered + report.coverageProven = selected.coverageProven // The cycle committed to these CIDs one scan ago, so each record is read // again here rather than trusted from that list. A record that has since been diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 93f1b31..d6e3ca1 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,3 +1,4 @@ +import { CID } from 'multiformats/cid' import { pino } from 'pino' import type { DestinationStream, LogFn, Logger } from 'pino' import { pinoHttp } from 'pino-http' @@ -70,11 +71,32 @@ export function routeShape(url: string | undefined): string { const LOG_SCRUBBERS: Array<[RegExp, string]> = [ [/\/(?:ip4|ip6|dns|dns4|dns6|dnsaddr)\/[^\s"',)]+/g, '[multiaddr]'], [/\b12D3Koo[1-9A-HJ-NP-Za-km-z]{40,}\b/g, '[peer]'], - [/\bQm[1-9A-HJ-NP-Za-km-z]{44}\b/g, '[cid]'], - [/\bb[a-z2-7]{58,}\b/g, '[cid]'], [/\n\s+at\s+[\s\S]*$/, ' [stack omitted]'] ] +/** + * Tokens shaped like an encoded CID, by multibase prefix. + * + * A length threshold alone would miss short forms — an identity multihash such + * as `bafkqad3qojuxmylumuqhaylznrxwcza` is 32 characters — and lowering it would + * start matching ordinary words. Each match is confirmed by the parser instead, + * so the shape only has to be cheap enough to skip prose. + */ +const CID_CANDIDATE = + /\b(?:b[a-z2-7]{15,}|B[A-Z2-7]{15,}|z[1-9A-HJ-NP-Za-km-z]{15,}|f[0-9a-f]{15,}|Qm[1-9A-HJ-NP-Za-km-z]{44})\b/g + +/** Replace every token the CID parser accepts, whatever its version or encoding. */ +function scrubCids(value: string): string { + return value.replace(CID_CANDIDATE, (token) => { + try { + CID.parse(token) + return '[cid]' + } catch { + return token + } + }) +} + /** * Remove content identifiers, peer identities, and stack traces from a message. * @@ -87,11 +109,14 @@ const LOG_SCRUBBERS: Array<[RegExp, string]> = [ * @returns the same text with every identifier replaced by a fixed placeholder */ export function scrubLogValue(value: string): string { - return LOG_SCRUBBERS.reduce((text, [pattern, placeholder]) => { + const scrubbed = LOG_SCRUBBERS.reduce((text, [pattern, placeholder]) => { // A global regex carries `lastIndex` between calls; reset before reuse. pattern.lastIndex = 0 return text.replace(pattern, placeholder) }, value) + + CID_CANDIDATE.lastIndex = 0 + return scrubCids(scrubbed) } /** Apply {@link scrubLogValue} through the strings of one log argument. */ diff --git a/test/healthState.test.ts b/test/healthState.test.ts index fe11857..772ddfc 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -143,6 +143,28 @@ describe('health checkpoint state', () => { assert.equal(read.evaluatedAt, 13_000) }) + it('refuses to advance while the clock is behind the last checkpoint', () => { + const prior = evaluateHealth(policy, healthy(12_345)).completed! + // The clock moved back past the checkpoint this node already recorded. + const rolledBack = evaluateHealth(policy, { ...healthy(9_000), previous: prior }) + + assert.equal(rolledBack.snapshot.checks.clockConsistent, false) + assert.notEqual(rolledBack.snapshot.state, 'ready') + // Nothing is persisted, so no round can claim to have started after it ended. + assert.equal(rolledBack.completed, undefined) + assert.equal(rolledBack.snapshot.height, prior.height) + }) + + it('does not treat a repair completion from the future as fresh', () => { + const result = evaluateHealth(policy, { + ...healthy(12_345), + repairCompletedAt: 20_000 + }) + + assert.equal(result.snapshot.checks.repairFresh, false) + assert.notEqual(result.snapshot.state, 'ready') + }) + it('keeps the maximum checkpoint age inclusive', () => { const ready = evaluateHealth(policy, healthy(12_345)).snapshot const boundary = refreshHealthSnapshot(ready, 15_345, policy) diff --git a/test/logCapture.test.ts b/test/logCapture.test.ts index a32672a..7348aeb 100644 --- a/test/logCapture.test.ts +++ b/test/logCapture.test.ts @@ -39,6 +39,19 @@ describe('application log sanitization', () => { assert.equal(output.includes('kept the local copy instead'), true) }) + it('covers CID forms a length threshold would miss', () => { + const { logger, lines } = capture() + // Valid CIDv1 with an identity multihash: 32 characters, well under the + // length of the common sha2-256 form. + const shortCid = 'bafkqad3qojuxmylumuqhaylznrxwcza' + + logger.error(`Cannot pin ${shortCid}`) + + const output = lines() + assert.equal(output.includes(shortCid), false) + assert.equal(output.includes('[cid]'), true) + }) + it('keeps peer identities and multiaddrs out of a message', () => { const { logger, lines } = capture() diff --git a/test/nodeRoutes.test.ts b/test/nodeRoutes.test.ts index 0ba6948..d7e1b41 100644 --- a/test/nodeRoutes.test.ts +++ b/test/nodeRoutes.test.ts @@ -20,6 +20,7 @@ const health: HealthSnapshot = { replication: { repairRequired: true, lastCompleteAt: 12_345, ageMs: 0, backlog: 0 }, checks: { checkpointFresh: true, + clockConsistent: true, helia: true, startupReconciliation: true, storageFresh: true, diff --git a/test/repairCycle.test.ts b/test/repairCycle.test.ts index 9fcb064..d28d3e7 100644 --- a/test/repairCycle.test.ts +++ b/test/repairCycle.test.ts @@ -142,6 +142,7 @@ describe('repair cycle candidate list', () => { await seed(SWEEP_BATCHES.repair + 5) const first = await nextRepairCycleBatch(registry, undefined) + assert.equal(first.coverageProven, true) resetRepairCycle() const resumed = await nextRepairCycleBatch(registry, first.nextCursor) @@ -153,4 +154,23 @@ describe('repair cycle candidate list', () => { ) assert.equal(resumed.cycleCompleted, true) }) + + it('does not claim coverage for a cycle rebuilt after a restart', async () => { + await seed(SWEEP_BATCHES.repair + 3, 100) + + const first = await nextRepairCycleBatch(registry, undefined) + resetRepairCycle() + + // Admitted while the process was down, sorting before the cursor. The + // rebuilt list contains it, so the end-of-cycle scan cannot name it as a + // late arrival — the cycle has to say so itself. + await seed(1, 0) + + const resumed = await nextRepairCycleBatch(registry, first.nextCursor) + + assert.equal(resumed.cycleCompleted, true) + assert.equal(resumed.uncovered, 0) + assert.equal(resumed.coverageProven, false) + assert.equal(resumed.cids.includes(cid(0)), false) + }) }) diff --git a/test/repairEvidence.test.ts b/test/repairEvidence.test.ts new file mode 100644 index 0000000..fb5b028 --- /dev/null +++ b/test/repairEvidence.test.ts @@ -0,0 +1,76 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { parseEvidence, type RepairCycleEvidence } from '../src/storage/repairEvidence.js' + +const NOW = 1_720_614_998_797 + +const valid: RepairCycleEvidence = { + membershipVersion: 'a'.repeat(64), + policyVersion: 'b'.repeat(64), + startedAt: NOW - 60_000, + superseded: false, + examined: 120, + checked: 100, + underReplicated: 4, + repaired: 4, + stillMissing: 0, + unrecoverable: 0, + lastCompletedAt: NOW - 30_000, + lastCompletedSuccessfully: true, + lastCompletedBacklog: 0 +} + +describe('persisted repair evidence', () => { + it('accepts state this node could have written', () => { + assert.deepEqual(parseEvidence({ ...valid }, NOW), valid) + }) + + it('rejects a completion stamped ahead of the clock', () => { + // `evaluateHealth` measures repair freshness from this value, so a future + // timestamp would read as permanently fresh. + assert.equal(parseEvidence({ ...valid, lastCompletedAt: NOW + 3_600_000 }, NOW), null) + assert.equal(parseEvidence({ ...valid, startedAt: NOW + 1_000 }, NOW), null) + }) + + it('rejects negative counters', () => { + for (const field of [ + 'examined', + 'checked', + 'underReplicated', + 'repaired', + 'stillMissing', + 'unrecoverable', + 'lastCompletedBacklog' + ] as const) { + assert.equal(parseEvidence({ ...valid, [field]: -1 }, NOW), null, field) + } + }) + + it('rejects internally inconsistent evidence', () => { + // More records checked than the cycle ever visited. + assert.equal(parseEvidence({ ...valid, examined: 10, checked: 20 }, NOW), null) + + // A clean completion cannot carry a backlog. + assert.equal( + parseEvidence({ ...valid, lastCompletedSuccessfully: true, lastCompletedBacklog: 3 }, NOW), + null + ) + }) + + it('accepts a cycle that has never completed', () => { + const running = { ...valid, lastCompletedAt: null, lastCompletedSuccessfully: false } + + assert.deepEqual(parseEvidence(running, NOW), running) + }) + + it('fills in fields written by an earlier release', () => { + const legacy = { ...valid } + delete (legacy as Partial).examined + delete (legacy as Partial).superseded + + const parsed = parseEvidence(legacy, NOW) + + assert.equal(parsed?.examined, valid.checked) + assert.equal(parsed?.superseded, false) + }) +}) From 5c4baf54ab3947259d5dbba9b232956c2f5e221e Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 1 Sep 2026 11:43:43 +0300 Subject: [PATCH 144/167] fix: improve health state consistency checks and enhance error handling for repair evidence parsing --- src/health/state.ts | 29 ++++++++++++++++++++++++----- src/replication.cron.ts | 6 +++--- src/storage/repairEvidence.ts | 16 +++++++++++----- src/utils/logger.ts | 11 ++++++----- test/healthState.test.ts | 15 +++++++++++++++ test/logCapture.test.ts | 19 +++++++++++++------ test/repairEvidence.test.ts | 8 ++++++++ 7 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src/health/state.ts b/src/health/state.ts index 585ad61..5a9b24e 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -199,20 +199,39 @@ export function refreshHealthSnapshot( const checkpointAge = age(timestamp, current.checkpoint.observedAt) const storageAge = age(timestamp, current.storage.measuredAt) const repairAge = age(timestamp, current.replication.lastCompleteAt) + + // `age` clamps a negative duration to zero, so a clock behind the evidence in + // this snapshot would read as brand new rather than as impossible. Each + // observation is compared against the request time explicitly instead, and the + // endpoint fails safe on the first read after a rollback rather than waiting + // for the next scheduled evaluation. + const notInFuture = (observedAt: number | null): boolean => + observedAt === null || timestamp >= observedAt + const clockConsistent = + current.checks.clockConsistent && + notInFuture(current.checkpoint.observedAt) && + notInFuture(current.storage.measuredAt) && + notInFuture(current.replication.lastCompleteAt) + const checks = { ...current.checks, - checkpointFresh: checkpointAge !== null && checkpointAge <= current.checkpoint.maxAgeMs, + clockConsistent, + checkpointFresh: + clockConsistent && checkpointAge !== null && checkpointAge <= current.checkpoint.maxAgeMs, storageFresh: - current.checks.storageFresh && storageAge !== null && storageAge <= policy.storageMaxAgeMs, + current.checks.storageFresh && + clockConsistent && + storageAge !== null && + storageAge <= policy.storageMaxAgeMs, repairFresh: !current.replication.repairRequired || (current.checks.repairFresh && + clockConsistent && current.replication.backlog === 0 && repairAge !== null && - repairAge <= policy.repairMaxAgeMs && - timestamp >= (current.replication.lastCompleteAt ?? timestamp)) + repairAge <= policy.repairMaxAgeMs) } - const freshnessFailed = !checks.storageFresh || !checks.repairFresh + const freshnessFailed = !checks.clockConsistent || !checks.storageFresh || !checks.repairFresh return { ...current, diff --git a/src/replication.cron.ts b/src/replication.cron.ts index 1666685..e00ef6d 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -45,12 +45,12 @@ async function loadEvidence(now: number = Date.now()): Promise + let parsed: unknown try { - parsed = JSON.parse(decoder.decode(stored)) as Partial + parsed = JSON.parse(decoder.decode(stored)) as unknown } catch { logger.warn('Ignoring malformed persisted repair-cycle evidence') - parsed = {} + parsed = null } evidence = parseEvidence(parsed, now) ?? emptyEvidence(now) } catch (err) { diff --git a/src/storage/repairEvidence.ts b/src/storage/repairEvidence.ts index 712ecd3..e53943d 100644 --- a/src/storage/repairEvidence.ts +++ b/src/storage/repairEvidence.ts @@ -35,14 +35,20 @@ function isCount(value: unknown): value is number { * so a future timestamp would read as permanently fresh and let a node with an * unverified registry advertise itself as ready. * - * @param parsed value decoded from the datastore + * @param value raw value decoded from the datastore, of any shape * @param now current time, used to reject state from ahead of the clock * @returns the evidence, or `null` when it cannot be trusted */ -export function parseEvidence( - parsed: Partial, - now: number -): RepairCycleEvidence | null { +export function parseEvidence(value: unknown, now: number): RepairCycleEvidence | null { + // `JSON.parse` legally returns `null` and scalars, and a cast does not change + // that at runtime. Reading a field off one would throw out of `loadEvidence`, + // whose caller is awaited during startup, so an untrusted payload would stop + // the node from starting instead of falling back to an empty cycle. + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return null + } + + const parsed = value as Partial const valid = typeof parsed.membershipVersion === 'string' && typeof parsed.policyVersion === 'string' && diff --git a/src/utils/logger.ts b/src/utils/logger.ts index d6e3ca1..cda1ee1 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -77,13 +77,14 @@ const LOG_SCRUBBERS: Array<[RegExp, string]> = [ /** * Tokens shaped like an encoded CID, by multibase prefix. * - * A length threshold alone would miss short forms — an identity multihash such - * as `bafkqad3qojuxmylumuqhaylznrxwcza` is 32 characters — and lowering it would - * start matching ordinary words. Each match is confirmed by the parser instead, - * so the shape only has to be cheap enough to skip prose. + * The bounds are the shortest a CID can be, not a guess at what looks like one: + * an identity multihash over empty bytes encodes to `bafkqaaa`, eight + * characters. Ordinary words are inside these bounds too, which is why the + * decision belongs to the parser below rather than to the shape — the shape only + * has to be cheap enough that most prose never reaches it. */ const CID_CANDIDATE = - /\b(?:b[a-z2-7]{15,}|B[A-Z2-7]{15,}|z[1-9A-HJ-NP-Za-km-z]{15,}|f[0-9a-f]{15,}|Qm[1-9A-HJ-NP-Za-km-z]{44})\b/g + /\b(?:b[a-z2-7]{6,}|B[A-Z2-7]{6,}|z[1-9A-HJ-NP-Za-km-z]{6,}|f[0-9a-f]{7,}|Qm[1-9A-HJ-NP-Za-km-z]{44})\b/g /** Replace every token the CID parser accepts, whatever its version or encoding. */ function scrubCids(value: string): string { diff --git a/test/healthState.test.ts b/test/healthState.test.ts index 772ddfc..68de1da 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -165,6 +165,21 @@ describe('health checkpoint state', () => { assert.notEqual(result.snapshot.state, 'ready') }) + it('fails safe on the first read after the clock moves back', () => { + const ready = evaluateHealth(policy, healthy(10_000)).snapshot + assert.equal(ready.state, 'ready') + + // `age` clamps a negative duration to zero, so without an explicit lower + // bound this would read as a brand new observation instead of an impossible + // one, and stay `ready` until the next scheduled evaluation. + const rolledBack = refreshHealthSnapshot(ready, 5_000, policy) + + assert.equal(rolledBack.checks.clockConsistent, false) + assert.equal(rolledBack.checks.checkpointFresh, false) + assert.equal(rolledBack.checks.storageFresh, false) + assert.equal(rolledBack.state, 'degraded') + }) + it('keeps the maximum checkpoint age inclusive', () => { const ready = evaluateHealth(policy, healthy(12_345)).snapshot const boundary = refreshHealthSnapshot(ready, 15_345, policy) diff --git a/test/logCapture.test.ts b/test/logCapture.test.ts index 7348aeb..f6859d5 100644 --- a/test/logCapture.test.ts +++ b/test/logCapture.test.ts @@ -1,6 +1,9 @@ import assert from 'node:assert/strict' import { Writable } from 'node:stream' import { describe, it } from 'node:test' +import { CID } from 'multiformats/cid' +import * as raw from 'multiformats/codecs/raw' +import { identity } from 'multiformats/hashes/identity' import { createApplicationLogger } from '../src/utils/logger.js' const CID_V1 = 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' @@ -41,15 +44,19 @@ describe('application log sanitization', () => { it('covers CID forms a length threshold would miss', () => { const { logger, lines } = capture() - // Valid CIDv1 with an identity multihash: 32 characters, well under the - // length of the common sha2-256 form. - const shortCid = 'bafkqad3qojuxmylumuqhaylznrxwcza' + // Identity multihash over empty bytes: `bafkqaaa`, the shortest CID there + // is, and one an all-CID guarantee has to cover like any other. + const shortest = CID.createV1(raw.code, identity.digest(new Uint8Array())).toString() + const short = 'bafkqad3qojuxmylumuqhaylznrxwcza' - logger.error(`Cannot pin ${shortCid}`) + logger.error(`Cannot pin ${shortest}`) + logger.error(`Cannot pin ${short}`) const output = lines() - assert.equal(output.includes(shortCid), false) - assert.equal(output.includes('[cid]'), true) + assert.equal(shortest, 'bafkqaaa') + assert.equal(output.includes(shortest), false) + assert.equal(output.includes(short), false) + assert.equal(output.match(/\[cid\]/g)?.length, 2) }) it('keeps peer identities and multiaddrs out of a message', () => { diff --git a/test/repairEvidence.test.ts b/test/repairEvidence.test.ts index fb5b028..8642f2b 100644 --- a/test/repairEvidence.test.ts +++ b/test/repairEvidence.test.ts @@ -25,6 +25,14 @@ describe('persisted repair evidence', () => { assert.deepEqual(parseEvidence({ ...valid }, NOW), valid) }) + it('rejects a payload that is not an object', () => { + // `JSON.parse` legally returns these, and reading a field off one would + // throw out of the startup path instead of falling back to an empty cycle. + for (const value of [null, 42, 'evidence', true, [valid]]) { + assert.equal(parseEvidence(value, NOW), null, JSON.stringify(value)) + } + }) + it('rejects a completion stamped ahead of the clock', () => { // `evaluateHealth` measures repair freshness from this value, so a future // timestamp would read as permanently fresh. From 9d823f3f0736cf74dab12d20975258fc586961ec Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 1 Sep 2026 12:18:38 +0300 Subject: [PATCH 145/167] fix: reject future observations in the evaluator and bound the log scrubber walk Compute clock consistency across every recorded time a checkpoint attempt trusts, not only the previous checkpoint. Storage freshness kept an upper bound alone, so with no previous checkpoint to notice a rollback nothing was watching at all -- the state after one that was discarded, or after a membership change reset it. A measurement from ahead of the attempt could then complete a checkpoint. The rule now lives in one helper shared with the read-time refresh, so the two paths cannot drift apart again. Replace a subtree past the scrubber's depth limit instead of returning it. Handing it back unchanged let an identifier nested below the cutoff reach the log, which is the leak the walk exists to close. The depth test moved below the primitive check, so a scalar at any depth is still returned on its own terms. Co-Authored-By: Claude Opus 5 --- src/health/state.ts | 52 +++++++++++++++++++++++++--------------- src/utils/logger.ts | 18 +++++++++++++- test/healthState.test.ts | 15 ++++++++++++ test/logCapture.test.ts | 10 ++++++++ 4 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/health/state.ts b/src/health/state.ts index 5a9b24e..eaa9574 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -85,6 +85,17 @@ function age(now: number, observedAt: number | null): number | null { return observedAt === null ? null : Math.max(0, now - observedAt) } +/** + * Whether an observation could have been made by the time it is being read. + * + * `age` clamps a negative duration to zero, so a timestamp from ahead of the + * clock reads as a brand new observation rather than an impossible one. Every + * path that trusts a recorded time checks this first. + */ +function notInFuture(now: number, observedAt: number | null): boolean { + return observedAt === null || now >= observedAt +} + /** Return the fixed checkpoint round containing `time`. */ export function checkpointRound(time: number, intervalMs: number): number { return Math.floor(time / intervalMs) * intervalMs @@ -101,26 +112,33 @@ export function evaluateHealth( policy: HealthConfig, input: HealthInputs ): { snapshot: HealthSnapshot; completed?: HealthCheckpoint } { + // Every recorded time this attempt trusts, checked against the same rule the + // read-time refresh applies. A clock behind the last checkpoint would keep the + // old height through `Math.max` while stamping a lower `completedAt`, + // persisting a round that starts after it finished — the shape + // `parseCheckpoint` refuses to load. A storage or repair observation from the + // future is the same problem without a previous checkpoint to notice it, which + // is exactly the state after a rollback that discarded one. + const clockConsistent = + notInFuture(input.now, input.previous?.completedAt ?? null) && + notInFuture(input.now, input.storageUpdatedAt) && + notInFuture(input.now, input.repairCompletedAt) + const prerequisiteChecks = { - // A clock behind the last checkpoint would keep the old height through - // `Math.max` while stamping a lower `completedAt`, persisting a round that - // starts after it finished — the shape `parseCheckpoint` refuses to load. - // Advancement waits for the clock instead of recording that. - clockConsistent: input.previous === null || input.previous.completedAt <= input.now, + clockConsistent, helia: input.heliaReady, startupReconciliation: input.startupComplete && input.startupHealthy, storageFresh: + clockConsistent && input.storageUpdatedAt !== null && input.now - input.storageUpdatedAt <= policy.storageMaxAgeMs, storageReserve: input.storageAvailableBytes >= input.storageReservedBytes, repairFresh: !input.repairRequired || - (input.repairHealthy && + (clockConsistent && + input.repairHealthy && input.repairBacklog === 0 && input.repairCompletedAt !== null && - // A completion stamped ahead of the clock is not evidence of freshness; - // without the lower bound its negative age would always pass. - input.now >= input.repairCompletedAt && input.now - input.repairCompletedAt <= policy.repairMaxAgeMs), peerAttestations: input.attestedPeers >= policy.requiredPeerCount } @@ -200,18 +218,14 @@ export function refreshHealthSnapshot( const storageAge = age(timestamp, current.storage.measuredAt) const repairAge = age(timestamp, current.replication.lastCompleteAt) - // `age` clamps a negative duration to zero, so a clock behind the evidence in - // this snapshot would read as brand new rather than as impossible. Each - // observation is compared against the request time explicitly instead, and the - // endpoint fails safe on the first read after a rollback rather than waiting - // for the next scheduled evaluation. - const notInFuture = (observedAt: number | null): boolean => - observedAt === null || timestamp >= observedAt + // The endpoint fails safe on the first read after a rollback rather than + // waiting for the next scheduled evaluation. The conjunction keeps it one-way: + // a read can clear the flag, never restore it. const clockConsistent = current.checks.clockConsistent && - notInFuture(current.checkpoint.observedAt) && - notInFuture(current.storage.measuredAt) && - notInFuture(current.replication.lastCompleteAt) + notInFuture(timestamp, current.checkpoint.observedAt) && + notInFuture(timestamp, current.storage.measuredAt) && + notInFuture(timestamp, current.replication.lastCompleteAt) const checks = { ...current.checks, diff --git a/src/utils/logger.ts b/src/utils/logger.ts index cda1ee1..2a8d662 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -120,6 +120,18 @@ export function scrubLogValue(value: string): string { return scrubCids(scrubbed) } +/** + * How deep the scrubber walks a structured argument. + * + * Beyond this the subtree is replaced rather than returned: handing it back + * unchanged would let an identifier below the cutoff reach the log, which is + * the leak the walk exists to close. + */ +const MAX_SCRUB_DEPTH = 4 + +/** Stands in for a subtree past {@link MAX_SCRUB_DEPTH}. */ +const OMITTED = '[omitted]' + /** Apply {@link scrubLogValue} through the strings of one log argument. */ function scrubLogArgument(value: unknown, depth = 0): unknown { if (typeof value === 'string') { @@ -139,10 +151,14 @@ function scrubLogArgument(value: unknown, depth = 0): unknown { return scrubbed } - if (value === null || typeof value !== 'object' || depth > 4) { + if (value === null || typeof value !== 'object') { return value } + if (depth > MAX_SCRUB_DEPTH) { + return OMITTED + } + if (Array.isArray(value)) { return value.map((item) => scrubLogArgument(item, depth + 1)) } diff --git a/test/healthState.test.ts b/test/healthState.test.ts index 68de1da..10b08bb 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -165,6 +165,21 @@ describe('health checkpoint state', () => { assert.notEqual(result.snapshot.state, 'ready') }) + it('refuses a storage measurement from ahead of the attempt', () => { + // No previous checkpoint to notice the rollback — the state after one that + // was discarded, or after a membership change reset it. + const result = evaluateHealth(policy, { + ...healthy(5_000), + storageUpdatedAt: 10_000, + previous: null + }) + + assert.equal(result.snapshot.checks.clockConsistent, false) + assert.equal(result.snapshot.checks.storageFresh, false) + assert.notEqual(result.snapshot.state, 'ready') + assert.equal(result.completed, undefined) + }) + it('fails safe on the first read after the clock moves back', () => { const ready = evaluateHealth(policy, healthy(10_000)).snapshot assert.equal(ready.state, 'ready') diff --git a/test/logCapture.test.ts b/test/logCapture.test.ts index f6859d5..4b9b602 100644 --- a/test/logCapture.test.ts +++ b/test/logCapture.test.ts @@ -83,6 +83,16 @@ describe('application log sanitization', () => { assert.equal(output.includes('replication_repair_pass'), true) }) + it('omits a subtree past the depth limit rather than passing it through', () => { + const { logger, lines } = capture() + + logger.info({ a: { b: { c: { d: { e: { cid: CID_V1 } } } } } }, 'nested') + + const output = lines() + assert.equal(output.includes(CID_V1), false) + assert.equal(output.includes('[omitted]'), true) + }) + it('reports an error without its stack', () => { const { logger, lines } = capture() const failure = Object.assign(new Error(`Cannot pin ${CID_V1}`), { code: 'ERR_NOT_FOUND' }) From d02d73fd78d6165e15457fc292d41e1c5be5c33b Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sat, 12 Sep 2026 23:15:59 +0300 Subject: [PATCH 146/167] feat: add docs and position as oss --- .dockerignore | 39 + .github/workflows/docker.yml | 91 + .github/workflows/docs.yml | 93 + .github/workflows/publish-docker.yml | 143 + .gitignore | 9 + .markdownlint.jsonc | 1 + .prettierignore | 9 + AGENTS.md | 48 + Dockerfile | 99 + README.md | 623 ++- docker/config.example.json5 | 114 + docker/docker-compose.yml | 61 + docker/healthcheck.mjs | 81 + docs/.vitepress/config.mjs | 143 + docs/guide/adamant-messenger.md | 76 + docs/guide/architecture.md | 333 ++ docs/guide/comparison.md | 187 + docs/guide/configuration.md | 514 +++ docs/guide/contributing.md | 137 + docs/guide/docker.md | 347 ++ docs/guide/installation.md | 321 ++ docs/guide/quick-start.md | 377 ++ docs/guide/security.md | 358 ++ docs/guide/use-cases.md | 143 + docs/guide/what-is-it.md | 115 + docs/index.md | 86 + docs/operations/monitoring.md | 185 + docs/operations/persistence.md | 130 + docs/operations/troubleshooting.md | 181 + docs/operations/upgrades.md | 157 + docs/public/CNAME | 1 + docs/public/favicon.svg | 12 + docs/public/logo.svg | 13 + docs/reference/api.md | 307 ++ package-lock.json | 5643 ++++++++++++++++++-------- package.json | 39 +- scripts/check-docs-artifact.mjs | 107 + scripts/docker-smoke-test.sh | 287 ++ scripts/generate-api-reference.mjs | 201 + 39 files changed, 9854 insertions(+), 1957 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/publish-docker.yml create mode 100644 Dockerfile create mode 100644 docker/config.example.json5 create mode 100644 docker/docker-compose.yml create mode 100755 docker/healthcheck.mjs create mode 100644 docs/.vitepress/config.mjs create mode 100644 docs/guide/adamant-messenger.md create mode 100644 docs/guide/architecture.md create mode 100644 docs/guide/comparison.md create mode 100644 docs/guide/configuration.md create mode 100644 docs/guide/contributing.md create mode 100644 docs/guide/docker.md create mode 100644 docs/guide/installation.md create mode 100644 docs/guide/quick-start.md create mode 100644 docs/guide/security.md create mode 100644 docs/guide/use-cases.md create mode 100644 docs/guide/what-is-it.md create mode 100644 docs/index.md create mode 100644 docs/operations/monitoring.md create mode 100644 docs/operations/persistence.md create mode 100644 docs/operations/troubleshooting.md create mode 100644 docs/operations/upgrades.md create mode 100644 docs/public/CNAME create mode 100644 docs/public/favicon.svg create mode 100644 docs/public/logo.svg create mode 100644 docs/reference/api.md create mode 100755 scripts/check-docs-artifact.mjs create mode 100755 scripts/docker-smoke-test.sh create mode 100755 scripts/generate-api-reference.mjs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3970ee5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,39 @@ +# Keep deployment configuration, secrets, persistent state, and developer +# scratch space out of the build context. Everything the image needs is listed +# explicitly in the Dockerfile, so anything excluded here can never end up in a +# layer or in `docker history`. + +.git +.github +.husky +.vscode +.idea +.DS_Store + +node_modules +dist +dist-test +logs + +# Deployment configuration and secrets are mounted at runtime, never baked in. +# config.default.json5 is copied by the Dockerfile as a documented template. +config.json5 +config.*.json5 +!config.default.json5 + +# Persistent node state: blockstore, datastore, peer identity, registry. +.adm-ipfs +.adm-ipfs-test +docker/data + +# Reports and generated artifacts +npm-audit-report.json +semgrep-results.sarif +.playwright-cli + +# Documentation site, tests, and agent working directories +docs +test +.ai-ignored +.ai-tasks +.claude diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..d298636 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,91 @@ +name: Container + +# Builds the production image and runs the documented runtime checks on it. +# Nothing is pushed here: publication happens only from a GitHub Release, in +# .github/workflows/publish-docker.yml. + +on: + push: + branches: [dev] + paths: + - 'Dockerfile' + - '.dockerignore' + - 'docker/**' + - 'src/**' + - 'scripts/docker-smoke-test.sh' + - 'package.json' + - 'package-lock.json' + - 'tsconfig*.json' + - '.github/workflows/docker.yml' + pull_request: + branches: [dev] + paths: + - 'Dockerfile' + - '.dockerignore' + - 'docker/**' + - 'src/**' + - 'scripts/docker-smoke-test.sh' + - 'package.json' + - 'package-lock.json' + - 'tsconfig*.json' + - '.github/workflows/docker.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: container-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke-test: + name: Build and smoke-test (${{ matrix.platform }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Both published architectures are exercised. linux/amd64 is native on + # the runner; linux/arm64 runs under QEMU, which is slower but proves + # that the native dependencies resolve and the service starts. + platform: [linux/amd64, linux/arm64] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + if: matrix.platform != 'linux/amd64' + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + platforms: ${{ matrix.platform }} + push: false + load: true + tags: ipfs-node:ci + build-args: | + IMAGE_VERSION=0.0.0-ci + IMAGE_REVISION=${{ github.sha }} + cache-from: type=gha,scope=${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=${{ matrix.platform }} + + - name: Verify OCI metadata + run: | + docker image inspect ipfs-node:ci \ + --format '{{ json .Config.Labels }}' | tee labels.json + for label in \ + org.opencontainers.image.source \ + org.opencontainers.image.documentation \ + org.opencontainers.image.licenses \ + org.opencontainers.image.revision \ + org.opencontainers.image.version; do + grep -q "\"$label\"" labels.json || { echo "missing label: $label"; exit 1; } + done + + - name: Run the documented runtime checks + run: ./scripts/docker-smoke-test.sh ipfs-node:ci ${{ matrix.platform }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..910fad6 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,93 @@ +name: Docs + +# Builds the VitePress documentation site and deploys it to GitHub Pages at +# https://ipfs-node.docs.adamant.im. +# +# `dev` is the default branch of this repository, so the branch trigger points +# there. Repository settings must have Pages source set to "GitHub Actions" and +# the custom domain set to ipfs-node.docs.adamant.im with HTTPS enforced. + +on: + push: + branches: [dev] + paths: + - 'docs/**' + - 'scripts/generate-api-reference.mjs' + - 'scripts/check-docs-artifact.mjs' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/docs.yml' + pull_request: + branches: [dev] + paths: + - 'docs/**' + - 'scripts/generate-api-reference.mjs' + - 'scripts/check-docs-artifact.mjs' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/docs.yml' + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + +# One deployment at a time, and never cancel one that is already publishing. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build site + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + + # The site build touches no runtime dependency, so the node-datachannel + # prebuild download that `npm ci` normally performs is skipped here. Every + # other workflow installs with scripts enabled. + - name: Install dependencies + run: npm ci --ignore-scripts + + # Generates the API reference from docs/openapi.yaml, builds the site, + # and fails on a broken link, a missing CNAME, missing generated API + # content, or a leaked build path. + - name: Build documentation + run: npm run docs:build + + - name: Configure Pages + if: github.event_name != 'pull_request' + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + name: Deploy to GitHub Pages + # A pull request validates the build only; nothing is published from a fork + # or from an unmerged branch. + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml new file mode 100644 index 0000000..ca85704 --- /dev/null +++ b/.github/workflows/publish-docker.yml @@ -0,0 +1,143 @@ +name: Publish container + +# Publishes ghcr.io/adamant-im/ipfs-node when a GitHub Release is published. +# Nothing is published from a branch push: a release is the only intentional +# distribution event. +# +# Tagging policy: +# +# - every release publishes the immutable version tag, e.g. `0.2.0` +# - a stable release also moves `latest` +# - a prerelease publishes its version tag only, so `latest` never points at one + +on: + release: + types: [published] + +permissions: + contents: read + +env: + IMAGE: ghcr.io/adamant-im/ipfs-node + +jobs: + publish: + name: Build and publish + runs-on: ubuntu-latest + permissions: + contents: read + # Required to push the image, its SBOM, and its provenance attestation to + # GHCR. No other scope is needed. + packages: write + outputs: + version: ${{ steps.release.outputs.version }} + digest: ${{ steps.build.outputs.digest }} + steps: + - name: Checkout the release tag + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + fetch-depth: 0 + + # `dev` is this repository's default and stable release branch. A tag made + # on a feature branch must not become a published image. + - name: Verify the release tag is on dev + run: | + git fetch --no-tags origin dev + git merge-base --is-ancestor HEAD origin/dev \ + || { echo "the release tag is not an ancestor of origin/dev"; exit 1; } + + - name: Resolve the release version + id: release + env: + TAG_NAME: ${{ github.event.release.tag_name }} + run: | + VERSION="${TAG_NAME#v}" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + PACKAGE_VERSION="$(node -p "require('./package.json').version")" + if [ "$VERSION" != "$PACKAGE_VERSION" ]; then + echo "release tag ${TAG_NAME} does not match package.json version ${PACKAGE_VERSION}" + exit 1 + fi + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute image tags + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=semver,pattern={{version}} + type=raw,value=latest,enable=${{ github.event.release.prerelease == false }} + labels: | + org.opencontainers.image.version=${{ steps.release.outputs.version }} + + - name: Build and push + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + IMAGE_VERSION=${{ steps.release.outputs.version }} + IMAGE_REVISION=${{ github.sha }} + IMAGE_CREATED=${{ github.event.release.published_at }} + # Pushed to GHCR as OCI attestations alongside the image; no extra + # workflow permission beyond `packages: write` is required. + sbom: true + provenance: mode=max + + verify: + name: Verify published image (${{ matrix.platform }}) + needs: publish + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + strategy: + fail-fast: false + matrix: + platform: [linux/amd64, linux/arm64] + steps: + - name: Checkout the release tag + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Set up QEMU + if: matrix.platform != 'linux/amd64' + uses: docker/setup-qemu-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # The immutable version tag, not `latest`: the smoke test must describe + # the artifact this release produced. + - name: Pull the published architecture + run: | + docker pull --platform ${{ matrix.platform }} \ + "${IMAGE}:${{ needs.publish.outputs.version }}" + + - name: Run the documented runtime checks + run: | + ./scripts/docker-smoke-test.sh \ + "${IMAGE}:${{ needs.publish.outputs.version }}" ${{ matrix.platform }} diff --git a/.gitignore b/.gitignore index e0f4670..05bfc96 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,12 @@ npm-audit-report.json .ai-ignored/ .ai-tasks/ .claude/ + +# documentation site build output and generated pages +docs/.vitepress/cache/ +docs/.vitepress/dist/ +docs/reference/endpoints.md +docs/public/openapi.yaml + +# docker evaluation stack +docker/data/ diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc index 3b8e3f9..b811f25 100644 --- a/.markdownlint.jsonc +++ b/.markdownlint.jsonc @@ -6,5 +6,6 @@ "MD060": false, "MD040": false, "MD036": false, + "MD025": false, "MD034": false } diff --git a/.prettierignore b/.prettierignore index e880d95..9632d6c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,9 +5,18 @@ package-lock.json npm-audit-report.json # Semgrep rule definitions are formatted for readability of the pattern blocks + .semgrep # Scratch worktrees and agent working directories + .claude .ai-ignored .ai-tasks + +# Documentation site build output and generated pages + +docs/.vitepress/cache +docs/.vitepress/dist +docs/reference/endpoints.md +docs/public/openapi.yaml diff --git a/AGENTS.md b/AGENTS.md index f1b4b31..8f8f898 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,42 @@ Optimize agent work for: Prefer the smallest safe change that fully addresses the task. Avoid unrelated rewrites and speculative abstractions. +## Product Positioning + +ADAMANT IPFS Node is a universal, self-hostable open-source product: an IPFS storage node for +application file delivery, with bounded disk usage, deterministic replication, repair, health +checkpoints, and a REST API. ADAMANT Messenger is a real adopter and the reference deployment, +not the product's purpose. + +- Write user-facing copy and examples so a non-ADAMANT application can use them unchanged +- Present ADAMANT Messenger as one adopter and one worked example, never as the intended consumer +- Keep ADAMANT-specific values out of defaults that a new deployment would copy blindly; the peer + list in `config.default.json5` points at the ADAMANT production mesh, so any example a stranger + is expected to run must use an empty or placeholder peer set +- Describe the service as a standalone Node.js and Helia application, never as a Kubo wrapper or a + Kubo-compatible API + +### Claims + +Derive every capability, decentralization, privacy, resource, and scale claim from current code, +current tests, or a reproducible measurement recorded in the repository. + +- Do not publish CPU, memory, disk, throughput, latency, concurrency, or uptime figures that no + recorded measurement supports; "simple" and "low-resource" are claims, not adjectives +- Do not describe planned or in-progress work as an available feature; name the open issue instead +- State the boundary next to the benefit: a controlled peer topology avoids the public DHT and + public gateways and reduces public exposure of content-routing metadata, but does not by itself + make a deployment private, anonymous, trustless, or censorship-proof +- Availability still depends on independent nodes, independent operators, replication settings, and + deployer choices; do not turn a durability policy into an availability guarantee + +### Public surface synchronization + +`README.md`, the documentation site under `docs/`, `docs/openapi.yaml`, the container files and +examples, `package.json` metadata, and the GitHub repository presentation describe one product. +When public behavior changes, update all of them in the same change, or state explicitly which one +was deliberately left alone and why. + ## Language Policy - Developers may communicate with AI in any language @@ -77,6 +113,18 @@ Preserve backward compatibility unless the task explicitly approves a breaking c - Never claim a check passed unless it was actually executed - Report exact commands, results, and any checks that were not run +Documentation site and container work carries further expectations: + +- Build the documentation site from the lockfile before claiming it works, and treat a broken + internal link, a missing generated API reference, or a missing custom-domain file as a failure + rather than a warning +- Exercise a container image at runtime before claiming it works: it must start from mounted + configuration, run unprivileged, reach the documented health state, serve an upload and a + download, keep its persistent state and peer identity across replacement, and shut down cleanly + on `SIGTERM` +- Keep validation instructions in terms of repository scripts, not of a specific base image, tag, + runner, or registry path, so they do not go stale + ## Issue, Label, and PR Conventions Follow organization-wide governance, templates, title conventions, and label casing. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..77a4577 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,99 @@ +# Production image for ADAMANT IPFS Node. +# +# No `# syntax=` directive on purpose: this file uses no feature beyond the +# built-in BuildKit frontend, and resolving an external frontend image makes +# every build depend on a registry round trip. +# +# The base is Debian bookworm. Helia pulls @libp2p/webrtc, whose +# node-datachannel native module downloads a prebuilt binary during install; +# glibc is the combination this image is built and smoke-tested on. Alpine is +# not offered — musl prebuilds exist upstream, but nothing here has validated +# that runtime, and an untested base is not a supported one. +# +# Persistent state lives under HOME. `storeFolder` in the configuration file is +# resolved from the home directory of the process user (src/store.ts), so the +# image pins HOME to /data and a single /data volume holds the blockstore, the +# datastore, the libp2p peer identity, the pin set, the lifecycle registry, the +# repair cursor, and the health checkpoint. See Adamant-im/ipfs-node#30 for the +# planned absolute `dataDir` option. + +ARG NODE_IMAGE=node:24.13.0-bookworm-slim + +FROM ${NODE_IMAGE} AS build + +WORKDIR /app + +# Install scripts must run: @libp2p/webrtc pulls node-datachannel, whose binary +# is downloaded here. A tree installed with --ignore-scripts fails at startup. +COPY package.json package-lock.json ./ +RUN npm ci + +COPY tsconfig.json tsconfig.build.json ./ +COPY src ./src +RUN npm run build \ + && npm prune --omit=dev --omit=peer \ + && npm cache clean --force + +FROM ${NODE_IMAGE} AS runtime + +# Populated by the release workflow from the Git tag and commit. +ARG IMAGE_VERSION=0.0.0-dev +ARG IMAGE_REVISION=unknown +ARG IMAGE_CREATED=1970-01-01T00:00:00Z + +LABEL org.opencontainers.image.title="ADAMANT IPFS Node" \ + org.opencontainers.image.description="Self-hosted IPFS storage node for application file delivery, with bounded disk usage, deterministic replication, repair, health checkpoints, and a REST API" \ + org.opencontainers.image.source="https://github.com/Adamant-im/ipfs-node" \ + org.opencontainers.image.url="https://github.com/Adamant-im/ipfs-node" \ + org.opencontainers.image.documentation="https://ipfs-node.docs.adamant.im" \ + org.opencontainers.image.licenses="GPL-3.0" \ + org.opencontainers.image.vendor="ADAMANT Developer Community" \ + org.opencontainers.image.authors="ADAMANT Developer Community " \ + org.opencontainers.image.version="${IMAGE_VERSION}" \ + org.opencontainers.image.revision="${IMAGE_REVISION}" \ + org.opencontainers.image.created="${IMAGE_CREATED}" + +ENV NODE_ENV=production \ + HOME=/data + +WORKDIR /app + +# The image ships no configuration. `config.json5` is mounted at runtime so that +# peer lists, the administrative key, and every other deployment value stay out +# of the image layers. config.default.json5 is copied as a documented template +# only; it is never selected automatically, because its peer list points at the +# ADAMANT production mesh. +COPY --chown=root:root package.json ./package.json +COPY --chown=root:root config.default.json5 ./config.default.json5 +COPY --chown=root:root docker/healthcheck.mjs ./docker/healthcheck.mjs +COPY --from=build --chown=root:root /app/node_modules ./node_modules +COPY --from=build --chown=root:root /app/dist ./dist + +# `node` (uid 1000) already exists in the base image. /data is its home and the +# only path the process writes to, so the rest of the filesystem stays read-only +# for the runtime user. +RUN mkdir -p /data && chown -R node:node /data + +USER node + +VOLUME ["/data"] + +# 4000 serves the HTTP API, 4001 is the libp2p listener from +# `peerDiscovery.listen`. Both are published by the operator; TLS termination +# and any reverse proxy stay outside the image. +EXPOSE 4000 4001 + +# Liveness is the process being alive; readiness is the documented health state. +# GET /api/node/health always answers 200 and reports the state in JSON, so the +# check parses the body instead of trusting the status code. Startup +# reconciliation, the first disk scan, and the first checkpoint have to complete +# before a node reports "ready", which is what the start period covers. +HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \ + CMD ["node", "/app/docker/healthcheck.mjs"] + +# Node handles SIGTERM itself (src/index.ts), so it can run as PID 1 without an +# init shim. Shutdown closes the HTTP server, stops the background jobs, and +# stops Helia, with a 15 s internal deadline; give the container at least 20 s. +STOPSIGNAL SIGTERM + +CMD ["node", "dist/index.js"] diff --git a/README.md b/README.md index de0507a..3f6d61b 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,102 @@ -# ADAMANT IPFS node - -This service embeds a Helia/libp2p node and exposes the file-transfer API used by ADAMANT Messenger. It stores uploaded files in an on-disk IPFS blockstore, pins them, retrieves content by CID, and maintains connections to configured peers. - -The application is not a Kubo wrapper. It is a Node.js service with an Express REST API around an in-process Helia node. - -The stable client and lifecycle contract is also available as [OpenAPI 3.1](docs/openapi.yaml). +# ADAMANT IPFS Node + +Self-hosted IPFS storage node for application file delivery, with bounded disk usage, deterministic +replication, repair, health checkpoints, and a REST API. + +It is a standalone Node.js service: an in-process [Helia](https://helia.io) node and a +[libp2p](https://libp2p.io) host behind an Express REST API. It is not a wrapper around Kubo, it +does not expose a Kubo-compatible API, and it is not an IPFS gateway. A deployment is a set of nodes +that know each other by multiaddr and exchange blocks, replicas, and health attestations only with +each other. + +- Documentation: +- Source: +- Releases: +- Container package: +- API specification: [docs/openapi.yaml](docs/openapi.yaml) +- Storage and replication reference: [docs/storage-lifecycle.md](docs/storage-lifecycle.md) + +## Features + +- REST upload of multipart files and content-addressed download by CID +- Controlled peer mesh over TCP with Noise encryption and Yamux multiplexing; only `identify` and + `ping` are registered +- Bounded storage lifecycle: disk reserve, aggregate request limits, temporary uploads with a TTL, + and watermark-driven garbage collection +- Deterministic placement by rendezvous hashing over the configured peer set, with copy counts that + shrink as a file ages +- Cross-node replication and a resumable repair cycle over a libp2p protocol authenticated by the + handshake +- Network-aware health checkpoints with a monotonic height and an explicit membership epoch +- Per-endpoint rate limits and admission control on concurrent uploads and downloads +- Structured newline-delimited JSON logs with content identifiers, peer ids, and credentials scrubbed + +## Adopters + +[ADAMANT Messenger](https://adamant.im) runs this node in production for attachment delivery, and is +the reference deployment. It is one adopter, not the purpose of the project — see +[the adopter page](https://ipfs-node.docs.adamant.im/guide/adamant-messenger). + +## Good fit + +- Application-owned, content-addressed file delivery across a small or fixed set of mutually trusted + storage nodes +- Privacy-conscious deployments that prefer a controlled peer mesh over public IPFS discovery and + gateways +- Teams that need replication, repair, bounded local storage, health reporting, and a REST API + without operating separate Kubo and IPFS Cluster control planes +- Messenger and media-attachment storage +- Self-hosted services that value predictable configuration and operational limits over the breadth + of a full public IPFS implementation + +Details: + +## Choose another solution when + +- You need a general-purpose public IPFS node, public DHT participation, IPNS, a public gateway, or + Kubo API compatibility +- The cluster is large, highly dynamic, or made of mutually untrusted operators +- End users must own their files and delete them with an authenticated request +- You need built-in egress accounting and monthly quotas +- Content must be reachable from the public IPFS network +- You need S3-compatible APIs, mature cloud IAM, lifecycle tiers, or managed global delivery + +Comparison with Kubo, Kubo plus IPFS Cluster, a custom Helia application, managed pinning providers, +and object storage: + +## Boundaries and limitations + +Read this before choosing an architecture around the node. + +- **No public IPFS participation.** There is no DHT, no IPNS, no public gateway, no mDNS, no circuit + relay, no NAT traversal, and no public bootstrap list. Content uploaded here is announced nowhere, + and a CID that exists only on the public network cannot be fetched here +- **No Kubo API.** Kubo clients and tooling do not work against this service +- **No end-user identity.** Upload and download are unauthenticated by design; the only credential + is one administrative key. Uploader-signed deletion is open work + ([#27](https://github.com/Adamant-im/ipfs-node/issues/27)) +- **Static peer lists.** Every node must be listed on every other node, and changing that list + resets the health checkpoint epoch. Peer discovery is open work + ([#28](https://github.com/Adamant-im/ipfs-node/issues/28)) +- **No traffic accounting.** Rate and concurrency limits exist; per-client byte accounting and + monthly volume limits do not ([#29](https://github.com/Adamant-im/ipfs-node/issues/29)) +- **No absolute data directory.** The store is always `$HOME/` + ([#30](https://github.com/Adamant-im/ipfs-node/issues/30)) +- **Public-network interoperability is research**, not a feature + ([#31](https://github.com/Adamant-im/ipfs-node/issues/31)) + +A controlled peer topology avoids the public DHT and public gateways and reduces public exposure of +content-routing metadata, but it does not by itself make a deployment private, anonymous, trustless, +or censorship-proof. Availability still depends on independent nodes, independent operators, +replication settings, and deployer choices. ## Requirements -- Node.js 24 LTS. The repository ships an `.nvmrc`, so `nvm use` selects it +- Node.js 24. The repository ships an `.nvmrc`, so `nvm use` selects it - A TLS-terminating reverse proxy for every public deployment - A unique administrative API key for operator endpoints -## Install and run +## Quick start from source ```bash git clone https://github.com/Adamant-im/ipfs-node.git @@ -23,10 +107,13 @@ npm run build node dist/index.js ``` -`npm ci` must run install scripts. Helia depends on `@libp2p/webrtc`, whose -`node-datachannel` native module downloads a prebuilt binary from GitHub -releases; installing with `--ignore-scripts` produces a tree that fails at -startup. See [Dependency notes](#dependency-notes). +`npm ci` must run install scripts. Helia depends on `@libp2p/webrtc`, whose `node-datachannel` +native module downloads a prebuilt binary from GitHub releases; installing with `--ignore-scripts` +produces a tree that fails at startup. See [Dependency notes](#dependency-notes). + +Copy `config.default.json5` to `config.json5` before starting. **Its peer list points at the ADAMANT +production mesh**, so a deployment that is not joining ADAMANT must replace `nodes`, +`peerDiscovery.bootstrap`, and `cors.allowedOrigins`. Empty peer lists run a standalone node. The process can also be managed with PM2: @@ -35,96 +122,48 @@ npm install --global pm2 pm2 start dist/index.js --name="IPFS node" ``` -## Configuration +Full guide: + +## Quick start with Docker -Copy `config.default.json5` to `config.json5` and replace all deployment-specific values. - -The config file is selected by the `IPFS_NODE_CONFIG` environment variable, or by the first CLI argument when the variable is not set. Both select `config..json5`; with neither, `config.json5` is used. `node dist/index.js test1` and `IPFS_NODE_CONFIG=test1 node dist/index.js` are equivalent. - -The whole configuration is validated at startup: a missing file, invalid JSON5, or a field with the wrong type aborts the process with a message naming the offending field. The security-relevant fields are shown below: - -```jsonc -{ - "serverPort": 4000, - "uploadLimitSizeBytes": 268435456, - "maxFileCount": 10, - "findFileTimeout": 20000, - "downloadIdleTimeout": 20000, - "downloadMinBytesPerSecond": 32768, - "downloadMaxDurationMs": 14400000, - "cors": { - "allowedOrigins": ["https://adm.im", "https://*.adamant.im", "http://localhost:8080"] - }, - "trustProxy": false, - "rateLimits": { - "upload": { "windowMs": 900000, "limit": 10 }, - "pin": { "windowMs": 900000, "limit": 10 }, - "read": { "windowMs": 60000, "limit": 100 } - }, - "adminApiKey": "", - "enableDebugApi": false, - "prettyLogs": false, - "peeringSchedule": "*/30 * * * * *", - "storage": { - "maxRequestSizeBytes": 536870912, - "maxConcurrentUploads": 32, - "maxConcurrentDownloads": 64, - "maxConcurrentDownloadsPerClient": 8, - "diskReserveBytes": 5368709120, - "confirmationRequired": false, - "temporaryTtlMs": 86400000, - "gc": { - "enabled": true, - "schedule": "0 */15 * * * *", - "highWatermarkBytes": 53687091200, - "lowWatermarkBytes": 42949672960 - } - }, - "replication": { - "enabled": true, - "placement": [ - { "minAgeMs": 0, "copies": 4 }, - { "minAgeMs": 15552000000, "copies": 3 }, - { "minAgeMs": 31536000000, "copies": 2 } - ], - "ackQuorum": 1, - "requireQuorumOnUpload": false, - "requestTimeoutMs": 30000, - "repairEnabled": true, - "repairSchedule": "0 */30 * * * *", - "repairBatchDelayMs": 1000, - "repairProbeConcurrency": 4 - }, - "health": { - "checkpointIntervalMs": 60000, - "maxCheckpointAgeMs": 180000, - "storageMaxAgeMs": 120000, - "repairMaxAgeMs": 3600000, - "clockSkewToleranceMs": 10000, - "peerAttestationTimeoutMs": 5000, - "requiredPeerCount": 1 - } -} +```bash +docker volume create ipfs-node-data + +docker run -d \ + --name ipfs-node \ + --restart unless-stopped \ + --stop-timeout 20 \ + -v ipfs-node-data:/data \ + -v "$PWD/config.json5:/app/config.json5:ro" \ + -p 127.0.0.1:4000:4000 \ + -p 4001:4001 \ + ghcr.io/adamant-im/ipfs-node: ``` -`findFileTimeout` bounds discovery, while `downloadIdleTimeout` (defaulting to -`findFileTimeout`) bounds a stalled transfer. `downloadMinBytesPerSecond` -(default `32768`) derives a complete-transfer deadline from the file size, while -`downloadMaxDurationMs` (default four hours) provides an absolute ceiling. -`storage.maxConcurrentDownloads` (default `64`) bounds sockets and UnixFS -iterators globally, and `storage.maxConcurrentDownloadsPerClient` (default `8`) -keeps one address from holding every slot. Discovery, stat, and streaming have -separate bounds, so a failed request may spend more than one `findFileTimeout` -before returning `408`. +Take `` from the [releases page](https://github.com/Adamant-im/ipfs-node/releases): every +release publishes an immutable version tag, a stable release also moves `latest`, and a prerelease +publishes its version tag only. + +The image ships no configuration; mount one at `/app/config.json5`. `HOME` is `/data`, so a single +`/data` volume holds the blockstore, datastore, peer identity, pin set, lifecycle registry, repair +cursor, and health checkpoint. `docker/config.example.json5` is a standalone starting point and +`docker/docker-compose.yml` is a minimal evaluation stack. + +Full guide: -Startup refuses a `downloadMaxDurationMs` below the time `uploadLimitSizeBytes` -needs at `downloadMinBytesPerSecond`: the ceiling wins over the size-aware -deadline, so a lower value would cut the largest permitted files off mid-stream -and report them as retrieval timeouts. +## Configuration + +Copy `config.default.json5` to `config.json5` and replace every deployment-specific value. -`storage`, `replication`, and `health` are optional; every option falls back to a documented default, so an existing configuration file keeps working. Storage and replication are described in [docs/storage-lifecycle.md](docs/storage-lifecycle.md), together with the file states, the collection policy, and the recovery procedures. +The config file is selected by the `IPFS_NODE_CONFIG` environment variable, or by the first CLI +argument when the variable is not set. Both select `config..json5`; with neither, +`config.json5` is used. `node dist/index.js test1` and `IPFS_NODE_CONFIG=test1 node dist/index.js` +are equivalent. -Logs are newline-delimited JSON by default. Set `prettyLogs: true` only for an interactive development terminal; production collectors should keep the structured fields emitted by Pino and the health checkpoint. +The whole configuration is validated at startup: a missing file, invalid JSON5, or a field with the +wrong type aborts the process with a message naming the offending field. `storage`, `replication`, +and `health` are optional and every option falls back to a documented default, so an existing +configuration file keeps working. Generate the administrative secret before enabling operator endpoints: @@ -132,26 +171,13 @@ Generate the administrative secret before enabling operator endpoints: openssl rand -hex 32 ``` -Set the generated value as `adminApiKey`. A missing or empty key fails closed: administrative routes return `503 Service not configured`. Known placeholder values and secrets shorter than 32 characters prevent startup. - -### Configuration migration +A missing or empty `adminApiKey` fails closed: administrative routes return +`503 Service not configured`. Known placeholder values and secrets shorter than 32 characters +prevent startup. -- Remove `autoPeeringPeriod`; no scheduled auto-peering exists, and `GET /api/debug/autopeering` performs it on request -- Replace `cors.origin` or `cors.originRegexps` with `cors.allowedOrigins` -- Use exact origins such as `https://adm.im` or any-depth subdomain suffix wildcards such as `https://*.adamant.im` -- Set `adminApiKey` before using any administrative API -- Leave `trustProxy` as `false` for direct connections; configure exact proxy addresses, CIDR ranges, or a verified hop count behind a proxy -- Migrate operator scripts and dashboards from the former detailed `GET /api/node/info` response to authenticated `GET /api/node/details`; `/info` is now the public legacy PWA/iOS contract -- Set `enableDebugApi: true` only when the authenticated debug route is operationally required -- Tune the endpoint-specific `rateLimits` for the deployment perimeter -- Review `storage.diskReserveBytes` and `storage.maxRequestSizeBytes` for the deployment volume; the defaults suit a dedicated disk -- `storage.gc.enabled` is on by default and frees blocks only when the blockstore passes `highWatermarkBytes` or free space falls into `diskReserveBytes`; released files stay readable until then -- `replication.enabled` is on by default and needs no key and no extra address: copies travel on a libp2p protocol between the peers already listed in `nodes`. Turning it off leaves every file in a single copy -- Tune `replication.placement` if the deployment wants a different number of copies per file age -- Keep `health.requiredPeerCount` within the number of configured remote peers. A single-node or test deployment may set it to `0` -- `peeringSchedule` is optional and defaults to every thirty seconds; it redials the peers in `nodes` that are not connected, which `autoPeeringPeriod` never did - -Invalid CORS, proxy, API-key, or rate-limit configuration stops the process instead of silently weakening the boundary. +Every option, default, and cross-field rule, plus the migration notes for `autoPeeringPeriod`, +`cors.originRegexps`, and the `/api/node/info` to `/api/node/details` move: + ## HTTP access policy @@ -165,81 +191,106 @@ Invalid CORS, proxy, API-key, or rate-limit configuration stops the process inst | Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | | Authenticated user | None | The service has no end-user identity or session layer | -Administrative coverage includes pin operations, dial operations, peer-store data, connection data, status, peers, and topology-sensitive node information. CORS is a browser control and is never treated as authentication. - -`GET /api/helia/routing/findProviders/:cid` no longer exists. Provider lookup requires content routing, and this deployment intentionally runs no DHT — see [Network topology](#network-topology). +CORS is a browser control and is never treated as authentication. +`GET /api/helia/routing/findProviders/:cid` no longer exists: provider lookup requires content +routing, and this deployment intentionally runs none. ### Public upload decision -Upload remains public for compatibility with direct Messenger clients. The service has no safe channel for distributing an upload secret and does not implement a short-lived signing protocol. Public upload is constrained by per-client request limits, per-file size, per-request file count, filename sanitization, and the deployment proxy. - -This is an explicit compatibility decision, not an authorization guarantee. A deployment that requires signed upload authorization must enforce it at a trusted gateway until a client-compatible signing protocol is designed. Files of any content type are accepted, but downloads are served as `application/octet-stream` attachments with content sniffing disabled. - -The multipart contract accepts `files` parts only. Text fields are rejected with a controlled `400 Bad Request` response. - -An interrupted upload no longer leaves blocks behind. Each request owns a session that records the blocks it created; a rejected, aborted, or partially failed request removes exactly those, skipping blocks that already existed, blocks a concurrent upload is still writing, and blocks a pin protects. Whatever survives cleanup is unpinned and reclaimable by garbage collection. Strict-quorum uploads prepare rollback-capable remote pins and commit them concurrently only after the local decision is durable; success is returned only after permanent copies still satisfy the quorum. Blockstore growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the collection watermarks — see [docs/storage-lifecycle.md](docs/storage-lifecycle.md). - -### CORS +Upload remains public for compatibility with direct Messenger clients. The service has no safe +channel for distributing an upload secret and does not implement a short-lived signing protocol. +Public upload is constrained by per-client request limits, per-file size, per-request file count, +filename sanitization, and the deployment proxy. -`cors.allowedOrigins` accepts canonical HTTP(S) origins only. It does not accept paths, credentials, query strings, fragments, or unrestricted `*`. Wildcards match subdomains at any depth beneath the configured suffix. For example, `https://*.adamant.im` matches both `https://chat.adamant.im` and `https://nested.chat.adamant.im`, but not `https://adamant.im` or `https://adamant.im.example.org`. +This is an explicit compatibility decision, not an authorization guarantee. A deployment that +requires signed upload authorization must enforce it at a trusted gateway until a client-compatible +signing protocol is designed. Files of any content type are accepted, but downloads are served as +`application/octet-stream` attachments with content sniffing disabled. -Requests without an `Origin` header, such as server-to-server calls and `curl`, are not blocked by CORS. Unauthorized administrative requests are still rejected by API-key middleware. +The multipart contract accepts `files` parts only. Text fields are rejected with a controlled +`400 Bad Request`. -### Trusted proxy and rate limits +An interrupted upload leaves no blocks behind: each request owns a session that records the blocks +it created, and a rejected, aborted, or partially failed request removes exactly those. Blockstore +growth is bounded by the disk reserve, the aggregate request size, the concurrency limit, and the +collection watermarks. -Express uses the socket address as the client IP while `trustProxy` is `false`. Behind a reverse proxy, set `trustProxy` to the exact proxy address or CIDR when possible: - -```jsonc -trustProxy: ['127.0.0.1/8', '::1/128'] -``` - -A numeric hop count is supported only for a fixed topology in which every path to the application has exactly that number of trusted hops. The blanket value `true` is rejected because a client could spoof `X-Forwarded-For` when the last proxy does not overwrite it. - -Application limiters use in-memory counters per process. The TLS proxy must also enforce request rates, connection limits, header limits, and a body-size limit no larger than `uploadLimitSizeBytes` for multi-process or distributed deployments. - -Keeping `trustProxy` disabled behind a proxy makes every client share the proxy's IP identity and therefore the same application rate-limit bucket. The process logs a startup warning for this configuration. Do not enable a broader trust rule merely to suppress the warning; configure only the known proxy addresses or a verified fixed hop count. +Security, privacy, TLS, proxy, and exposure guidance: + ## Network topology -The node forms a private mesh with the peers listed in `nodes` and `peerDiscovery.bootstrap`. libp2p is configured explicitly and the Helia defaults are not merged in, so the node runs: +The node forms a controlled mesh with the peers listed in `nodes` and `peerDiscovery.bootstrap`. +libp2p is configured explicitly and the Helia defaults are not merged in, so the node runs: - TCP transport only, with Noise encryption and Yamux stream multiplexing - bootstrap peer discovery, restricted to the configured multiaddrs - the `identify` and `ping` services only -There is no DHT, no mDNS discovery, no circuit relay, no NAT traversal, and no HTTP gateway routing. Blocks are exchanged with known peers over bitswap, so block requests never leave the configured peer set and no CID is disclosed to a public gateway. +There is no DHT, no mDNS discovery, no circuit relay, no NAT traversal, and no HTTP gateway routing. +Blocks are exchanged with known peers over bitswap, so block requests never leave the configured +peer set and no CID is disclosed to a public gateway. + +The node is composed from `createHeliaLight`, `withLibp2pLight`, and `withBitswap` rather than +`createHelia`, because `createHelia` merges its default libp2p configuration into whatever is passed +in. Keeping it would silently add mDNS, the public IPFS bootstrap list, kad-DHT, AutoNAT, AutoTLS, +UPnP, circuit relay, and WebRTC/WebSocket transports. -The node is composed from `createHeliaLight`, `withLibp2pLight`, and `withBitswap` rather than `createHelia`, because `createHelia` merges its default libp2p configuration into whatever is passed in. Keeping it would silently add mDNS, the public IPFS bootstrap list, kad-DHT, AutoNAT, AutoTLS, UPnP, circuit relay, and WebRTC/WebSocket transports. +The libp2p private key is stored in the datastore under `/pkcs8/self`, so a node keeps its peer +identity across restarts as long as its store directory is preserved. -The libp2p private key is stored in the datastore under `/pkcs8/self`, so a node keeps its peer identity across restarts as long as its `storeFolder` is preserved. Upgrading from the previous Helia 4 stack preserves both the peer identity and the existing blockstore contents; no store migration is required. +Architecture, trust boundaries, and protocol detail: + ### TLS boundary -The Node.js process serves HTTP and does not terminate TLS. Bind it to a private interface or firewall it so clients can reach it only through a correctly configured HTTPS reverse proxy. The proxy must replace untrusted forwarding headers and forward requests to the configured `serverPort`. +The Node.js process serves HTTP and does not terminate TLS. Bind it to a private interface or +firewall it so clients can reach it only through a correctly configured HTTPS reverse proxy. The +proxy must replace untrusted forwarding headers and forward requests to the configured `serverPort`. Never expose the application port directly to the internet. ### Dependency audit policy -Run the production policy and static checks with: - ```bash npm run security:audit npm run security:semgrep ``` -The audit fails on every high or critical production advisory. There are no accepted exceptions: the Helia 4 Kademlia DHT advisory `GHSA-32mq-hpph-xfvr`, previously tolerated until the runtime upgrade, is resolved by the Helia 7 dependency set. - +The audit fails on every high or critical production advisory. There are no accepted exceptions. Use `npm run security:audit:raw` to inspect the unfiltered npm result. ## Dependency notes -`helia` depends on `@helia/libp2p`, which depends on `@libp2p/webrtc` even though WebRTC is never configured here. That pulls two things into the runtime tree: +`helia` depends on `@helia/libp2p`, which depends on `@libp2p/webrtc` even though WebRTC is never +configured here. That pulls two things into the tree: + +- `node-datachannel`, a native module whose prebuilt binary is downloaded from GitHub releases + during `npm install`. An installer that reaches the npm registry but not GitHub releases produces + a tree that fails at startup, so `--ignore-scripts` is suitable only for auditing and for building + the documentation site, not for running or testing +- `react-native-webrtc`, and through it `react-native` and its Metro bundler, in the development + tree only + +`npm run security:audit` scopes the audit with `--omit=dev --omit=peer` and reports no findings. +Running a bare `npm audit --omit=dev` additionally surfaces advisories against Metro and its +`image-size` dependency; Metro is a React Native build tool that this service never loads. Re-check +these when Helia is upgraded. + +## API + +The stable client, lifecycle, and node-health contract is +[OpenAPI 3.1](docs/openapi.yaml). Worked examples, status-code tables, and the generated endpoint +reference are on the documentation site: -- `node-datachannel`, a native module whose prebuilt binary is downloaded from GitHub releases during `npm install`. An installer that reaches the npm registry but not GitHub releases produces a tree that fails at startup, so `--ignore-scripts` is suitable only for auditing, not for running or testing -- `react-native-webrtc`, and through it `react-native` and its Metro bundler +- +- -`npm run security:audit` scopes the audit with `--omit=dev --omit=peer` and reports no findings. Running a bare `npm audit --omit=dev` additionally surfaces advisories against Metro and its `image-size` dependency; Metro is a React Native build tool that this service never loads. Re-check these when Helia is upgraded. +`GET /api/node/health` always returns `200`; consumers must inspect `state`, which is `starting`, +`ready`, `stale`, or `degraded`. `height` is a persisted, monotonic checkpoint that advances only +when every prerequisite passes and freezes on failure, and it is comparable between nodes only when +their `membership.version` matches. Health semantics and alerting: + ## Development @@ -247,16 +298,15 @@ Use `npm run security:audit:raw` to inspect the unfiltered npm result. npm run dev npm run lint npm run format +npm run typecheck npm test ``` -`npm test` compiles `src` and `test` to `dist-test` and then runs the unit and integration suites with Node's built-in test runner against `config.test.json5`, which has no bootstrap peers and listens on loopback with an OS-assigned port. - -The unit suites cover the HTTP security boundary, configuration validation, filename sanitization, disk measurement, and the file CIDs issued before the Helia migration. The integration suite starts two isolated nodes with temporary stores, transfers a file between them over bitswap, and verifies that a peer identity survives a restart. - -### TypeScript project layout +`npm test` compiles `src` and `test` to `dist-test` and then runs the unit and integration suites +with Node's built-in test runner against `config.test.json5`, which has no bootstrap peers and +listens on loopback with an OS-assigned port. -Three configurations share one set of compiler options: +Three TypeScript configurations share one set of compiler options: | File | Purpose | | --------------------- | --------------------------------------------------------------------- | @@ -264,212 +314,20 @@ Three configurations share one set of compiler options: | `tsconfig.build.json` | Builds `src` into `dist` for `npm run build` | | `tsconfig.test.json` | Builds `src` and `test` into `dist-test` for `npm test` | -Type-check everything without emitting: - -```bash -npm run typecheck -``` - -Pass no file arguments to `tsc`. Naming a file on the command line makes TypeScript ignore `tsconfig.json` entirely, so `outDir` is not applied — the `.js` file is written next to its source — and the project's `lib`, `module`, and `moduleResolution` settings are replaced by defaults, which reports module-resolution and missing-`@types/node` errors that the project itself does not have. - -## API usage +Pass no file arguments to `tsc`. Naming a file on the command line makes TypeScript ignore +`tsconfig.json` entirely, so `outDir` is not applied and the project's `lib`, `module`, and +`moduleResolution` settings are replaced by defaults. -### Upload files - -Send one or more `files` parts as multipart form data: +The documentation site is built from `docs/`: ```bash -curl --fail-with-body \ - --form 'files=@file.txt' \ - https://ipfs.example.org/api/file/upload -``` - -Example response: - -```json -{ - "filesNames": ["file.txt"], - "cids": ["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"], - "files": [ - { - "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", - "name": "file.txt", - "state": "confirmed", - "expiresAt": null - } - ], - "replication": { - "mode": "quorum", - "desiredCopies": 4, - "copies": 3, - "required": 1, - "acknowledged": 1, - "replicaCount": 0, - "cachedCount": 0, - "satisfied": true, - "networkTooSmall": true, - "failedAttemptCount": 0, - "attempts": [] - } -} +npm run docs:dev +npm run docs:build ``` -The `replication` object on this public route is counts and per-attempt -outcomes only: no node names, peer ids, or peer error text. - -Upload responses use the following stable status contract: - -| Status | Meaning | -| --------------------------- | -------------------------------------------------------------------------- | -| `200 OK` | Every file was stored and pinned | -| `400 Bad Request` | No file was sent, too many files, or a single file exceeded its size limit | -| `413 Payload Too Large` | The combined size of the files exceeded `storage.maxRequestSizeBytes` | -| `429 Too Many Requests` | The upload rate limit or the concurrent upload limit was exceeded | -| `503 Service Unavailable` | The replication quorum was required and could not be reached | -| `507 Insufficient Storage` | Storing the request would consume `storage.diskReserveBytes` | -| `500 Internal Server Error` | Storage or replica settlement failed; do not treat it as a clean rejection | - -### Check the state of a file - -```bash -curl --fail-with-body \ - https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm/status -``` - -Example response: - -```json -{ - "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", - "state": "confirmed", - "pinned": true, - "createdAt": 1720614998797, - "expiresAt": null, - "confirmedAt": 1720614998797, - "replication": { "acknowledged": 1, "required": 1, "heldLocally": true } -} -``` - -### Read the storage report and policy - -```bash -curl --fail-with-body https://ipfs.example.org/api/storage/metrics -curl --fail-with-body https://ipfs.example.org/api/storage/policy -``` - -`metrics` reports pinned, reclaimable, available, and reserved bytes together with the lifecycle counters, and the libp2p replication protocol this node speaks — nodes on different protocol versions cannot place copies on each other, so a mixed deployment is visible here. `policy` reports the limits and the durability mode a client should expect before uploading. - -### Download a file - -```bash -curl --fail-with-body \ - --output file.bin \ - https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm -``` - -Download responses use the following stable status contract: - -| Status | Meaning | -| --------------------------- | ---------------------------------------------------------------------- | -| `200 OK` | The response contains the requested file as an attachment | -| `400 Bad Request` | The CID is invalid | -| `408 Request Timeout` | The file could not be found or retrieved before the configured timeout | -| `429 Too Many Requests` | The read rate limit was exceeded | -| `500 Internal Server Error` | An unexpected internal failure occurred before streaming started | - -Discovery, idle transfer time, and the size-aware complete transfer all have bounded timeouts and remain `408 Request Timeout` for compatibility with existing clients. Client disconnect cancels the underlying retrieval. Range headers are ignored and the server returns the complete `200` representation with `Accept-Ranges: none`, preserving current PWA/iOS behavior. Successful responses include an `ETag` and private one-hour caching with revalidation; a matching `If-None-Match` returns `304` after availability has been checked. If an error occurs after response bytes have started, the server terminates the incomplete response because an HTTP status and JSON error body can no longer be sent safely. - -### Check public health - -```bash -curl --fail-with-body https://ipfs.example.org/api/node/health -``` - -Example response: - -```json -{ - "version": "0.1.0", - "uptimeMs": 123456, - "state": "ready", - "height": 1720614960000, - "timestamp": 1720614998797, - "evaluatedAt": 1720614998700, - "checkpoint": { - "intervalMs": 60000, - "observedAt": 1720614998700, - "ageMs": 97, - "maxAgeMs": 180000 - }, - "membership": { - "version": "d0f1...", - "requiredPeers": 1, - "attestedPeers": 2 - }, - "startup": { "complete": true, "healthy": true }, - "storage": { - "measuredAt": 1720614980000, - "measurementAgeMs": 18797, - "reserveHealthy": true - }, - "replication": { - "repairRequired": true, - "lastCompleteAt": 1720614900000, - "ageMs": 98797, - "backlog": 0 - }, - "checks": { - "checkpointFresh": true, - "clockConsistent": true, - "helia": true, - "startupReconciliation": true, - "storageFresh": true, - "storageReserve": true, - "repairFresh": true, - "peerAttestations": true - } -} -``` - -The endpoint always returns `200`; consumers must inspect `state`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful repair cycle with no known backlog, and the configured peer attestations all pass. It freezes on failure. Observation timestamps and ages let clients reject an absolutely stale cluster even when every node reports the same height. `starting`, `degraded`, and `stale` distinguish warm-up, a current failed prerequisite, and an expired last checkpoint. - -State changes are deliberately asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` between checkpoints. Recovery is never decided on a read: returning to `ready` requires a successful checkpoint, so expect up to `health.checkpointIntervalMs` of lag after the underlying fault clears. A clock that moves behind the checkpoint this node already recorded clears `clockConsistent` and stops advancement until it catches up, rather than persisting a round that starts after it finished. `checkpointFresh`, `storageFresh`, and `repairFresh` follow the same rule; every other entry in `checks`, along with `membership` and `startup`, describes `evaluatedAt` — the last checkpoint attempt, which may be a failed one — and not the moment of the request. `checkpoint.observedAt` dates the last attempt that _succeeded_, so the two differ whenever the most recent attempt failed. Peers may attest an adjacent round across a boundary, so two healthy nodes can briefly report heights one `checkpointIntervalMs` apart. - -`membership.version` identifies the configured peer-set epoch. Changing the node list resets the persisted checkpoint for that node: `height` remains `0` until the first valid checkpoint under the new membership. Clients must compare heights only when membership versions match and treat a version change as a new epoch. - -During a staged deployment, older peers do not implement the health protocol. Set `health.requiredPeerCount` to `0` on the transitioning fleet, then raise it after every required peer is upgraded; otherwise prolonged `degraded` health is the expected fail-safe result. While it is `0`, `checks.peerAttestations` is always `true` and the checkpoint proves only this node's own prerequisites: network coverage is not validated until the threshold is raised, so treat the transition window as unverified for routing decisions that depend on it. - -Repair runs in bounded 50-record passes. The same selected batch feeds both local-holder repair and released-record rescue. A scheduled run starts a full cycle immediately and continues its remaining passes after `replication.repairBatchDelayMs`; this delay and `replication.repairProbeConcurrency` control peer load, while `repairSchedule` controls when a new completed cycle is refreshed. Set `health.repairMaxAgeMs` longer than the largest expected full-cycle duration plus the schedule interval. An incomplete or backlogged cycle intentionally keeps health degraded. - -### Get legacy client node information - -```bash -curl --fail-with-body https://ipfs.example.org/api/node/info -``` - -This public compatibility route retains `version`, `timestamp`, `heliaStatus`, `blockstoreSizeMb`, `datastoreSizeMb`, and `availableSizeInMb` for the current PWA and iOS application. It does not expose peer identity or topology. - -### Get administrative node information - -```bash -curl --fail-with-body \ - --header 'x-api-key: your-generated-key' \ - https://ipfs.example.org/api/node/details -``` - -The authenticated response adds peer addresses, byte-accurate storage figures, checkpoint detail, and bounded HTTP counters without paths, CIDs, IP addresses, or user-controlled metric labels. - -### Run garbage collection - -Add `?dryRun=true` to report the exact CIDs that would be released and retained without deleting anything. This is the supported way to review a deletion policy before enabling the scheduled collector. - -```bash -curl --fail-with-body --request POST \ - --header 'x-api-key: your-generated-key' \ - 'https://ipfs.example.org/api/storage/gc?dryRun=true' -``` - -`POST /api/storage/repair` places copies of any confirmed file whose designated holders have not all acknowledged. A collection pass also hands over local copies of files that belong on other nodes, once those nodes confirm they hold them; the report lists them under `demoted`. +`docs/reference/endpoints.md` and `docs/public/openapi.yaml` are generated from `docs/openapi.yaml` +and must not be edited by hand. The build fails on a broken internal link, a missing generated API +reference, or a missing custom-domain file. ## Validation @@ -478,8 +336,49 @@ npm ci npm run build npm run lint npm run format +npm run typecheck npm test +npm run docs:build npm run security:audit npm run security:semgrep git diff --check ``` + +To exercise the container: + +```bash +docker build -t ipfs-node:local . +./scripts/docker-smoke-test.sh ipfs-node:local +``` + +## Documentation + +- [What it is](https://ipfs-node.docs.adamant.im/guide/what-is-it) +- [Use cases](https://ipfs-node.docs.adamant.im/guide/use-cases) and + [Comparison](https://ipfs-node.docs.adamant.im/guide/comparison) +- [Architecture](https://ipfs-node.docs.adamant.im/guide/architecture) +- [Quick start](https://ipfs-node.docs.adamant.im/guide/quick-start), + [Docker](https://ipfs-node.docs.adamant.im/guide/docker), + [Installation](https://ipfs-node.docs.adamant.im/guide/installation) +- [Configuration](https://ipfs-node.docs.adamant.im/guide/configuration) and + [Security and privacy](https://ipfs-node.docs.adamant.im/guide/security) +- [API overview](https://ipfs-node.docs.adamant.im/reference/api) and + [Endpoint reference](https://ipfs-node.docs.adamant.im/reference/endpoints) +- Operations: + [monitoring](https://ipfs-node.docs.adamant.im/operations/monitoring), + [persistence](https://ipfs-node.docs.adamant.im/operations/persistence), + [upgrades](https://ipfs-node.docs.adamant.im/operations/upgrades), + [troubleshooting](https://ipfs-node.docs.adamant.im/operations/troubleshooting) +- In this repository: [docs/openapi.yaml](docs/openapi.yaml), + [docs/storage-lifecycle.md](docs/storage-lifecycle.md), [AGENTS.md](AGENTS.md) + +## Contributing + +Issues and pull requests are welcome. Conventions, checks, and the documentation workflow are in +[AGENTS.md](AGENTS.md) and on +[the contributing page](https://ipfs-node.docs.adamant.im/guide/contributing). Report suspected +vulnerabilities privately through GitHub security advisories rather than in a public issue. + +## License + +[GPL-3.0](LICENSE). Copyright (c) ADAMANT Foundation and contributors. diff --git a/docker/config.example.json5 b/docker/config.example.json5 new file mode 100644 index 0000000..b0b491c --- /dev/null +++ b/docker/config.example.json5 @@ -0,0 +1,114 @@ +// Standalone evaluation configuration for the Docker Compose stack. +// +// cp config.example.json5 config.json5 +// +// It joins no existing network: `nodes` and `peerDiscovery.bootstrap` are +// empty, so this node peers with nobody and stores one local copy of everything +// it accepts. Add the peers of your own deployment before relying on +// replication, and read +// https://ipfs-node.docs.adamant.im/guide/configuration first. +{ + // Peers this node replicates with and keeps connections to. Every entry needs + // a multiaddr ending in /p2p/. Leave empty for a single node. + nodes: [], + + // Resolved from the home directory of the process user. The image pins + // HOME=/data, so this is /data/.adm-ipfs inside the container and lives on + // the `ipfs-node-data` volume. + storeFolder: '.adm-ipfs', + + logLevel: 'info', + // Production logs are newline-delimited JSON. Enable this only for a local + // terminal; a log collector wants the structured fields. + prettyLogs: false, + + peerDiscovery: { + // Dialled once at startup. Use the same peers as `nodes` for a real mesh. + bootstrap: [], + listen: ['/ip4/0.0.0.0/tcp/4001'] + }, + + serverPort: 4000, + diskUsageScanPeriod: '*/30 * * * * *', + // Redials the peers in `nodes` that are not connected. Bootstrap dials once + // and never again, so a mesh without this never recovers from a restart. + peeringSchedule: '*/30 * * * * *', + + uploadLimitSizeBytes: 268435456, // 256 MiB per file + maxFileCount: 10, + findFileTimeout: 20000, + downloadIdleTimeout: 20000, + downloadMinBytesPerSecond: 32768, + downloadMaxDurationMs: 14400000, // 4 h absolute ceiling for one response + + cors: { + // Exact origins and any-depth subdomain wildcards. Replace these with the + // origins of your own application; `*` is rejected. + allowedOrigins: ['http://localhost:8080'] + }, + + // Keep false when clients connect directly. Behind a reverse proxy, list the + // exact proxy addresses or CIDR ranges instead. + trustProxy: false, + + rateLimits: { + upload: { windowMs: 900000, limit: 10 }, + pin: { windowMs: 900000, limit: 10 }, + read: { windowMs: 60000, limit: 100 } + }, + + // Generate with: openssl rand -hex 32 + // Administrative routes answer 503 while this is empty, which is the safe + // default for an evaluation node. Mount a real secret before using them. + adminApiKey: '', + + // The debug API is not mounted unless this is true; it still needs the key. + enableDebugApi: false, + + storage: { + maxRequestSizeBytes: 536870912, // 512 MiB combined per request + maxConcurrentUploads: 32, + maxConcurrentDownloads: 64, + maxConcurrentDownloadsPerClient: 8, + // Free space uploads must never consume. Lower it only on a small + // evaluation volume, and never below what one accepted request can write. + diskReserveBytes: 1073741824, // 1 GiB + confirmationRequired: false, + temporaryTtlMs: 86400000, + gc: { + enabled: true, + schedule: '0 */15 * * * *', + highWatermarkBytes: 10737418240, // 10 GiB + lowWatermarkBytes: 8589934592 // 8 GiB + } + }, + + replication: { + // A single node has no peers to place copies on, so every file stays in one + // copy here. Add peers to `nodes` before this promises anything. + enabled: true, + placement: [ + { minAgeMs: 0, copies: 4 }, + { minAgeMs: 15552000000, copies: 3 }, + { minAgeMs: 31536000000, copies: 2 } + ], + ackQuorum: 1, + requireQuorumOnUpload: false, + requestTimeoutMs: 30000, + repairEnabled: true, + repairSchedule: '0 */30 * * * *', + repairBatchDelayMs: 1000, + repairProbeConcurrency: 4 + }, + + health: { + checkpointIntervalMs: 60000, + maxCheckpointAgeMs: 180000, + storageMaxAgeMs: 120000, + repairMaxAgeMs: 3600000, + clockSkewToleranceMs: 10000, + // Must not exceed the number of configured remote peers, so a single node + // has to keep this at 0. Raise it once every required peer is upgraded. + requiredPeerCount: 0 + } +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..c58808c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,61 @@ +# Minimal Compose stack for evaluating ADAMANT IPFS Node locally. +# +# cd docker +# cp config.example.json5 config.json5 # then edit it +# docker compose up --build +# +# Set IPFS_NODE_IMAGE to a published, pinned tag to run the maintained image +# instead of building from this checkout: +# +# IPFS_NODE_IMAGE=ghcr.io/adamant-im/ipfs-node:0.1.0 docker compose up +# +# This stack terminates no TLS. Never expose port 4000 to the internet directly; +# put an HTTPS reverse proxy in front of it. See +# https://ipfs-node.docs.adamant.im/guide/security + +services: + ipfs-node: + image: ${IPFS_NODE_IMAGE:-ipfs-node:local} + build: + context: .. + dockerfile: Dockerfile + restart: unless-stopped + + # Shutdown closes the HTTP server, stops the background jobs, and stops + # Helia, with a 15 s internal deadline. Anything shorter risks killing the + # process while it is still writing persistent state. + stop_grace_period: 20s + + ports: + # The API is bound to loopback: reach it through a reverse proxy. + - '127.0.0.1:4000:4000' + # The libp2p listener has to be reachable by the configured peers. + - '4001:4001' + + volumes: + # Every piece of persistent state lives under /data: blockstore, + # datastore, libp2p peer identity, pin set, lifecycle registry, repair + # cursor, and health checkpoint. Replacing the container while keeping + # this volume keeps the node's identity and content. + - ipfs-node-data:/data + # Deployment configuration is mounted, never baked into the image. + - ./config.json5:/app/config.json5:ro + + # Container health follows the documented readiness semantics rather than + # the HTTP status code, which is always 200. Overrides the image default + # only to show the knobs; see docker/healthcheck.mjs. + healthcheck: + test: ['CMD', 'node', '/app/docker/healthcheck.mjs'] + interval: 30s + timeout: 10s + start_period: 180s + retries: 3 + + # The image already runs as the unprivileged `node` user (uid 1000). These + # keep an escalation from being useful if one is ever found. + security_opt: + - no-new-privileges:true + read_only: false + +volumes: + ipfs-node-data: diff --git a/docker/healthcheck.mjs b/docker/healthcheck.mjs new file mode 100755 index 0000000..aca31d6 --- /dev/null +++ b/docker/healthcheck.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +/** + * Container health check for ADAMANT IPFS Node. + * + * `GET /api/node/health` always answers `200` and reports the node state in the + * JSON body, so a check that only looked at the status code would call a + * `degraded` or `stale` node healthy. This script evaluates the documented + * readiness semantics instead. + * + * Environment: + * + * - `IPFS_NODE_HEALTHCHECK_URL` — full URL to probe. + * Default `http://127.0.0.1:${IPFS_NODE_HEALTHCHECK_PORT}/api/node/health` + * - `IPFS_NODE_HEALTHCHECK_PORT` — API port when no URL is given. Default `4000` + * - `IPFS_NODE_HEALTHCHECK_STATES` — comma-separated states treated as healthy. + * Default `ready`. Set `ready,degraded` only when a deployment deliberately + * accepts a node whose prerequisites currently fail + * - `IPFS_NODE_HEALTHCHECK_TIMEOUT_MS` — request timeout. Default `5000` + * + * Exit codes follow the Docker contract: `0` healthy, `1` unhealthy. + */ + +const port = process.env.IPFS_NODE_HEALTHCHECK_PORT ?? '4000' +const url = process.env.IPFS_NODE_HEALTHCHECK_URL ?? `http://127.0.0.1:${port}/api/node/health` +const timeoutMs = Number.parseInt(process.env.IPFS_NODE_HEALTHCHECK_TIMEOUT_MS ?? '5000', 10) +const healthyStates = (process.env.IPFS_NODE_HEALTHCHECK_STATES ?? 'ready') + .split(',') + .map((state) => state.trim()) + .filter((state) => state !== '') + +/** Report the reason and exit unhealthy. */ +function unhealthy(reason) { + console.error(`unhealthy: ${reason}`) + process.exit(1) +} + +if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) { + unhealthy('IPFS_NODE_HEALTHCHECK_TIMEOUT_MS must be a positive integer') +} + +if (healthyStates.length === 0) { + unhealthy('IPFS_NODE_HEALTHCHECK_STATES must list at least one state') +} + +const controller = new AbortController() +const timer = setTimeout(() => controller.abort(), timeoutMs) + +let response +try { + response = await fetch(url, { signal: controller.signal }) +} catch (err) { + unhealthy(`${url} did not answer: ${err.message}`) +} finally { + clearTimeout(timer) +} + +if (!response.ok) { + unhealthy(`${url} answered HTTP ${response.status}`) +} + +let payload +try { + payload = await response.json() +} catch (err) { + unhealthy(`${url} returned a body that is not JSON: ${err.message}`) +} + +const state = payload?.state +if (typeof state !== 'string') { + unhealthy('the health response carries no state field') +} + +if (!healthyStates.includes(state)) { + const failed = Object.entries(payload.checks ?? {}) + .filter(([, passed]) => passed !== true) + .map(([name]) => name) + const detail = failed.length > 0 ? `; failing checks: ${failed.join(', ')}` : '' + unhealthy(`state is "${state}", expected one of ${healthyStates.join(', ')}${detail}`) +} + +console.log(`healthy: state "${state}", height ${payload.height ?? 'unknown'}`) diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs new file mode 100644 index 0000000..7e03da0 --- /dev/null +++ b/docs/.vitepress/config.mjs @@ -0,0 +1,143 @@ +import { defineConfig } from 'vitepress' + +/** Public site origin; used for the sitemap and the canonical link of each page. */ +const SITE_ORIGIN = 'https://ipfs-node.docs.adamant.im' + +/** Default branch of the source repository, used by the edit-on-GitHub links. */ +const REPOSITORY_BRANCH = 'dev' + +export default defineConfig({ + lang: 'en-US', + title: 'ADAMANT IPFS Node', + description: + 'Self-hosted IPFS storage node for application file delivery, with bounded disk usage, ' + + 'deterministic replication, repair, health checkpoints, and a REST API.', + cleanUrls: true, + lastUpdated: true, + metaChunk: true, + + // A broken internal link is a documentation defect, so the build fails on one + // instead of publishing a page that leads nowhere. + ignoreDeadLinks: false, + + sitemap: { + hostname: SITE_ORIGIN + }, + + head: [ + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], + ['meta', { name: 'theme-color', content: '#0e7490' }], + ['meta', { name: 'author', content: 'ADAMANT Foundation' }] + ], + + /** + * Add a canonical URL and Open Graph metadata per page. + * + * No analytics, telemetry, or third-party runtime script is added here on + * purpose: the site loads nothing that is not served from its own origin. + * + * @param pageData Page currently being rendered + */ + transformPageData(pageData) { + const path = pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2') + const canonical = `${SITE_ORIGIN}/${path}`.replace(/\/$/, '/') + const title = pageData.frontmatter.title ?? pageData.title ?? 'ADAMANT IPFS Node' + const description = pageData.frontmatter.description ?? pageData.description ?? '' + + pageData.frontmatter.head ??= [] + pageData.frontmatter.head.push( + ['link', { rel: 'canonical', href: canonical }], + ['meta', { property: 'og:type', content: 'website' }], + ['meta', { property: 'og:url', content: canonical }], + ['meta', { property: 'og:title', content: title }], + ['meta', { property: 'og:description', content: description }] + ) + }, + + themeConfig: { + logo: { src: '/logo.svg', alt: 'ADAMANT IPFS Node logo' }, + siteTitle: 'ADAMANT IPFS Node', + + nav: [ + { text: 'Guide', link: '/guide/what-is-it' }, + { text: 'Reference', link: '/reference/api' }, + { text: 'Operations', link: '/operations/monitoring' }, + { + text: 'Project', + items: [ + { text: 'Source', link: 'https://github.com/Adamant-im/ipfs-node' }, + { text: 'Releases', link: 'https://github.com/Adamant-im/ipfs-node/releases' }, + { + text: 'Container package', + link: 'https://github.com/Adamant-im/ipfs-node/pkgs/container/ipfs-node' + }, + { text: 'Issues', link: 'https://github.com/Adamant-im/ipfs-node/issues' } + ] + } + ], + + sidebar: [ + { + text: 'Introduction', + items: [ + { text: 'What it is', link: '/guide/what-is-it' }, + { text: 'Use cases', link: '/guide/use-cases' }, + { text: 'Comparison', link: '/guide/comparison' }, + { text: 'Architecture', link: '/guide/architecture' } + ] + }, + { + text: 'Get started', + items: [ + { text: 'Quick start', link: '/guide/quick-start' }, + { text: 'Docker', link: '/guide/docker' }, + { text: 'Installation', link: '/guide/installation' }, + { text: 'Configuration', link: '/guide/configuration' }, + { text: 'Security and privacy', link: '/guide/security' } + ] + }, + { + text: 'Reference', + items: [ + { text: 'API overview', link: '/reference/api' }, + { text: 'Endpoint reference', link: '/reference/endpoints' }, + { text: 'Storage lifecycle', link: '/storage-lifecycle' } + ] + }, + { + text: 'Operations', + items: [ + { text: 'Monitoring and health', link: '/operations/monitoring' }, + { text: 'Persistent state and backups', link: '/operations/persistence' }, + { text: 'Upgrades and rollback', link: '/operations/upgrades' }, + { text: 'Troubleshooting', link: '/operations/troubleshooting' } + ] + }, + { + text: 'Project', + items: [ + { text: 'ADAMANT Messenger', link: '/guide/adamant-messenger' }, + { text: 'Contributing', link: '/guide/contributing' } + ] + } + ], + + outline: { level: [2, 3], label: 'On this page' }, + + socialLinks: [{ icon: 'github', link: 'https://github.com/Adamant-im/ipfs-node' }], + + editLink: { + pattern: `https://github.com/Adamant-im/ipfs-node/edit/${REPOSITORY_BRANCH}/docs/:path`, + text: 'Edit this page on GitHub' + }, + + search: { + provider: 'local' + }, + + footer: { + message: 'Released under the GPL-3.0 License.', + copyright: 'Copyright (c) ADAMANT Foundation and contributors' + } + } +}) diff --git a/docs/guide/adamant-messenger.md b/docs/guide/adamant-messenger.md new file mode 100644 index 0000000..daf3ac4 --- /dev/null +++ b/docs/guide/adamant-messenger.md @@ -0,0 +1,76 @@ +--- +title: ADAMANT Messenger +description: How the reference deployment uses the node, what the node does and does not know about it, and what another application should change. +--- + +# ADAMANT Messenger + +[ADAMANT Messenger](https://adamant.im) runs this node in production for attachment delivery. It is +the deployment the defaults in `config.default.json5` were shaped against, and the reason a few +compatibility decisions in the HTTP contract exist. + +## Why it is here + +An adopter is useful evidence: it shows the service running against real traffic and it explains +otherwise puzzling parts of the contract. It is not the product's purpose. The node is a +general-purpose storage service, and every page of this documentation is written so that a +non-ADAMANT application can follow it unchanged. + +If you are evaluating the node for your own application, [Use cases](/guide/use-cases) and +[Comparison](/guide/comparison) are the pages to read; this one is context. + +## How the messenger uses the node + +The access shape is write-once, read-by-CID: + +- a client uploads an attachment through `POST /api/file/upload` and receives its CID +- the CID travels to the recipient inside the message +- the recipient's client fetches it through `GET /api/file/:cid` + +Sender and recipient are usually on different nodes. That is why a node fetches content it does not +hold and streams it on rather than redirecting: the recipient learns the CID as soon as the message +arrives and asks its own node for it immediately, long before any background job could have moved a +copy there. Routing stays the node's problem, not the client's — see +[Architecture](/guide/architecture). + +Nothing is ever mutated in place, so the absence of mutable naming costs this deployment nothing. + +## What the node does and does not know + +The node stores whatever bytes it is given and does not encrypt content. Content encryption is the +responsibility of the ADAMANT client protocol; the node never sees a plaintext attachment and never +sees an account. + +It also has no notion of the message that carried the CID, of who uploaded it, or of who read it. +Access time is deliberately not recorded: replication tiers on file age rather than on recency, +because recording when a file was last read would build a trail of user activity, and sharing that +trail between nodes would spread it further. See [Security and privacy](/guide/security). + +## Compatibility surface + +Three parts of the contract exist because of existing clients, and they are documented as +compatibility decisions rather than as recommendations: + +- `POST /api/file/upload` is unauthenticated, so clients can upload directly. It is bounded by size, + count, rate, concurrency, and the disk reserve, but it is not an authorization guarantee. + Uploader-signed authorization is open work in [issue #27](https://github.com/Adamant-im/ipfs-node/issues/27) +- `GET /api/node/info` is a public, sanitized route retained for the current PWA and iOS + application. It carries no peer identity and no topology; the detailed operator response moved to + the authenticated `GET /api/node/details` +- `GET /api/file/:cid` ignores `Range` and answers with the complete representation and + `Accept-Ranges: none`, preserving existing client behaviour + +A new application is free to place its own gateway in front of the node and enforce whatever +authorization it needs. + +## Running your own deployment instead + +`config.default.json5` is the ADAMANT production template. Its `nodes` and +`peerDiscovery.bootstrap` lists point at the ADAMANT production mesh, and its `cors.allowedOrigins` +entries are ADAMANT origins. **A deployment that is not joining ADAMANT must replace all three.** +Leaving `nodes` and `peerDiscovery.bootstrap` empty runs a standalone node. + +Start at [Quick start](/guide/quick-start), or [Docker](/guide/docker) for a container. The +messenger client itself is developed in +[Adamant-im/adamant-im](https://github.com/Adamant-im/adamant-im), and the wider protocol +documentation is at [docs.adamant.im](https://docs.adamant.im). diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md new file mode 100644 index 0000000..815ef5f --- /dev/null +++ b/docs/guide/architecture.md @@ -0,0 +1,333 @@ +--- +title: Architecture +description: How the node is composed, how it reaches its peers, what runs between them, and which trust boundaries a deployment has to enforce itself. +--- + +# Architecture + +The node is a single Node.js service built on Helia and libp2p. It keeps content in a local +blockstore, exchanges blocks with a configured set of peers, and exposes an HTTP API for +applications. It is not a wrapper around Kubo, it does not expose a Kubo-compatible API, and it does +not take part in the public IPFS network. + +Everything on this page is the current behaviour of the code. Option names and defaults are +described in [Configuration](/guide/configuration); request and response details are in the +[API reference](/reference/api). + +## Process layout + +One process runs everything. `src/index.ts` is the entry point: it builds the Express application +and starts the scheduled work. `src/helia.ts` creates the Helia node at import time, so every router +shares one node instead of opening a second one. + +Two ports are used: the HTTP API listens on `serverPort`, and libp2p listens on the multiaddrs in +`peerDiscovery.listen`. The configuration templates in the repository use `4000` and +`/ip4/0.0.0.0/tcp/4001`. + +The HTTP API and the Helia node share one blockstore and one datastore, both opened in +`src/store.ts` before the node starts. An upload writes blocks through UnixFS into the same +blockstore that Bitswap serves to peers, and the pin set, the file registry and the health +checkpoint live in the same datastore. There is no sidecar daemon and no second copy of the data. + +Scheduled work runs in the same process: + +| Job | Schedule | Purpose | +| ------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------- | +| Disk usage | `diskUsageScanPeriod` | Refreshes the storage report behind `GET /api/storage/metrics`; one scan also runs at startup | +| Peering | `peeringSchedule` | Redials the entries in `nodes` that are not connected | +| Garbage collection | `storage.gc.schedule` | Frees blocks when space is short; started only when `storage.gc.enabled` is true | +| Admission recovery | `storage.gc.schedule` | Replaces the collector when it is disabled, clearing upload tokens whose handler died | +| Replication repair | `replication.repairSchedule` | Restores missing copies; started only when `replication.enabled` and `replication.repairEnabled` are both true | +| Health checkpoint | `health.checkpointIntervalMs` | Writes the readiness checkpoint on a fixed interval timer rather than on a cron expression | + +## libp2p composition + +The node is composed explicitly from `createHeliaLight`, `withLibp2pLight` and `withBitswap` instead +of `createHelia`. `createHelia` merges the Helia default libp2p configuration — mDNS, the public IPFS +bootstrap list, kad-DHT, AutoNAT, AutoTLS, UPnP, circuit relay and the WebRTC and WebSocket +transports — into whatever is passed to it, which would widen the peer topology without the operator +asking for it. The light constructors apply exactly the configuration given here, which also keeps +the kad-DHT service out of the process. + +Bitswap is kept, because it is how known peers exchange blocks. HTTP gateway routing is not +registered, so a block request never leaves the configured peer set. + +The resulting libp2p configuration is exactly this: + +| Element | Value | +| --------------------- | ----------------------------------------------------------------------------------------- | +| Transport | `tcp()` | +| Connection encryption | `noise()` | +| Stream multiplexer | `yamux()` | +| Peer discovery | `bootstrap()` over `peerDiscovery.bootstrap`, registered only when that list is not empty | +| Services | `identify()` and `ping()` | +| Block exchange | Bitswap | +| Listen addresses | `peerDiscovery.listen` | +| Connection ceiling | `connectionManager.maxConnections`, default `100` | +| Connection allow list | `connectionManager.allow`, set to the `multiAddr` of every entry in `nodes` | +| Peer identity | libp2p private key loaded from, or created in, the datastore at `/pkcs8/self` | + +`identify` lets peers exchange supported protocols and observed addresses. `ping` backs +`GET /api/libp2p/services/ping`. Nothing else is registered: there is no DHT, no mDNS, no circuit +relay, no AutoNAT, AutoTLS or UPnP, no WebRTC or WebSocket transport, no gateway routing and no +IPNS. + +Because the private key lives in the datastore, the peer id is stable across restarts, which is what +lets other operators keep a fixed `multiAddr` for this node. + +```text + HTTP clients reverse proxy (TLS) + | | + v v + +-------------------------------------------------------------------+ + | one Node.js process | + | | + | Express API Helia + libp2p | + | :serverPort :peerDiscovery.listen | + | routers, rate limits, TCP / Noise / Yamux | + | admission, size limits identify, ping, Bitswap | + | | | | + | +----------------+-------------------+ | + | v | + | blockstore-fs + datastore-fs | + | $HOME/ | + | | + | scheduled jobs: disk usage, peering, garbage collection, | + | replication repair, health checkpoint | + +-------------------------------------------------------------------+ + | | + /adamant/replication/1.0.0 /adamant/health/1.0.0 + | | + v v + +--------------------+ +--------------------+ + | peer from "nodes" | | peer from "nodes" | + +--------------------+ +--------------------+ +``` + +## Peer topology + +The peer set is the `nodes` list in the configuration file. Every entry carries a name and a +`multiAddr` containing `/p2p/`, so a peer is identified by its key rather than by its +address. + +`@libp2p/bootstrap` dials the addresses in `peerDiscovery.bootstrap` once shortly after start and +never again. Nothing in libp2p reconnects a peer that restarted or dropped, and a mesh that quietly +comes apart still accepts uploads, so the failure surfaces later as slow retrieval and as +replication that cannot place copies. The peering job closes that gap: on every `peeringSchedule` +tick it dials the entries in `nodes` that are not currently connected, gives each dial ten seconds, +and logs the failures at debug level. The work is bounded by the size of the operator's own peer +list. + +The `allow` list holds the same addresses. It keeps configured peers connectable once +`maxConnections` is reached. It is not an access control list: no deny list is configured, so any +peer that can reach the listen address may still open a connection. What a connected peer is allowed +to ask for is decided by the protocol handlers, not by the connection manager. + +There is no DHT, mDNS, relay or NAT traversal, because the peers are known in advance. Content +routing is unnecessary when every candidate holder is named in the configuration, and leaving those +services out keeps the kad-DHT implementation out of the process. That has two consequences an +operator has to plan for: + +- Every node needs an address its peers can dial directly. Without hole punching or relaying, a node + behind NAT needs a forwarded port and a `multiAddr` that reflects it. +- Adding or removing a node is a configuration change on every other node, and it resets the health + checkpoint epoch. Automatic peer discovery is open work in [issue #28](https://github.com/Adamant-im/ipfs-node/issues/28). + +Avoiding the public DHT and public gateways reduces how much content-routing metadata is published +to strangers. It does not by itself make a deployment private, anonymous, trustless or +censorship-proof: the HTTP API is reachable by anyone who can reach the port, peer addresses live in +each operator's configuration, and the operators of the listed nodes can read what they store. + +## Protocols between nodes + +Two libp2p protocols run between nodes: + +- `/adamant/replication/1.0.0` places, probes and withdraws copies +- `/adamant/health/1.0.0` collects checkpoint attestations + +Both run over libp2p streams rather than over the REST API. The libp2p handshake already proves the +remote peer id cryptographically, so no shared secret has to be distributed, no second HTTP port has +to be exposed, and no peer has to publish an HTTP address for the others to reach it. Messages on +both protocols are length-prefixed JSON with a hard size cap, and a request that exceeds it, or that +does not complete before its timeout, aborts the stream. + +The replication protocol carries seven operations: + +| Operation | Meaning | Accepted from | +| --------- | ------------------------------------------------------------------ | ----------------------- | +| `store` | Store and pin the CID, pulling the blocks over the connection | Peers listed in `nodes` | +| `stage` | Prepare a pin that becomes permanent only after the upload commits | Peers listed in `nodes` | +| `commit` | Make a prepared copy permanent | Peers listed in `nodes` | +| `abort` | Withdraw one upload's claim on a prepared copy | Peers listed in `nodes` | +| `have` | Report whether this node deliberately holds the CID | Peers listed in `nodes` | +| `accept` | Report whether this node has room for another copy | Peers listed in `nodes` | +| `cache` | Hold an unpinned copy nobody is responsible for | Any connected peer | + +The authorization rule is the same on both protocols: anything that makes this node responsible for +content, and every health attestation, is accepted only from a peer whose id appears in `nodes`. +Everything else is refused with `not_authorized`, and the refusal is logged. Until file ownership is +signed by the uploader, that check is what stands between the node and an arbitrary peer spending +its disk. Uploader-signed authorization is open work in [issue #27](https://github.com/Adamant-im/ipfs-node/issues/27). + +`cache` is deliberately open, because it grants nothing a reader does not already have. The blocks +are neither pinned nor registered, so they sit in the same tier as content cached while answering a +read and are reclaimed as soon as space is short, and the DAG is fetched through Bitswap from +whichever connected peer has the blocks — the same path a public read uses. Open is not unbounded: +the request is refused with `no_room` when the node has none, one copy is held to the same size an +upload is, and per-peer and node-wide intake budgets over a rolling window bound how much bandwidth +and cache space peers can spend together. + +The protocol version describes the wire format, not the release. Taking it from `package.json` would +break interoperability on every release, so it is raised only when the format changes in a way an +older node cannot read. The node offers the versions it speaks, newest first, and libp2p negotiates +the first one both ends support. There is currently one entry, so a mixed-version deployment cannot +place copies and an upgrade has to be applied to every node. `GET /api/storage/metrics` reports the +protocol string this node offers under `replication.protocol`, which is what makes a half-upgraded +network visible. + +The health protocol adds three checks of its own on top of authorization. An attestation is refused +when the caller's `membershipVersion` differs from the local one, when the requested round is +neither the local round nor an adjacent one, or when the two timestamps differ by more than +`health.clockSkewToleranceMs`. The membership version is a hash of the sorted peer ids in `nodes`, +so two nodes configured differently cannot attest each other. See +[Monitoring](/operations/monitoring) for how those checkpoints are read. + +## Content flow + +An upload arrives at `POST /api/file/upload`. The rate limiter runs first, then admission refuses +the request before any block is written when the node is at its concurrency limit, when the +aggregate request size is too large, or when storing the request would eat into the disk reserve. +The multipart parser enforces `maxFileCount` and the per-file `uploadLimitSizeBytes` while the bytes +flow, and rejects non-file fields, so an over-limit part never reaches the blockstore. Content is +imported into UnixFS as it streams. Once the parts are in, the handler takes an exclusive lock on +every CID in the request, writes a pin-intent marker, pins, and registers the file; then it places +copies on peers and commits the local records and the remote copies together. + +A download arrives at `GET /api/file/:cid`. After the read limiter and the global and per-client +concurrency guards, the node routes the request itself before asking for any block: + +1. If the block is already in the local blockstore, nothing else happens. +2. Otherwise the node computes the file's likely holders by ranking the configured peers against the + CID, widened by a couple of places so that a file stored just outside the current holder set is + still covered, and dials those that are not connected. The dials are best effort and bounded by a + short timeout; a peer that cannot be reached quickly must not delay the read. +3. Bitswap then fetches the blocks from whichever of the connected peers has them. + +There is no DHT lookup and no gateway fallback at any step, so a CID whose holders are outside the +configured peer set cannot be found. When discovery does not complete within `findFileTimeout`, the +request is answered `408 File request timed out`. The node never answers `404` for a CID it cannot +retrieve, because it cannot know whether the content exists somewhere it cannot see; `404` on this +API means an unknown registry record or an unrouted path. Once streaming starts, an idle timeout and +a size-aware total deadline bound the response. + +Blocks pulled in while serving a read stay in the blockstore unpinned and unregistered. They cost +nothing to keep while there is room and are reclaimed by the collector when space is short, which is +the same tier that holds copies taken through the `cache` operation. Pinning, confirmation, +replication tiers and reclamation are described in full in +[the storage lifecycle](/storage-lifecycle). + +## Persistent state + +Everything durable lives under one directory, `$HOME/`, where `storeFolder` defaults to +`.adm-ipfs`. Both subdirectories are created at startup if they are missing. + +| Path | Contents | +| ------------- | ----------------------------------------- | +| `blockstore/` | Content blocks, stored by `blockstore-fs` | +| `datastore/` | Everything else, stored by `datastore-fs` | + +The datastore holds: + +- the libp2p private key at `/pkcs8/self`, which is the node's peer identity +- the Helia pin set, which decides what is durable +- the file lifecycle registry under `/adm/files` +- pin-intent markers under `/adm/pin-intent` +- the health checkpoint at `/adm/health/checkpoint` +- the replication repair cursor and evidence at `/adm/health/repair-cycle` + +Losing the datastore is not the same as losing the blockstore. Without it the node comes up with a +new peer id, and every other operator's `nodes` entry for it stops matching. Back up and restore the +whole directory as one unit; see [Persistence](/operations/persistence). + +The path is resolved from the home directory of the process user, so it depends on which user runs +the service, and a service manager that changes `HOME` changes where the data goes. An optional +absolute `dataDir` option is open work in [issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) and is not implemented. + +## Trust boundaries + +| Boundary | What it gives | What it does not give | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| HTTP public routes | Rate limits, upload and download concurrency admission, size limits and the disk reserve are enforced before work starts | No caller identity. Anyone who can reach the port can upload and read, and content is not authorized by its uploader ([#27](https://github.com/Adamant-im/ipfs-node/issues/27)). A deployment that needs upload authorization must enforce it in front of the node | +| HTTP administrative routes | `x-api-key` is compared in constant time against `adminApiKey`; a missing or wrong key is `401`, and an unset key makes the routes answer `503`, so they fail closed | No per-operator identity: one shared secret, and two holders of it are indistinguishable in the logs. No TLS at the application level | +| libp2p peers in `nodes` | The handshake proves the peer id, so durable operations and health attestations come from a key the operator listed, with no shared secret to distribute | Identity is not honesty. A listed peer can claim it holds a file, refuse to take one, or stop answering, and its operator can read everything stored on it | +| libp2p peers not in `nodes` | They may connect, exchange blocks over Bitswap, and ask for an unpinned `cache` copy bounded by the intake budgets and the disk reserve | They cannot make this node responsible for content, cannot commit or abort a copy, and cannot contribute to health readiness | +| Reverse proxy | TLS termination, and a trustworthy client address for rate limiting when `trustProxy` names exact addresses, CIDRs or a verified hop count | Nothing by default: with `trustProxy: false` every client shares the proxy address for rate limiting, and `true` is rejected outright. The proxy is also where deployment-specific authorization has to live | + +The HTTP surface and its access classes are described in [Security](/guide/security). + +## Public IPFS limitations + +This node is not a participant in the public IPFS network, and choosing it for public +interoperability would be a mistake. There is no DHT, no public bootstrap list, no gateway routing +and no IPNS, in either direction: + +- A CID uploaded here is announced nowhere. Public gateways, Kubo nodes and other IPFS clients will + not find it unless they connect directly to one of the configured nodes. +- A CID that exists only on the public network cannot be fetched here. The node asks the peers it + was configured with and nothing else, and the request ends in `408`. + +Content addressing itself is unchanged: the CIDs are ordinary IPFS CIDs and the blocks are ordinary +UnixFS blocks, so nothing about the data format prevents interoperability later. Making that work is +research, tracked in [issue #31](https://github.com/Adamant-im/ipfs-node/issues/31), and it is not available today. [Comparison](/guide/comparison) +covers when Kubo, IPFS Cluster or a public gateway is the better choice. + +## Startup and shutdown + +Module evaluation happens before the entry point body runs: the configuration file is selected and +validated, an invalid field aborts the process, the blockstore and datastore are opened, and the +Helia node is created and started, logging one `Listening on ` line per listen address +and then `Helia is running! PeerID: `. + +The entry point then proceeds in this order: + +1. Log `Using config file: `. +2. Start the disk-usage job. +3. Start the peering job and run one peering pass to completion, so the node joins the mesh before + it serves any request. +4. Snapshot the pin set. This decides which pins are legacy before the node can accept new ones, so + a replica staged later is never mistaken for pre-existing content. +5. Register the replication protocol handler with `replication.requestTimeoutMs`. It is registered + whether or not this node places copies of its own, so a node can be added to a network without + reconfiguring every other node first. +6. Initialize replication repair state, then start the health service. It loads the persisted + checkpoint, discards one written under a different membership version, registers the health + protocol, runs one checkpoint immediately, and then repeats on `health.checkpointIntervalMs`. +7. Start registry backfill and admission recovery in the background. They are deliberately not + awaited: reads, uploads and incoming copies do not need a complete registry, and blocking here + would make the whole API unavailable for the length of the migration. When they settle, the + startup reconciliation result is recorded and the lifecycle jobs start — the collector, or + admission recovery when the collector is off, and replication repair when it is enabled. +8. Build the Express application: disable `x-powered-by`, apply `trust proxy`, mount request logging + and metrics collection, apply the CORS policy, answer `GET /` with `IPFS node`, mount the API + routers at their access classes, then the not-found and error handlers. A warning is logged when + `trustProxy` is `false`. +9. Listen on `serverPort`, logging `Server is running on http://localhost:` and a warning that + TLS is not handled at the application level. +10. Register the `SIGINT` and `SIGTERM` handlers, each of which runs once. + +Shutdown runs on either signal: + +1. Log `Received SIGTERM, shutting down`, or the same line for `SIGINT`. +2. Stop the disk-usage, peering, garbage-collection and admission-recovery jobs. +3. Begin stopping replication repair and the health service; both are settled together in the + background. +4. Arm two timers: idle HTTP connections are force-closed after 12 s, and a hard deadline fires at + 15 s. +5. Close the HTTP server. In-flight requests finish; idle keep-alive connections are closed when the + 12 s timer fires. +6. Once the server is closed, wait for the background jobs to stop, then stop Helia, which logs + `Helia node stopped`, and exit with code `0`. +7. If the hard deadline fires first, log `Shutdown timed out` and exit with code `1`. + +A supervisor or container stop timeout therefore has to outlast the 15 s deadline; give it at least +20 s. See [Docker](/guide/docker) for the packaged defaults. diff --git a/docs/guide/comparison.md b/docs/guide/comparison.md new file mode 100644 index 0000000..15f38ff --- /dev/null +++ b/docs/guide/comparison.md @@ -0,0 +1,187 @@ +--- +title: Comparison +description: How ADAMANT IPFS Node compares in design scope with Kubo, IPFS Cluster, a custom Helia application, managed pinning, and object storage. +--- + +# Comparison + +## How to read this page + +This page compares design scope, not speed. It describes what each option is built to do, what it +asks an operator to run, and where its boundaries lie. It is not a benchmark: no performance, +capacity, or availability measurements are published for this project, so none appear here, and no +version-specific behaviour is attributed to any other project. Statements about the alternatives are +deliberately general; read their own documentation before choosing one. + +## At a glance + +| Property | ADAMANT IPFS Node | Kubo | Kubo with IPFS Cluster | Custom Helia application | Managed provider | Object storage | +| ----------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------- | ------------------------------------------- | ---------------------------------------------- | +| Runtime | One Node.js 24 process embedding Helia and Express | A Go daemon | A Go daemon plus a cluster daemon per node | A Node.js process the author maintains | Hosted by the provider | Hosted service, or a self-hosted server | +| Peer discovery | Static `nodes` list, dialled at startup and redialled on `peeringSchedule` | Public IPFS bootstrap peers and local network discovery | As Kubo, plus a cluster peer set | Whatever the author configures | Provider-operated | None; clients address a service endpoint | +| Content routing | None; holders are derived from the CID | Participates in the public DHT | Public DHT through Kubo | Whatever the author configures | Provider-operated | None; keys are location-addressed | +| Public gateway | None | Ships an HTTP gateway | Kubo's HTTP gateway | Only if the author builds one | Provider-operated | The service HTTP endpoint | +| Pin orchestration | libp2p protocol `/adamant/replication/1.0.0` between configured nodes | None; pinning is per node | Consensus inside the cluster | None; Helia has no native pin-orchestration protocol | Provider-operated | Not applicable | +| Placement decision | Rendezvous hashing over the CID, with age-tiered copy counts | Whatever each node pins locally | Allocation decided by the cluster | The author's to design | The provider's | The storage system's | +| Bounded local storage | Disk reserve, GC watermarks, and a TTL for temporary files | Repo GC against a configured storage maximum | Kubo repo GC, plus per-pin replication factors | The author's to implement | Not applicable; the provider holds the data | Quota and lifecycle policy | +| Built-in REST file API | `POST /api/file/upload`, `GET /api/file/:cid`, `GET /api/file/:cid/status` | An RPC API and gateway paths, not an application file API | A cluster API for pins; delivery through Kubo | The author's to build | Provider API | An HTTP object API | +| Health/readiness contract | `GET /api/node/health` always returns `200` with a `state` and a monotonic `height` | Not assessed here | Not assessed here | The author's to define | Provider-defined | Service-defined | +| Operational components to run | One process, behind a TLS-terminating reverse proxy | One daemon per node | Two daemons per node, plus cluster state | One process the author maintains | None | None when hosted; one service when self-hosted | +| Best-fit network shape | A small, fixed, mutually configured node set | Participation in the public IPFS network | Large or dynamic clusters | Requirements this project does not cover | No node operation wanted | Content addressing not required | + +"Not assessed here" means the fact was not verified while writing this page, not that the capability +is missing. Cells describing this project are taken from the code; see [Architecture](/guide/architecture) +and [the storage lifecycle](/storage-lifecycle). + +## Kubo + +[Kubo](https://github.com/ipfs/kubo) is the reference IPFS implementation. This project is not a +Kubo wrapper and does not expose a Kubo-compatible API. + +Choose Kubo when: + +- content must be reachable from the public IPFS network, through the DHT and public gateways +- the deployment wants the reference implementation and the tooling built around it +- single-node pinning is enough, and cross-node durability is handled somewhere else + +Choose ADAMANT IPFS Node when: + +- the node set is known in advance and content should stay inside it +- an application needs an upload and download REST API rather than an RPC surface plus a gateway +- disk usage must stay bounded by configuration, and copies must be placed and repaired without an + additional service + +Public participation is exactly what this project gives up. The controlled topology avoids the public +DHT and public gateways and reduces public exposure of content-routing metadata, but it does not by +itself make a deployment private, anonymous, trustless, or censorship-proof: the node stores and +serves whatever bytes it is given without encrypting them, the upload route is public, and anyone +who learns a CID can fetch it from any node in the set. The cost of the trade is reach. Content +stored here is not announced to the public network, and a CID that only public peers hold cannot be +retrieved through this node. + +## Kubo with IPFS Cluster + +[IPFS Cluster](https://ipfscluster.io) adds pin orchestration on top of Kubo by running a second +daemon beside each node and allocating pins through consensus. + +Choose it when: + +- the node set is large or changes often, and consensus-based allocation is worth its operational cost +- pins must be managed from outside the application that serves files +- the deployment already runs Kubo and intends to keep it + +Choose ADAMANT IPFS Node when: + +- the node set is small, fixed, and mutually configured +- two daemons, extra persistent state, and new failure modes per node are not wanted +- every node should compute placement itself, without a consensus round + +Consensus buys an explicit, inspectable, mutable allocation: an operator can pin, unpin, and +re-allocate through the cluster, and the cluster records that decision. Rendezvous hashing buys none +of that. The designated holder set is a function of the CID and the configured peer list, so it is +not steerable per file and it changes only when the peer list changes. That is the deliberate choice +recorded in "Why not Kubo with IPFS Cluster" in [the storage lifecycle](/storage-lifecycle): +Helia-native orchestration costs one protocol handler and no new runtime, credential, or port, and +Kubo with IPFS Cluster remains the fallback if the node set ever grows past the point where +consensus-based allocation pays for itself. + +## A custom Helia application + +Building directly on [Helia](https://helia.io) and [libp2p](https://libp2p.io) leaves every design +decision open, which is the route this project took. + +Choose it when: + +- the requirements differ from this project's in ways configuration cannot cover +- the application needs libp2p services this node does not register, such as content routing or + browser transports +- the file API must be embedded in an existing service rather than run beside it + +Choose ADAMANT IPFS Node when: + +- placement, repair, garbage collection, and health checkpoints would otherwise have to be written + from scratch +- an operational contract is wanted now: startup configuration validation, bounded disk usage, a + readiness state, and a container image + +This project is a Helia application with its opinions already applied, and those opinions are +narrow. The node is composed from `createHeliaLight`, `withLibp2pLight`, and `withBitswap` rather +than `createHelia`, precisely so that no default is merged in behind the operator: transport is TCP +only, with Noise encryption and Yamux multiplexing, and the only libp2p services registered are +`identify` and `ping`. An application that needs a DHT, a relay, NAT traversal, or browser +transports is better served by building on Helia directly than by working around this node. The +source is GPL-3.0, which is worth checking against the terms a derived service needs. + +## Managed IPFS pinning and storage providers + +Managed providers accept content over an API and keep it available without the operator running a +node. + +Choose them when: + +- running, upgrading, and monitoring a node is not wanted +- content should be reachable from the public IPFS network without operating that participation +- capacity should grow without provisioning disks + +Choose ADAMANT IPFS Node when: + +- the data must stay on hardware the operator controls +- the deployment must keep working without an external account, API key, or third-party availability +- the content set is application-scoped and the peer list is known in advance + +A provider removes the operational work this project asks for and takes the durability question off +the operator's desk. In exchange the operator gives up control over where the bytes live and over +who can observe the access pattern, and takes on a dependency whose availability and terms sit +outside the deployment. Self-hosting reverses both sides of that trade, including the parts that are +inconvenient. + +## Conventional object storage + +S3-compatible object storage, self-hosted or managed, is the mainstream answer for application file +delivery, and for many applications it is the right one. + +Choose it when: + +- content addressing, cross-application deduplication, and peer-to-peer retrieval are not requirements +- mutable keys, per-object authorization, or signed URLs are needed +- the delivery path should be a CDN in front of a bucket + +Choose ADAMANT IPFS Node when: + +- clients already exchange CIDs, and identical bytes must resolve to the same identifier everywhere +- any node in the set must be able to serve any file, without the client knowing which node holds it +- the storage layer should be self-hosted and free of a provider account + +Object storage offers several things this node does not. Byte ranges are one: this node ignores the +`Range` header and answers `Accept-Ranges: none`, sending the complete representation with an +`ETag`. Per-object authorization is another: the download route is public, and access control has to +be applied in front of the node. Object semantics differ too, because a node that cannot retrieve a +CID answers `408` after `findFileTimeout` rather than `404` — it cannot know that the content does +not exist. What object storage does not offer is an identifier derived from the content itself, which +every node in the set resolves the same way and any client can verify. + +## What this project deliberately does not do + +These boundaries are design decisions, not gaps waiting to be closed by configuration. + +- No DHT. No kad-DHT service is registered, so no content-routing query leaves the node and none arrives. +- No IPNS. There is no mutable naming layer; content is addressed by CID only. +- No public gateway. HTTP gateway routing is not registered, so block requests never leave the + configured peer set, and files are served only through the node's own REST routes. +- No Kubo API. This is a Node.js service around an in-process Helia node, not a Kubo wrapper and not + a Kubo-compatible RPC surface. +- No mDNS, circuit relay, NAT traversal, AutoNAT, AutoTLS, or UPnP, and no WebRTC or WebSocket + transports +- No interactive API explorer. The site publishes the OpenAPI file and a generated endpoint table + instead. +- No public IPFS interoperability. Content stored here is not announced to the public network, and + content that only public peers hold is not retrievable through this node. + +Taken together, these choices keep content routing inside a configured peer set and reduce public +exposure of content-routing metadata. They do not make a deployment private, anonymous, trustless, +or censorship-proof. The node does not encrypt what it stores, the upload and download routes are +public, and every operator in the peer set can read every file placed on their node. Deployments +that need confidentiality or upload authorization must provide them above this layer. The full +topology and the reasoning behind it are in [Architecture](/guide/architecture); the exposure model +is in [Security and privacy](/guide/security). Open questions are tracked in the +[issue tracker](https://github.com/Adamant-im/ipfs-node/issues). diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md new file mode 100644 index 0000000..826beab --- /dev/null +++ b/docs/guide/configuration.md @@ -0,0 +1,514 @@ +--- +title: Configuration +description: Complete reference for the JSON5 configuration file, its defaults, its validation rules, and the cross-field checks that abort startup. +--- + +# Configuration + +The node reads one JSON5 file at startup, validates it in full, and aborts if anything is wrong. +There is no environment-variable override for individual options: the file is the whole +configuration surface, and `IPFS_NODE_CONFIG` only chooses which file is read. + +Every default printed on this page comes from `src/config.ts`, `src/security/config.ts`, +`src/security/cors.ts`, `src/security/trustProxy.ts`, `src/security/rateLimit.ts`, +`src/middleware/rateLimiter.ts` and `src/storage/config.ts`. + +## Selecting a configuration file + +The file name is resolved in this order: + +1. `IPFS_NODE_CONFIG`, if set and non-empty +2. the first command-line argument, if present +3. `config.json5` + +A name selects `config..json5`, so the two forms below are equivalent. + +```bash +node dist/index.js # config.json5 +node dist/index.js test1 # config.test1.json5 +IPFS_NODE_CONFIG=test1 node dist/index.js # config.test1.json5 +``` + +The environment variable wins over the argument, which matters when a process manager owns the +argument list. + +The file is read from the repository root, located by walking up from the compiled entry point to +the nearest `package.json`. In the container image that root is `/app`, so the configuration is +mounted at `/app/config.json5`. See [Docker](/guide/docker). + +`config.default.json5` is a template, never a selected file. Its `nodes` and +`peerDiscovery.bootstrap` entries point at the ADAMANT production mesh, so copy it as a starting +shape and replace the peer list with your own before starting a deployment. The first log line +names the file actually in use: + +```text +Using config file: config.json5 +``` + +## Validation + +The whole file is parsed and validated before any cron job, the libp2p host, or the HTTP server +starts. A failure throws and the process exits. + +- Most failures carry the field path: `Invalid config: "storage.gc.lowWatermarkBytes" must be lower than storage.gc.highWatermarkBytes` +- The security section throws its own messages, such as `adminApiKey must be an empty value or a unique secret of at least 32 characters` +- An unreadable or unparseable file fails with `Cannot read config file ` or `Cannot parse config file ` + +Two properties are worth relying on: + +- Unknown keys are ignored. A deployment can keep extra keys in the file, and a key removed from a + past release stays silently inert rather than blocking startup. +- The format is JSON5, so comments, unquoted keys, single quotes, and trailing commas are all + accepted + +The `storage`, `replication`, and `health` sections are optional as a whole, and every key inside +them falls back to a documented default. Everything marked "required" below has no default. + +Cron expressions are only checked for being non-empty strings during validation; an invalid +expression throws when the job is constructed, which is still before the API serves traffic. + +## Core options + +| Option | Type | Default | Description | +| --------------------------- | ---------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `nodes` | array of objects | required | Peer nodes this node replicates with and keeps connections to. May be empty for a standalone node. | +| `nodes[].name` | string | required | Non-empty label used in logs and peering output | +| `nodes[].multiAddr` | string | required | Multiaddr containing a `/p2p/` component | +| `storeFolder` | string | required | Directory name resolved against the home directory of the process user | +| `logLevel` | string | required | One of `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent` | +| `prettyLogs` | boolean | `false` | Routes logs through `pino-pretty`. Leave false in production: the default output is newline-delimited JSON. | +| `peerDiscovery.bootstrap` | array of strings | required | Multiaddrs dialled once at startup. May be empty. | +| `peerDiscovery.listen` | array of strings | required | libp2p listen multiaddrs, at least one | +| `serverPort` | integer >= 1 | required | HTTP API port | +| `diskUsageScanPeriod` | cron string | required | How often the storage scan that feeds the metrics runs | +| `peeringSchedule` | cron string | `*/30 * * * * *` | How often unconnected entries of `nodes` are redialled | +| `uploadLimitSizeBytes` | integer >= 1 | required | Maximum size of one uploaded file | +| `maxFileCount` | integer 1-100 | required | Maximum number of files in one upload request | +| `findFileTimeout` | integer >= 1 | required | Milliseconds allowed for locating a file before a download fails | +| `downloadIdleTimeout` | integer >= 1 | `findFileTimeout` | Maximum pause between download chunks, in milliseconds | +| `downloadMinBytesPerSecond` | integer >= 1 | `32768` | Rate used to derive a size-aware complete-transfer deadline | +| `downloadMaxDurationMs` | integer >= 1 | `max(14400000, size-derived)` | Absolute ceiling for one download response, in milliseconds | + +Notes on the less obvious entries: + +- `storeFolder` is always resolved from the home directory, so the persistent path is + `$HOME/`, holding `blockstore/` and `datastore/`. An optional absolute `dataDir` is + tracked as [issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) and is not implemented. +- `nodes` is more than a peer list. The same multiaddrs form the libp2p connection-manager allow + list, and durable replication and health attestations are accepted only from those peers. Listing + the node itself is harmless: its own peer id is filtered out before dialling, which lets every + node in a deployment run the same list. +- Restricting peers this way avoids the public DHT and public gateways and reduces public exposure + of content-routing metadata. It does not by itself make a deployment private, anonymous, + trustless, or censorship-proof; content served over `GET /api/file/:cid` is public to anyone who + has the CID. +- `peerDiscovery.bootstrap` dials once at startup and never again, which is why `peeringSchedule` + exists. Without a peering schedule a mesh never recovers from a restart. +- The cron examples use the six-field form with a leading seconds field +- `downloadMaxDurationMs` caps the deadline of a single response. The effective deadline is the + smaller of this ceiling and the size-aware deadline + `ceil(fileSize / downloadMinBytesPerSecond) * 1000 + downloadIdleTimeout`. + +## CORS + +`cors.allowedOrigins` is required and must be a non-empty array. Each entry is either a canonical +HTTP or HTTPS origin, or an any-depth subdomain wildcard. + +| Option | Type | Default | Description | +| --------------------- | ---------------- | -------- | ----------------------------------- | +| `cors.allowedOrigins` | array of strings | required | Browser origins accepted by the API | + +Entry rules, enforced when the matcher is compiled: + +- Each entry is a non-empty string of at most 255 characters +- An exact origin must be canonical: scheme `http` or `https`, no credentials, no `*` in the + hostname, path exactly `/`, no query string, no fragment, and no trailing slash. The string must + equal the parsed origin exactly. +- A wildcard has the form `https://*.example.org`, optionally with a port. The suffix hostname must + contain a dot, must not start or end with a dot, must not contain `..`, and must be at most 253 + characters. A port above 65535 is rejected. +- A bare `*` is rejected in every position + +Matching is exact on scheme and port. A wildcard matches any subdomain at any depth but never the +suffix origin itself, so `https://*.example.org` accepts `https://app.example.org` and +`https://a.b.example.org` but not `https://example.org`. Add the bare origin as its own entry when +it is needed. + +A request without an `Origin` header is treated as a non-browser request and is allowed. CORS is a +browser-side control: it does not authenticate anything, and it does not restrict `curl` or any +server-side client. + +The rest of the CORS response is fixed and not configurable: + +| Property | Value | +| --------------- | --------------------------- | +| Methods | `GET`, `POST` | +| Allowed headers | `content-type`, `x-api-key` | +| Credentials | disabled | +| Preflight cache | 600 seconds | + +## Trusted proxy + +`trustProxy` is passed to the Express `trust proxy` setting after validation. It decides which +address a request is attributed to, which is what the rate limiters key on. + +| Option | Type | Default | Description | +| ------------ | -------------------------------------------------- | ------- | ------------------------------------------------------------------ | +| `trustProxy` | `false`, integer >= 1, string, or array of strings | `false` | Verified proxy hop count, or exact proxy addresses and CIDR ranges | + +Accepted forms: + +- `false` — no proxy is trusted, and the socket address is the client address +- a positive integer — the number of verified hops in front of this process +- a string — one exact address or CIDR range +- a non-empty array of strings — several addresses or ranges + +Rejected forms: + +- `true`, which would trust every forwarded address +- `*`, `0.0.0.0/0`, and `::/0` +- comma-separated values inside one string +- strings with surrounding whitespace, and empty strings + +Behind a reverse proxy, list the exact proxy addresses. While `trustProxy` is `false` the node logs +a warning at startup, because every client behind a proxy would otherwise share the proxy address +for rate-limiting purposes. + +## Rate limits + +`rateLimits` holds three independent policies. Each key resolves on its own, so setting only +`rateLimits.upload` leaves `pin` and `read` at their defaults. + +| Option | Type | Default | Description | +| ---------------------------- | ------------- | --------------------------------- | --------------------------------------------------------------------------------- | +| `rateLimits.upload` | policy object | `{ windowMs: 900000, limit: 10 }` | Applies to `POST /api/file/upload` | +| `rateLimits.pin` | policy object | `{ windowMs: 900000, limit: 10 }` | Applies to `POST /api/helia/pin/:cid` | +| `rateLimits.read` | policy object | `{ windowMs: 60000, limit: 100 }` | Applies to the public read routes and the administrative read routes listed below | +| `rateLimits..windowMs` | integer >= 1 | see above | Fixed window length in milliseconds | +| `rateLimits..limit` | integer >= 1 | see above | Requests allowed per window per client address | + +The `read` policy covers `GET /api/file/:cid`, `GET /api/file/:cid/status`, +`GET /api/node/info`, `GET /api/storage/metrics`, `GET /api/storage/policy`, +`GET /api/helia/pins`, and `GET /api/helia/pins/isPinned/:cid`. + +Behaviour of the limiters: + +- Exceeding a window returns `429` with the body `{"error":"Too many requests. Please try again later."}` +- Standard `RateLimit` headers are sent in the IETF draft-8 form; the legacy `X-RateLimit-*` headers + are disabled +- The window is fixed, not sliding, and keyed by the client address that `trustProxy` resolves + +These limits are separate from the storage concurrency limits, which also answer `429`; see +[Storage options](#storage-options). + +## Administrative access + +| Option | Type | Default | Description | +| ---------------- | ------- | ------- | ------------------------------------------------------------------ | +| `adminApiKey` | string | `''` | Secret required in the `x-api-key` header on administrative routes | +| `enableDebugApi` | boolean | `false` | Mounts `/api/debug/*`, which still requires the key | + +Rules for `adminApiKey`: + +- An empty value disables administrative access. Every administrative route then answers `503` with + `{"error":"Service not configured"}`, and the node still serves the public routes normally. +- A non-empty value must be a string of at least 32 characters +- The placeholders `change-me-use-openssl-rand-hex-32`, `replace-with-output-of-openssl-rand-hex-32` + and `your-generated-key-here` are rejected + +Generate a key with: + +```bash +openssl rand -hex 32 +``` + +A missing or non-matching `x-api-key` header returns `401` with `{"error":"Unauthorized"}`. The +comparison runs over a keyed digest in constant time, and authenticated responses carry +`Cache-Control: no-store`. + +`enableDebugApi: false` does not merely reject debug requests: the router is never mounted, so +`/api/debug/autopeering` returns the standard not-found response. Enabling it still requires a +configured key. Which routes are public and which are administrative is listed in +[Security](/guide/security) and in the generated [endpoint reference](/reference/endpoints). + +## Storage options + +The `storage` section bounds what one node accepts and how much of the disk it may use. The whole +section is optional. + +| Option | Type | Default | Description | +| ----------------------------------------- | ------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `storage.maxRequestSizeBytes` | integer >= 1 | `536870912` | Combined size of all files in one upload request. Over this the request is rejected with `413`. | +| `storage.maxConcurrentUploads` | integer >= 1 | `32` | Upload requests writing into the blockstore at once. Over this the request is rejected with `429`. | +| `storage.maxConcurrentDownloads` | integer >= 1 | `64` | File responses retrieving content at once. Over this the request is rejected with `429`. | +| `storage.maxConcurrentDownloadsPerClient` | integer >= 1 | `8` | Share of the global download slots one client address may hold | +| `storage.diskReserveBytes` | integer >= 0 | `5368709120` | Free space on the blockstore filesystem that uploads must never consume. An upload that would consume it is rejected with `507`. | +| `storage.confirmationRequired` | boolean | `false` | Keeps an upload temporary until `POST /api/file/:cid/confirm` | +| `storage.temporaryTtlMs` | integer >= 1 | `86400000` | Lifetime of an unconfirmed upload before it becomes reclaimable | +| `storage.gc.enabled` | boolean | `true` | Runs the scheduled collector. `POST /api/storage/gc` works regardless. | +| `storage.gc.schedule` | cron string | `0 */15 * * * *` | Collector schedule | +| `storage.gc.highWatermarkBytes` | integer >= 1 | `53687091200` | Blockstore size above which collection starts | +| `storage.gc.lowWatermarkBytes` | integer >= 1 | `42949672960` | Blockstore size collection reclaims down to | + +The collector frees blocks only when space is short: above the high watermark, or once free space +falls into `diskReserveBytes`. Leaving `gc.enabled` on therefore costs nothing until a threshold is +reached. + +Setting `storage.confirmationRequired: true` is a protocol change, not a tuning knob: clients must +call `POST /api/file/:cid/confirm` with the administrative key, and anything they upload without it +is reclaimed after `temporaryTtlMs`. The states and transitions are described in +[the storage lifecycle](/storage-lifecycle). + +## Replication options + +The `replication` section decides how many nodes hold a file and how missing copies are restored. +The whole section is optional. + +| Option | Type | Default | Description | +| ------------------------------------ | -------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| `replication.enabled` | boolean | `true` | Whether this node places copies of its own content on its peers | +| `replication.placement` | array of tiers | `[{ minAgeMs: 0, copies: 4 }, { minAgeMs: 15552000000, copies: 3 }, { minAgeMs: 31536000000, copies: 2 }]` | Target copy count by file age, including this node | +| `replication.placement[].minAgeMs` | integer >= 0 | `0` | Age at which the tier starts applying | +| `replication.placement[].copies` | integer >= 1 | `1` | Nodes that should hold a file in that tier | +| `replication.ackQuorum` | integer >= 1 | `1` | Acknowledgements counted before an upload is reported durable, including the local copy | +| `replication.requireQuorumOnUpload` | boolean | `false` | Fails an upload with `503` when the quorum is not reached | +| `replication.requestTimeoutMs` | integer >= 1 | `30000` | Timeout of one replication request to a peer | +| `replication.repairEnabled` | boolean | `true` | Runs the scheduled repair job | +| `replication.repairSchedule` | cron string | `0 */30 * * * *` | Repair schedule | +| `replication.repairBatchDelayMs` | integer >= 0 | `1000` | Pause between bounded passes inside one full repair cycle | +| `replication.repairProbeConcurrency` | integer >= 1 | `4` | Released records probed concurrently during one pass | + +The default tiers reduce copies at 180 days and at 365 days. Copies are reduced by file age rather +than by access time, which would record when users fetch their files. + +`replication.enabled: false` stops this node from placing copies of its own content; it keeps +answering peers that want to place a copy here. That asymmetry is deliberate, so a new node can join +without every existing node being reconfigured first. + +Durability settings are not availability guarantees. Whether a copy survives depends on independent +nodes, independent operators, and what each of them keeps running. + +## Health options + +The `health` section bounds the checkpoint that `GET /api/node/health` reports. The whole section is +optional. + +| Option | Type | Default | Description | +| --------------------------------- | --------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------- | +| `health.checkpointIntervalMs` | integer >= 1000 | `60000` | Length of one checkpoint round | +| `health.maxCheckpointAgeMs` | integer >= `checkpointIntervalMs` | `checkpointIntervalMs * 3` | Age at which the last completed checkpoint becomes stale | +| `health.storageMaxAgeMs` | integer >= `checkpointIntervalMs` | `checkpointIntervalMs * 2` | Maximum age of the cached storage scan a checkpoint accepts | +| `health.repairMaxAgeMs` | integer >= `checkpointIntervalMs` | `3600000` | Maximum age of a completed full repair sweep | +| `health.clockSkewToleranceMs` | integer >= 0 | `10000` | Allowed clock difference between two attesting peers | +| `health.peerAttestationTimeoutMs` | integer >= 1 | `5000` | Bound for one peer attestation call | +| `health.requiredPeerCount` | integer >= 0 | `1` when more than one node is configured, otherwise `0` | Configured remote peers that must attest a round | + +Three consequences worth planning for: + +- With `requiredPeerCount: 0` the peer-attestation check always passes, and the checkpoint proves + only this node's own prerequisites. Network coverage is then unverified. +- `repairMaxAgeMs` must be longer than the largest expected full repair cycle plus the repair + schedule interval, or a node whose repair is still working through its records reports `degraded` +- `checkpointIntervalMs` also sets the recovery lag. A downgrade can happen at read time, but + returning to `ready` needs a successful checkpoint. + +The states and what each check means are described in [Monitoring](/operations/monitoring). + +## Cross-field rules + +Every rule below aborts startup. The message names the field that failed. + +- `peerDiscovery.listen` must contain at least one multiaddr +- `logLevel` must be one of `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent` +- every `nodes[].multiAddr` must contain a `/p2p/` component, and no two entries may resolve + to the same peer id +- `maxFileCount` must be an integer from 1 through 100 +- `uploadLimitSizeBytes` must be a positive integer +- `cors.allowedOrigins` must be a non-empty array, and every entry must satisfy the rules in + [CORS](#cors) +- `trustProxy` must not be `true`, `*`, `0.0.0.0/0`, or `::/0` +- `adminApiKey` must be empty or at least 32 characters, and must not be one of the known + placeholders +- `downloadMaxDurationMs` must be greater than or equal to `downloadIdleTimeout` +- an explicitly set `downloadMaxDurationMs` must also cover + `ceil(uploadLimitSizeBytes / downloadMinBytesPerSecond) * 1000 + downloadIdleTimeout`, so that the + largest accepted file cannot be cut off mid-stream and reported as a retrieval timeout +- `storage.maxRequestSizeBytes` must be greater than or equal to `uploadLimitSizeBytes`, otherwise + no single file could be uploaded +- `storage.maxConcurrentDownloadsPerClient` must not exceed `storage.maxConcurrentDownloads` +- `storage.gc.lowWatermarkBytes` must be lower than `storage.gc.highWatermarkBytes` +- `replication.placement` must be a non-empty array, and `placement[0].minAgeMs` must be `0` so every + file matches a tier +- each later tier must have a strictly greater `minAgeMs` than the tier before it +- each later tier's `copies` must not exceed the tier before it, so copies shrink as a file ages +- `replication.ackQuorum` must not exceed the `copies` value of the fresh tier +- `replication.requireQuorumOnUpload: true` requires `replication.ackQuorum >= 2`, so a strict upload + cannot succeed on the local copy alone +- `storage.confirmationRequired` and `replication.requireQuorumOnUpload` cannot both be `true` +- `health.requiredPeerCount` must not exceed the number of configured remote peers, which is counted + as the number of `nodes` entries minus one +- `health.maxCheckpointAgeMs`, `health.storageMaxAgeMs`, and `health.repairMaxAgeMs` must each be at + least `health.checkpointIntervalMs` + +## Migrating an older configuration + +Unknown keys are ignored, so a removed option does not fail startup. It also does nothing, which is +the risk: a file that still carries an old key looks configured and is not. + +`autoPeeringPeriod` no longer exists. Replace it with `peeringSchedule`, which takes the same cron +form and redials the entries of `nodes` that are not connected. A file that keeps only +`autoPeeringPeriod` starts successfully and falls back to the `*/30 * * * * *` default. + +`cors.origin` and `cors.originRegexps` are replaced by `cors.allowedOrigins`. Regular expressions +are no longer accepted. Rewrite each pattern as an exact origin or an any-depth subdomain wildcard: + +```json5 +// Before +cors: { + origin: ['https://app.example.org'], + originRegexps: ['^https://.*\\.example\\.org$'] +} + +// After +cors: { + allowedOrigins: ['https://app.example.org', 'https://*.example.org'] +} +``` + +Because `cors.allowedOrigins` is required, a file that carries only the old keys aborts at startup +rather than silently allowing nothing. + +The detailed node response moved. `GET /api/node/info` is still public and still returns the version, +timestamp, Helia status, and megabyte disk figures. Peer id, listen multiaddrs, and the byte-accurate +storage report now come from `GET /api/node/details`, which requires `x-api-key`. A monitoring +integration that scraped identity fields from `/api/node/info` needs `adminApiKey` set and the header +added. + +For `health.requiredPeerCount` during a staged rollout: a node cannot complete a checkpoint while +fewer than `requiredPeerCount` peers attest the round, so a node upgraded ahead of its peers reports +a frozen `height` and a state other than `ready`. Set `requiredPeerCount: 0` on the nodes you upgrade first, +then raise it once every peer answers on `/adamant/health/1.0.0`. Two related points: + +- the bound is `nodes.length - 1`, so removing a peer from `nodes` may require lowering + `requiredPeerCount` in the same change +- changing `nodes` changes the membership epoch and resets the persisted checkpoint, so `height` + returns to `0` for the new epoch on every node + +## Full example + +A three-node deployment, shown from the perspective of the first node. Each node runs the same +`nodes` list, including its own entry, which is filtered out before dialling. + +The peer ids and addresses below are placeholders and are not valid. Replace each one with the value +its node reports at `GET /api/node/details`, and use the reachable address of that node. + +```json5 +{ + // Same list on every node. Each multiaddr must end in /p2p/. + nodes: [ + { + name: 'node-a', + multiAddr: '/ip4/198.51.100.11/tcp/4001/p2p/12D3KooWNodeAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + }, + { + name: 'node-b', + multiAddr: '/ip4/198.51.100.12/tcp/4001/p2p/12D3KooWNodeBbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' + }, + { + name: 'node-c', + multiAddr: '/ip4/198.51.100.13/tcp/4001/p2p/12D3KooWNodeCccccccccccccccccccccccccccccccccccccccc' + } + ], + + // Persistent state lives in $HOME/.adm-ipfs. + storeFolder: '.adm-ipfs', + logLevel: 'info', + prettyLogs: false, + + peerDiscovery: { + // Dialled once at startup; peeringSchedule keeps the mesh together after that. + bootstrap: [ + '/ip4/198.51.100.11/tcp/4001/p2p/12D3KooWNodeAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '/ip4/198.51.100.12/tcp/4001/p2p/12D3KooWNodeBbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + '/ip4/198.51.100.13/tcp/4001/p2p/12D3KooWNodeCccccccccccccccccccccccccccccccccccccccc' + ], + listen: ['/ip4/0.0.0.0/tcp/4001'] + }, + + serverPort: 4000, + diskUsageScanPeriod: '*/30 * * * * *', + peeringSchedule: '*/30 * * * * *', + + uploadLimitSizeBytes: 268435456, // 256 MiB per file + maxFileCount: 10, + findFileTimeout: 20000, + downloadIdleTimeout: 20000, + downloadMinBytesPerSecond: 32768, + downloadMaxDurationMs: 14400000, + + cors: { + // Replace with the origins of your own application. + allowedOrigins: ['https://app.example.org', 'https://*.example.org'] + }, + + // Behind a reverse proxy, list the exact proxy addresses instead. + trustProxy: false, + + rateLimits: { + upload: { windowMs: 900000, limit: 10 }, + pin: { windowMs: 900000, limit: 10 }, + read: { windowMs: 60000, limit: 100 } + }, + + // Generate with: openssl rand -hex 32. Empty makes admin routes answer 503. + adminApiKey: '', + enableDebugApi: false, + + storage: { + maxRequestSizeBytes: 536870912, + maxConcurrentUploads: 32, + maxConcurrentDownloads: 64, + maxConcurrentDownloadsPerClient: 8, + diskReserveBytes: 5368709120, + confirmationRequired: false, + temporaryTtlMs: 86400000, + gc: { + enabled: true, + schedule: '0 */15 * * * *', + highWatermarkBytes: 53687091200, + lowWatermarkBytes: 42949672960 + } + }, + + replication: { + enabled: true, + // Three nodes cannot hold four copies, so the fresh tier is set to 3. + placement: [ + { minAgeMs: 0, copies: 3 }, + { minAgeMs: 15552000000, copies: 2 } + ], + ackQuorum: 1, + requireQuorumOnUpload: false, + requestTimeoutMs: 30000, + repairEnabled: true, + repairSchedule: '0 */30 * * * *', + repairBatchDelayMs: 1000, + repairProbeConcurrency: 4 + }, + + health: { + checkpointIntervalMs: 60000, + maxCheckpointAgeMs: 180000, + storageMaxAgeMs: 120000, + repairMaxAgeMs: 3600000, + clockSkewToleranceMs: 10000, + peerAttestationTimeoutMs: 5000, + // At most nodes.length - 1, so 2 here. + requiredPeerCount: 1 + } +} +``` + +With this file in place, follow [Quick start](/guide/quick-start) for a source installation or +[Docker](/guide/docker) for a container. If a node does not reach `ready`, see +[Troubleshooting](/operations/troubleshooting). diff --git a/docs/guide/contributing.md b/docs/guide/contributing.md new file mode 100644 index 0000000..73fbe2e --- /dev/null +++ b/docs/guide/contributing.md @@ -0,0 +1,137 @@ +--- +title: Contributing +description: Development setup, repository layout, the checks that must pass, and the issue and pull-request conventions used by this project. +--- + +# Contributing + +The project is GPL-3.0 and developed in the open at +[Adamant-im/ipfs-node](https://github.com/Adamant-im/ipfs-node). The default branch is `dev`. + +## Development setup + +```bash +git clone https://github.com/Adamant-im/ipfs-node.git +cd ipfs-node +nvm use +npm ci +cp config.default.json5 config.json5 +npm run dev +``` + +`npm run dev` runs the service under nodemon. `npm ci` must be allowed to run install scripts: a +native module of the WebRTC transport package downloads its prebuilt binary during installation, and +a tree installed with `--ignore-scripts` fails at startup. + +Replace the peer list in `config.json5` before starting. The shipped template points at the ADAMANT +production mesh. + +## Repository layout + +| Path | Contents | +| ------------------- | --------------------------------------------------------------------------------- | +| `src/api/` | Express routers, grouped by access class | +| `src/middleware/` | Rate limiting, admission control, upload and download guards, error handling | +| `src/security/` | CORS, trusted proxy, API key, rate-limit policy, access-policy mounting | +| `src/storage/` | Lifecycle registry, admission, placement, replication, repair, collection | +| `src/health/` | Checkpoint state, health service, libp2p health protocol, membership | +| `src/utils/` | Logger, CID helpers, download responses, filename sanitization | +| `test/` | Unit suites | +| `test/integration/` | Multi-node integration suites | +| `docs/` | The documentation site, the OpenAPI document, and the storage lifecycle reference | +| `scripts/` | Audit, documentation generation and validation, container smoke test | + +## Checks + +| Command | Purpose | +| -------------------------- | ------------------------------------------------------------------------- | +| `npm run lint` | ESLint over the repository | +| `npm run format` | Prettier in check mode; `npm run format:fix` writes | +| `npm run typecheck` | `tsc --noEmit` over `src` and `test` | +| `npm run build` | Compiles `src` into `dist` | +| `npm test` | Builds into `dist-test`, then runs the unit and integration suites | +| `npm run security:audit` | Fails on any high or critical production advisory | +| `npm run security:semgrep` | Static analysis over `src/`; needs the `semgrep` CLI installed separately | +| `npm run docs:build` | Generates the API reference, builds the site, and validates the artifact | + +`npm test` runs Node's built-in test runner against `config.test.json5`, which has no bootstrap +peers and listens on loopback with an OS-assigned port. The unit suites cover the HTTP security +boundary, configuration validation, filename sanitization, disk measurement, the storage lifecycle, +and health state. The integration suites start isolated nodes with temporary stores, transfer a file +between them over Bitswap, exercise the replication protocol, and verify that a peer identity +survives a restart. + +Continuous integration runs lint, format, build, test build, unit tests, and integration tests on +every push and pull request against `dev`. Separate workflows build and smoke-test the container +image and build the documentation site. + +To exercise the container locally: + +```bash +docker build -t ipfs-node:local . +./scripts/docker-smoke-test.sh ipfs-node:local +``` + +## TypeScript project layout + +Three configurations share one set of compiler options: + +| File | Purpose | +| --------------------- | --------------------------------------------------------------------- | +| `tsconfig.json` | Type-checks `src` and `test`. Emits nothing; this is what editors use | +| `tsconfig.build.json` | Builds `src` into `dist` for `npm run build` | +| `tsconfig.test.json` | Builds `src` and `test` into `dist-test` for `npm test` | + +Pass no file arguments to `tsc`. Naming a file on the command line makes TypeScript ignore +`tsconfig.json` entirely, so `outDir` is not applied — the `.js` file is written next to its source +— and the project's `lib`, `module`, and `moduleResolution` settings are replaced by defaults, which +reports module-resolution and missing-`@types/node` errors the project does not have. + +## Documentation + +The site is VitePress, sourced from `docs/`. + +```bash +npm run docs:dev # local preview with hot reload +npm run docs:build # generate, build, and validate +npm run docs:preview # serve the built site +``` + +Two things are generated and must not be edited by hand: + +- `docs/reference/endpoints.md` is generated from `docs/openapi.yaml` by + `scripts/generate-api-reference.mjs`. Change the specification, not the page +- `docs/public/openapi.yaml` is a copy of the same specification, served by the site + +The build fails on a broken internal link, a missing generated API reference, a missing or wrong +`CNAME`, or a leaked build path. `docs/storage-lifecycle.md` and `docs/openapi.yaml` keep their +paths so existing repository links stay valid. + +Writing rules live in `AGENTS.md` and apply to human and AI contributors alike. The ones that come +up most: English only, a blank line before and after every list, no trailing period on a +single-sentence list item, a language tag on every fenced block, and no capability, privacy, +resource, or scale claim that the code, the tests, or a recorded measurement does not support. + +## Issues and pull requests + +Follow the [ADAMANT organization governance](https://github.com/Adamant-im/.github). + +- Search existing issues before opening a new one, and use one concise title prefix such as + `[Bug]`, `[Feat]`, `[Enhancement]`, `[Refactor]`, `[Docs]`, `[Test]`, `[Chore]`, `[Task]`, or + `[Composite]` +- Pull request titles use `Type: Short summary`, for example `Docs: Add configuration reference` +- Commit messages follow Conventional Commits +- Structure the pull request body with `Description`, `Related issue`, `How to test`, and + `Checklist`, and reference the issue with a closing keyword when appropriate +- Include validation evidence, and say which checks were not run + +Keep the public surface synchronized. When behaviour changes, `README.md`, this site, +`docs/openapi.yaml`, the container files, and `package.json` metadata are updated in the same +change, or the omission is stated deliberately. + +## Security + +Report suspected vulnerabilities privately through GitHub security advisories on +[the repository](https://github.com/Adamant-im/ipfs-node) rather than by opening a public issue. +Everything that is not a vulnerability belongs in the +[issue tracker](https://github.com/Adamant-im/ipfs-node/issues). diff --git a/docs/guide/docker.md b/docs/guide/docker.md new file mode 100644 index 0000000..5c96463 --- /dev/null +++ b/docs/guide/docker.md @@ -0,0 +1,347 @@ +--- +title: Docker +description: Run the ADAMANT IPFS node as a container with mounted configuration, one persistent volume, health checks, and a graceful stop. +--- + +# Docker + +The container runs the same service as a source installation: one Node.js process that embeds +Helia and libp2p and serves the REST API. Configuration and persistent state stay outside the +image, so the container is replaceable while the node keeps its peer identity and its content. + +## Image + +| Property | Value | +| ----------------- | -------------------------------------------------------- | +| Repository | `ghcr.io/adamant-im/ipfs-node` | +| Base image | `node:24.13.0-bookworm-slim` | +| Architectures | `linux/amd64`, `linux/arm64` | +| Runtime user | `node`, uid 1000 | +| Working directory | `/app` | +| Persistent path | `/data`, declared as a volume, with `HOME=/data` | +| Configuration | mounted at `/app/config.json5`, not shipped in the image | +| Exposed ports | `4000` HTTP API, `4001` libp2p | +| Command | `node dist/index.js` | +| Stop signal | `SIGTERM` | + +The base is Debian bookworm slim on the Node.js 24 line rather than Alpine. Helia pulls +`@libp2p/webrtc`, whose `node-datachannel` native module downloads a prebuilt binary during +install, and glibc is the combination this image is built and smoke-tested on. Alpine is not +offered: musl prebuilds exist upstream, but nothing in this repository has validated that runtime. + +The build is multi-stage. The build stage runs `npm ci`, `npm run build`, then +`npm prune --omit=dev --omit=peer`; the runtime stage copies only `package.json`, +`config.default.json5`, `docker/healthcheck.mjs`, the pruned `node_modules`, and `dist`. The +unprivileged `node` user already exists in the base image, `/data` is its home, and it is the only +path the process writes to. + +Tags come from GitHub Releases, and nothing is published from a branch push: + +- every release publishes an immutable version tag +- a stable release also moves `latest` +- a prerelease publishes its version tag only, so `latest` never points at a prerelease + +```bash +docker pull ghcr.io/adamant-im/ipfs-node: +``` + +No release has been published yet. Take the exact value for `` from the +[releases page](https://github.com/Adamant-im/ipfs-node/releases), or from the +[package page](https://github.com/Adamant-im/ipfs-node/pkgs/container/ipfs-node), and pin a version +tag or a digest in production rather than tracking `latest`. + +## Quick start with Compose + +`docker/docker-compose.yml` is a minimal stack for evaluating the node locally. + +```bash +cd docker +cp config.example.json5 config.json5 +docker compose up --build +``` + +Edit `config.json5` before running anything that matters. The example joins no network: `nodes` and +`peerDiscovery.bootstrap` are empty, so the node peers with nobody and keeps a single local copy of +everything it accepts. `adminApiKey` is empty as well, which makes administrative routes answer +`503`. + +The stack builds the image from the checkout by default. Set `IPFS_NODE_IMAGE` to run a published +image instead: + +```bash +IPFS_NODE_IMAGE=ghcr.io/adamant-im/ipfs-node: docker compose up +``` + +What the file sets, and why: + +- `stop_grace_period: 20s`, because the process has a 15 s internal shutdown deadline +- `ports: 127.0.0.1:4000:4000` for the API and `4001:4001` for the libp2p listener +- `volumes: ipfs-node-data:/data` for all persistent state, and `./config.json5:/app/config.json5:ro` + for the configuration +- `healthcheck` repeating the image defaults, so the knobs are visible in one place +- `security_opt: no-new-privileges:true` on top of the unprivileged runtime user +- `restart: unless-stopped` + +Read the node state once the container is up: + +```bash +curl -s http://127.0.0.1:4000/api/node/health +``` + +## Quick start with docker run + +```bash +docker volume create ipfs-node-data + +docker run -d \ + --name ipfs-node \ + --restart unless-stopped \ + --stop-timeout 20 \ + --security-opt no-new-privileges:true \ + -v ipfs-node-data:/data \ + -v "$PWD/config.json5:/app/config.json5:ro" \ + -p 127.0.0.1:4000:4000 \ + -p 4001:4001 \ + ghcr.io/adamant-im/ipfs-node: +``` + +`--stop-timeout 20` makes a later `docker stop` wait long enough for the graceful shutdown path. +`docker/config.example.json5` is a usable starting point for `config.json5`: copy it out of the +repository and edit it before the first start. + +## Configuration + +The image ships no `config.json5`. Every deployment value — peer list, administrative key, limits, +CORS origins — is mounted at runtime, so nothing deployment-specific ends up in an image layer or +in `docker history`. + +- Mount the file read-only at `/app/config.json5`. The process locates its configuration by walking + up from the compiled entry point to the nearest `package.json`, which is `/app` in the container +- `config.default.json5` is copied into the image as a documented template only. It is never + selected automatically, and its peer list points at the ADAMANT production mesh, so another + deployment must not use it as-is +- `IPFS_NODE_CONFIG` selects `config..json5`; otherwise the first command-line argument is + used, and otherwise `config.json5` +- Configuration is read once at startup, so recreate or restart the container after editing the file +- The build context excludes `config.json5` and `config.*.json5`, along with `.git`, `node_modules`, + `dist`, `logs`, `docs`, `test`, and the state directories + +To run a named configuration: + +```bash +docker run -d \ + --name ipfs-node \ + -e IPFS_NODE_CONFIG=production \ + -v "$PWD/config.production.json5:/app/config.production.json5:ro" \ + -v ipfs-node-data:/data \ + -p 127.0.0.1:4000:4000 \ + -p 4001:4001 \ + ghcr.io/adamant-im/ipfs-node: +``` + +Validation aborts the process and names the offending field, so a container that exits immediately +after start is usually a configuration error; read `docker logs`. Every option is described in +[Configuration](/guide/configuration). + +## Persistent data + +`HOME` is `/data` in the image, and `storeFolder` is resolved from the home directory of the process +user. With the default `storeFolder: '.adm-ipfs'` the store is `/data/.adm-ipfs`, which holds: + +- `blockstore/` with the content blocks +- `datastore/` with the libp2p peer identity, the pin set, the file lifecycle registry, the + pin-intent markers, the repair cycle cursor, and the health checkpoint + +One `/data` volume is therefore the entire durable state of the node. Replacing the container while +keeping the volume keeps the identity, the pins, and the content. + +A named volume is initialized from the image, where `/data` is owned by uid 1000, so ownership is +correct without an extra step. A host bind mount is not: create the directory and give it to uid +1000 before the first start, or the process cannot write its stores. + +```bash +sudo mkdir -p /srv/ipfs-node/data +sudo chown -R 1000:1000 /srv/ipfs-node/data +``` + +Losing the volume is not recoverable from the image. The node generates a new libp2p peer identity, +which changes the multiaddr other operators have configured for it, and content it used to serve +appears missing — a request for such a CID ends in a `408` timeout rather than a `404`, because the +node cannot know whether the content exists elsewhere. + +An optional absolute `dataDir` that would decouple the store path from `HOME` is open work in +[issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) and is not implemented. Backup and +restore procedures are in [Persistence](/operations/persistence). + +## Ports and exposure + +| Port | Purpose | Configured by | +| ------ | ---------------------------------------- | ---------------------- | +| `4000` | HTTP REST API | `serverPort` | +| `4001` | libp2p TCP listener for peer connections | `peerDiscovery.listen` | + +Both ports are declared with `EXPOSE`; publishing them is the operator's decision. + +- Bind port 4000 to loopback and put a TLS-terminating reverse proxy in front of it. The image + contains no TLS, the process serves plain HTTP, and it logs a warning about that at startup +- Port 4001 has to be reachable by every peer listed in `nodes`, otherwise replication and health + attestations cannot be exchanged +- Upload and download routes are public by design, and rate limits and size limits are the only + admission control on them. A deployment that needs authorized uploads has to enforce that in front + of the node +- The controlled peer topology avoids the public DHT and public gateways and reduces public exposure + of content-routing metadata. It does not by itself make a deployment private, anonymous, + trustless, or censorship-proof + +Reverse proxy settings, `trustProxy`, CORS, and administrative key handling are covered in +[Security](/guide/security). + +## Health checks + +`GET /api/node/health` always answers HTTP `200` and reports the node state in the JSON body. A +check that only looked at the status code would call a `degraded` or a `stale` node healthy, so the +bundled check parses the body and compares the `state` field. + +```dockerfile +HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \ + CMD ["node", "/app/docker/healthcheck.mjs"] +``` + +The start period covers startup reconciliation, the first disk usage scan, and the first health +checkpoint, all of which have to complete before a node reports `ready`. The script exits `0` when +the state is accepted and `1` otherwise, printing the failing prerequisite checks from the response +so `docker inspect` shows why a container is unhealthy. + +| Variable | Default | Effect | +| ---------------------------------- | -------------------------------------------------------------- | ----------------------------------------- | +| `IPFS_NODE_HEALTHCHECK_URL` | `http://127.0.0.1:$IPFS_NODE_HEALTHCHECK_PORT/api/node/health` | Full URL to probe | +| `IPFS_NODE_HEALTHCHECK_PORT` | `4000` | API port used when no URL is given | +| `IPFS_NODE_HEALTHCHECK_STATES` | `ready` | Comma-separated states treated as healthy | +| `IPFS_NODE_HEALTHCHECK_TIMEOUT_MS` | `5000` | Request timeout in milliseconds | + +Set `IPFS_NODE_HEALTHCHECK_STATES=ready,degraded` only when a deployment deliberately accepts a node +whose prerequisites currently fail. + +Liveness and readiness are different questions here: + +- liveness is the process running; a node that answers the endpoint at all is alive +- readiness is the `state` field, one of `starting`, `ready`, `stale`, or `degraded` + +Restart decisions belong to liveness, and traffic decisions belong to the state. Recovery from +`degraded` or `stale` back to `ready` requires a successful checkpoint, so expect up to +`health.checkpointIntervalMs` of lag before a recovered node reports `ready` again. The full health +contract is described in [Monitoring](/operations/monitoring). + +## Graceful shutdown + +`STOPSIGNAL` is `SIGTERM`, and the process handles `SIGTERM` and `SIGINT` itself, so it runs as PID +1 without an init shim. + +On receipt the node logs `Received SIGTERM, shutting down`, stops the disk usage, peering, garbage +collection, and admission recovery jobs, stops replication repair and the health service, closes the +HTTP server, stops Helia, and exits with code `0`. Idle connections are force-closed after 12 s, and +a hard deadline of 15 s logs `Shutdown timed out` and exits with code `1`. + +A stop timeout shorter than that deadline risks killing the process while it is still writing +persistent state, so give the container at least 20 s. + +```bash +docker stop --timeout 20 ipfs-node +``` + +With Compose the same budget is `stop_grace_period: 20s`; with `docker run` it is +`--stop-timeout 20`, which becomes the default for a later `docker stop`. + +## Image provenance + +The runtime stage sets the OCI labels `title`, `description`, `source`, `url`, `documentation`, +`licenses`, `vendor`, `authors`, `version`, `revision`, and `created`. The last three come from +build arguments, which the release workflow fills from the release version, the commit SHA, and the +release publication time. + +```bash +docker image inspect ghcr.io/adamant-im/ipfs-node: --format '{{ json .Config.Labels }}' +``` + +Publication is tied to a GitHub Release. The release job checks that the tag is an ancestor of `dev` +and that it matches the version in `package.json` before building, and it pushes `linux/amd64` and +`linux/arm64` in one manifest. Release builds also attach an SBOM and a provenance attestation to +the image in the registry, so the published artifact carries its dependency inventory and its build +metadata alongside it. After publication the same runtime checks are run again against the pulled +image, once per architecture. + +## Building the image yourself + +```bash +docker build -t ipfs-node:local . +``` + +The build needs network access, and install scripts must run: `node-datachannel` downloads its +prebuilt binary during `npm ci`, and a tree installed with `--ignore-scripts` fails at startup. + +| Build argument | Default | Purpose | +| ---------------- | ---------------------------- | ----------------------------------------- | +| `NODE_IMAGE` | `node:24.13.0-bookworm-slim` | Base image for both stages | +| `IMAGE_VERSION` | `0.0.0-dev` | `org.opencontainers.image.version` label | +| `IMAGE_REVISION` | `unknown` | `org.opencontainers.image.revision` label | +| `IMAGE_CREATED` | `1970-01-01T00:00:00Z` | `org.opencontainers.image.created` label | + +Stamping a local build with real metadata: + +```bash +docker build \ + --build-arg IMAGE_VERSION="$(node -p "require('./package.json').version")" \ + --build-arg IMAGE_REVISION="$(git rev-parse HEAD)" \ + -t ipfs-node:local . +``` + +Both published architectures with Buildx: + +```bash +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --build-arg IMAGE_VERSION=0.0.0-dev \ + --build-arg IMAGE_REVISION="$(git rev-parse HEAD)" \ + -t ipfs-node:local . +``` + +A multi-platform result cannot be loaded into the classic Docker image store. Push it to a registry, +or build one platform at a time with `--platform linux/arm64 --load` when the image has to stay +local. + +## Verification + +`scripts/docker-smoke-test.sh` exercises an image the way this page says it should be run: +configuration mounted at `/app/config.json5`, state on a single `/data` volume, an unprivileged +user, and readiness taken from the JSON health state. It needs `docker`, `curl`, and `node`, and it +removes the container, the volume, and its temporary directory on exit. + +```bash +./scripts/docker-smoke-test.sh ipfs-node:local +``` + +An optional second argument pins the platform, which is how a published multi-architecture image is +checked: + +```bash +./scripts/docker-smoke-test.sh ghcr.io/adamant-im/ipfs-node: linux/arm64 +``` + +The nine steps verify that the image: + +1. starts from a mounted configuration and does not run as root +2. reports a documented health state +3. reaches `ready` with a non-zero checkpoint height +4. agrees with its own bundled health check +5. accepts a multipart upload, returns identical bytes on download, reports the file as `confirmed`, + and answers the authenticated `GET /api/node/details` with a peer id +6. shuts down on `SIGTERM` with exit code `0` and the expected log line +7. keeps its content and its libp2p peer identity when the container is replaced on the same volume +8. applies a changed `maxFileCount` from the mounted file without a rebuild, visible in + `GET /api/storage/policy` +9. carries the administrative key in no layer, history entry, or metadata field, and ships no + `/app/config.json5` of its own + +The same script runs in CI on both architectures for changes to the Dockerfile, the `docker/` +directory, `src/`, or the dependency lockfile, and again against the published image after a +release. diff --git a/docs/guide/installation.md b/docs/guide/installation.md new file mode 100644 index 0000000..888c761 --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,321 @@ +--- +title: Installation from source +description: Install, build, and run the ADAMANT IPFS node from a source checkout with PM2 or systemd behind a TLS-terminating reverse proxy. +--- + +# Installation from source + +This page describes a source installation on a host you control: clone, build, configure, and run +the compiled entry point under a process manager behind a reverse proxy. The container image is +documented in [Docker](/guide/docker), and a first local run in +[Quick start](/guide/quick-start). + +The project is distributed as source and as a container image. `package.json` is `private: true` +and nothing is published to npm, so there is no global package to install and no `npx` invocation. + +## Requirements + +| Requirement | Detail | +| ------------ | -------------------------------------------------------------------------- | +| Node.js | Version 24. `.nvmrc` contains `24` and `engines.node` is `>=24.0.0` | +| Git and npm | `npm ci` installs from `package-lock.json` and must reach GitHub releases | +| Filesystem | A POSIX filesystem for the store directory `$HOME/` | +| Inbound TCP | The libp2p listen port, `4001` in the shipped examples | +| Outbound TCP | To every peer multiaddr in `nodes` and `peerDiscovery.bootstrap` | +| HTTP port | `serverPort`, `4000` in the shipped examples, bound to a private interface | +| TLS | A TLS-terminating reverse proxy in front of `serverPort` | + +The repository ships an `.nvmrc`, so `nvm use` inside the checkout selects the supported Node.js +line. Match it before installing; `npm ci` on an older runtime is not supported by `engines`. + +The blockstore and the datastore are plain directory trees written by `blockstore-fs` and +`datastore-fs`. Both live under the home directory of the process user: `blockstorePath` is +`$HOME//blockstore` and `datastorePath` is `$HOME//datastore`, with +`storeFolder` defaulting to `.adm-ipfs` in the shipped configuration. The parent path is the home +directory of the process user, not a configuration option; an optional absolute `dataDir` is +tracked as [issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) and is not implemented. Decide which home directory the service user has +before the first start, because moving the store later moves the peer identity with it. + +libp2p listens on the multiaddrs in `peerDiscovery.listen`, `/ip4/0.0.0.0/tcp/4001` in the shipped +examples. Peers dial that address directly: the node runs TCP only, with no NAT traversal, no +circuit relay, no AutoNAT, and no UPnP, so a host behind NAT needs an explicit port forward for +inbound connections. Outbound TCP is needed to every address listed in `nodes` and +`peerDiscovery.bootstrap`. + +Blocks are exchanged only with the configured peer set. That avoids the public DHT and public +gateways and reduces public exposure of content-routing metadata, but it does not by itself make a +deployment private, anonymous, trustless, or censorship-proof; see +[Security and privacy](/guide/security). + +## Install from source + +```bash +git clone https://github.com/Adamant-im/ipfs-node.git +cd ipfs-node +nvm install +nvm use +npm ci +npm run build +``` + +`nvm install` with no argument reads `.nvmrc` from the current directory, so run it from inside the +checkout. `npm run build` removes `dist` and compiles `src` with `tsconfig.build.json`; the entry +point is `dist/index.js`. + +Create a configuration file before the first start: + +```bash +cp config.default.json5 config.json5 +``` + +`config.default.json5` is a template, not a starting point for a new deployment: its `nodes` and +`peerDiscovery.bootstrap` entries point at the ADAMANT production mesh. Replace them with the peers +of your own deployment, or leave both empty for a single node, and review every other value in +[Configuration](/guide/configuration) before serving traffic. + +Then start the process: + +```bash +node dist/index.js +``` + +The first log line names the configuration file that was read. The process also logs the HTTP port, +a warning that TLS is not handled at the application level, and a warning while `trustProxy` is +`false`. + +`npm ci` must run install scripts. `helia` depends on `@helia/libp2p`, which depends on +`@libp2p/webrtc`, whose `node-datachannel` native module downloads a prebuilt binary from GitHub +releases during installation. Installing with `--ignore-scripts` produces a tree that fails at +startup, and an installer that reaches the npm registry but not GitHub releases fails the same way. + +`--ignore-scripts` is useful for two things and nothing else: + +- auditing the dependency tree without executing package code +- building the documentation site, which never loads the runtime node + +It is not suitable for running the service, for `npm test`, or for producing a deployable tree. + +## Dependency notes + +`helia` depends on `@helia/libp2p`, which depends on `@libp2p/webrtc` even though WebRTC is never +configured here. That pulls two things into the tree: + +- `node-datachannel`, a native module whose prebuilt binary is downloaded from GitHub releases + during `npm install`. Upstream publishes both glibc and musl builds, but glibc is the one this + project is tested on. +- `react-native-webrtc`, and through it `react-native` and its Metro bundler, in the development + tree only + +`npm run security:audit` runs `scripts/audit-production.mjs`, which calls +`npm audit --omit=dev --omit=peer --json` and fails on any high or critical advisory in the +production dependency set. `npm run security:audit:raw` runs npm's own reporter with the same +omissions at `--audit-level=high`. + +A bare `npm audit --omit=dev` uses a wider scope and additionally surfaces advisories against Metro +and its `image-size` dependency. Metro is a React Native build tool that this service never loads, +which is why the repository policy omits peer dependencies. Re-check these when Helia is upgraded. + +```bash +npm run security:audit +npm run security:semgrep +``` + +## Choosing a configuration file + +Configuration is a JSON5 file read from the repository root. The root is located by walking up from +the compiled entry point to the nearest `package.json`, so the file sits next to `package.json`, +not next to `dist/`, and the working directory of the process does not affect which file is found. + +The file name comes from `IPFS_NODE_CONFIG` when that variable is set, otherwise from the first CLI +argument, otherwise from the default. Both forms select `config..json5`. + +| Invocation | File read | +| --------------------------------------------- | -------------------- | +| `node dist/index.js` | `config.json5` | +| `node dist/index.js test1` | `config.test1.json5` | +| `IPFS_NODE_CONFIG=test1 node dist/index.js` | `config.test1.json5` | +| `IPFS_NODE_CONFIG=test1 node dist/index.js x` | `config.test1.json5` | + +The environment variable wins over the argument so that the configuration can still be selected +when the process is started by a tool that owns the argument list. Use it with `npm start`, with +PM2, and in a systemd unit. `npm test` uses the same mechanism and runs against `config.test.json5`. + +The whole file is validated at startup. A missing file, invalid JSON5, or a field with the wrong +type aborts the process with a message naming the offending field; unknown keys are ignored. The +first log line reports the selected file, which is the fastest way to confirm that a service manager +passed the environment you expected. + +## Running as a service + +Both examples below run `dist/index.js` from the checkout, with the configuration file in the +repository root next to `package.json`. + +PM2: + +```bash +npm install --global pm2 +pm2 start dist/index.js --name="IPFS node" +``` + +Pass a non-default configuration through the environment, for example +`IPFS_NODE_CONFIG=prod pm2 start dist/index.js --name="IPFS node"`. Use `pm2 save` and +`pm2 startup` if the process should come back after a reboot. + +A minimal systemd unit at `/etc/systemd/system/ipfs-node.service`: + +```ini +[Unit] +Description=ADAMANT IPFS node +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=ipfs-node +Group=ipfs-node +WorkingDirectory=/opt/ipfs-node +Environment=HOME=/var/lib/ipfs-node +ExecStart=/usr/local/bin/node dist/index.js +Restart=on-failure +RestartSec=5 +KillSignal=SIGTERM +TimeoutStopSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Notes on the unit: + +- Run the service as a dedicated unprivileged account. Create `ipfs-node`, give it + `/var/lib/ipfs-node` as its home, and make it the owner of both that directory and the checkout. +- `Environment=HOME=` decides where the store goes. `storeFolder` is resolved from the home + directory of the process user, so with the shipped value the store is + `/var/lib/ipfs-node/.adm-ipfs`. +- `WorkingDirectory=` is what makes the relative `dist/index.js` in `ExecStart=` resolve. Point it + at the checkout. +- Replace `/usr/local/bin/node` with the absolute path of the Node.js 24 binary. systemd reads no + shell profile, so an nvm-managed `node` is not on the unit's `PATH`; run `command -v node` in a + shell where `nvm use` has selected the right line and use that path. +- `TimeoutStopSec=30` leaves room for the shutdown sequence described below. Anything under 20 + seconds risks `SIGKILL` while persistent state is still being written. +- `Restart=on-failure` restarts after a crash and after a shutdown that exceeded its own deadline + and exited with code `1`, and leaves a clean stop alone + +On `SIGTERM` the process stops the disk-usage, peering, garbage-collection, and admission-recovery +jobs, stops replication repair and the health service, closes the HTTP server, stops Helia, and +exits. Idle connections are force-closed 12 seconds in, and a hard internal deadline of 15 seconds +logs `Shutdown timed out` and exits with code `1`. A stop timeout below 20 seconds cuts that +sequence short. + +Logs are newline-delimited JSON from Pino on stdout, which journald captures as it stands. Keep +`prettyLogs: false` outside an interactive terminal so the structured fields survive. + +## Reverse proxy + +The Node.js process serves plain HTTP and never terminates TLS. Bind `serverPort` to a private +interface or firewall it, and let every client reach it through an HTTPS reverse proxy. The +application port must never be exposed to the internet directly. + +An nginx server block for the shipped ports: + +```nginx +server { + listen 443 ssl; + http2 on; + server_name ipfs.example.org; + + ssl_certificate /etc/letsencrypt/live/ipfs.example.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ipfs.example.org/privkey.pem; + + # At or below uploadLimitSizeBytes; 256m matches the shipped 268435456. + client_max_body_size 256m; + + location / { + proxy_pass http://127.0.0.1:4000; + proxy_http_version 1.1; + + proxy_set_header Host $host; + # Overwrite, never append: a client-supplied X-Forwarded-For must not survive. + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + + # Stream uploads straight through instead of spooling the whole body first. + proxy_request_buffering off; + # Stream downloads back as they are produced. + proxy_buffering off; + + # Applies between two successive reads, not to the whole transfer. Keep it + # above downloadIdleTimeout so a stalled transfer ends on the node's own + # deadline and returns its documented status. + proxy_read_timeout 120s; + } +} +``` + +The proxy must overwrite untrusted forwarding headers. `$proxy_add_x_forwarded_for` appends to +whatever the client sent, so at an internet-facing edge use `$remote_addr`; the appending form is +correct only when the hop in front of nginx is itself trusted and sanitizing. + +Overwriting is only half of it: `trustProxy` has to list the proxy addresses, otherwise Express +keeps using the socket address and every client shares the proxy's identity and rate-limit bucket. +The process logs a startup warning while the value is `false`. Configure exact addresses or CIDR +ranges: + +```json5 +trustProxy: ['127.0.0.1/8', '::1/128'] +``` + +A numeric hop count is accepted only for a fixed topology where every path to the application +crosses exactly that many trusted hops. The blanket value `true` is rejected, because a client +could then spoof `X-Forwarded-For` whenever the last proxy does not overwrite it. + +`client_max_body_size` bounds one request at the edge; the node independently rejects a single file +over `uploadLimitSizeBytes` and a combined request over `storage.maxRequestSizeBytes`. Keep the +proxy limit at or below `uploadLimitSizeBytes` so oversized bodies are refused before they reach the +process. + +Application rate limiters are in-memory counters per process, so the proxy must also enforce request +rates, connection limits, and header limits for any multi-process or distributed deployment. The +full boundary, including the administrative key rules, is described in +[Security and privacy](/guide/security). + +## Upgrading + +An upgrade is a `git` update, a fresh `npm ci`, a rebuild, and a restart. The store directory is not +touched, so the peer identity, the blockstore, the pin set, the lifecycle registry, and the health +checkpoint survive; no store migration is required. + +Read [Upgrades and rollback](/operations/upgrades) for the procedure, the order to follow across a +mesh, and how to roll back. + +## Uninstalling + +Stop and disable the service first, so nothing writes to the store while it is being removed: + +```bash +sudo systemctl disable --now ipfs-node +``` + +Under PM2, use `pm2 delete "IPFS node"` and remove the saved process list. + +Then remove the store directory. With the shipped `storeFolder` that is `~/.adm-ipfs` in the home +directory of the service user: + +```bash +rm -rf /var/lib/ipfs-node/.adm-ipfs +``` + +Removing the store is irreversible and destroys more than cached content: + +- the libp2p private key kept in the datastore under `/pkcs8/self`, which is the node's peer + identity. Every peer that lists this node by `/p2p/` has to be reconfigured after a + rebuild. +- every block in the blockstore, together with the pin set. Any file whose only remaining copy was + held here is gone. +- the file lifecycle registry, the repair cursor, and the health checkpoint + +Back the directory up first if any of that content still matters; see +[Persistent state and backups](/operations/persistence). Finally delete the checkout, which also +removes `config.json5` and the administrative key it contains, and remove the service account if it +is no longer used. diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md new file mode 100644 index 0000000..89ea466 --- /dev/null +++ b/docs/guide/quick-start.md @@ -0,0 +1,377 @@ +--- +title: Quick start +description: Build, configure, and run a single ADAMANT IPFS node from source, then verify health, upload, and download with curl. +--- + +# Quick start + +The shortest path from an empty directory to a running node: clone, build, write a standalone +configuration, start the process, and confirm with `curl` that it stores and serves a file. + +The result is a single node that joins no network. It stores one local copy of everything it +accepts and replicates nothing, which is enough to exercise the API and to decide whether the +service fits. Adding peers is a configuration change, not a rebuild. To run the same service as a +container instead, use [Docker](/guide/docker). + +## Requirements + +| Requirement | Detail | +| ------------------ | ---------------------------------------------------------------------------- | +| Node.js 24 | `.nvmrc` pins the major version and `engines.node` is `>=24.0.0` | +| Git and npm | The project is distributed as source and is not published to npm | +| Reverse proxy | Required for any public deployment; the process does not terminate TLS | +| Administrative key | Needed for operator endpoints; generate one with `openssl rand -hex 32` | +| Two TCP ports | `serverPort` for the HTTP API and the `peerDiscovery.listen` port for libp2p | + +Install Node.js 24 with nvm so that `nvm use` inside the repository selects the version recorded in +`.nvmrc`. Any Node.js 24 installation works; `.nvmrc` only makes the choice explicit and keeps +contributors on one version. + +TLS is not handled at the application level. The node logs a warning about this on every start, so +put a TLS-terminating reverse proxy in front of it before exposing it to the internet, and read +[Security](/guide/security) before doing so. + +Administrative routes answer `503` while `adminApiKey` is empty. A non-empty key must be at least +32 characters and must not be one of the placeholder values the validator rejects, so generate a +real secret rather than editing an example by hand. + +## Install and build + +```bash +git clone https://github.com/Adamant-im/ipfs-node.git +cd ipfs-node +nvm use +npm ci +npm run build +node dist/index.js +``` + +`npm ci` must be allowed to run install scripts. Helia depends on `@libp2p/webrtc`, whose +`node-datachannel` native module downloads a prebuilt binary during installation. Installing with +`--ignore-scripts` produces a dependency tree that fails at startup; that flag is suitable only for +auditing dependencies and for building the documentation site. + +The build compiles TypeScript into `dist/`. Rerun `npm run build` after every code change; the +running process does not pick up source edits on its own. + +## Configure + +Configuration is a JSON5 file in the repository root. The file is selected by the +`IPFS_NODE_CONFIG` environment variable, otherwise by the first CLI argument, otherwise by the name +`config.json5`. Both forms select `config..json5`, so `node dist/index.js test1` and +`IPFS_NODE_CONFIG=test1 node dist/index.js` are equivalent. + +```bash +cp config.default.json5 config.json5 +``` + +::: warning +`config.default.json5` is the ADAMANT production template. Its `nodes` list and its +`peerDiscovery.bootstrap` list point at the ADAMANT production mesh, and its `cors.allowedOrigins` +entries are ADAMANT origins. A deployment that is not joining ADAMANT must replace all three before +starting the node. Leaving `nodes` and `peerDiscovery.bootstrap` empty runs a standalone node. +::: + +A minimal standalone configuration holds the required keys only: + +```json5 +{ + nodes: [], + storeFolder: '.adm-ipfs', + logLevel: 'info', + peerDiscovery: { + bootstrap: [], + listen: ['/ip4/0.0.0.0/tcp/4001'] + }, + serverPort: 4000, + diskUsageScanPeriod: '*/30 * * * * *', + uploadLimitSizeBytes: 268435456, + maxFileCount: 10, + findFileTimeout: 20000, + cors: { + allowedOrigins: ['http://localhost:8080'] + }, + health: { + requiredPeerCount: 0 + } +} +``` + +Every other option falls back to a documented default, including the whole `storage`, +`replication`, and `health` sections. [Configuration](/guide/configuration) lists each default and +the cross-field rules that are checked at startup. + +Notes on the keys above: + +- `nodes` and `peerDiscovery.bootstrap` are empty, so this node dials nobody and nobody is + authorized to place replicas on it. The node dials only the peers a configuration lists, which + keeps it off the public DHT and away from public gateways and reduces public exposure of + content-routing metadata. That is not by itself privacy, anonymity, trustlessness, or censorship + resistance +- `storeFolder` is resolved against the home directory of the process user, so this node keeps its + blockstore and datastore in `$HOME/.adm-ipfs`. An absolute data directory is not implemented yet; + it is tracked as [issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) +- `peerDiscovery.listen` must contain at least one multiaddr even for a standalone node +- `cors.allowedOrigins` must be non-empty and accepts exact origins and any-depth subdomain + wildcards such as `https://*.example.org`. A bare `*` is rejected. Requests without an `Origin` + header, including `curl`, are not affected by CORS +- `health.requiredPeerCount` is `0` because there is no remote peer to attest a checkpoint round. + It is also the default when fewer than two nodes are configured, and it can never exceed the + number of configured remote peers + +This configuration leaves `adminApiKey` unset, so every administrative route answers `503`. To use +them, add a generated key and send it as the `x-api-key` header: + +```json5 +{ + // openssl rand -hex 32 + adminApiKey: 'replace-with-output-of-openssl-rand-hex-32' +} +``` + +That exact placeholder is one of the values the validator refuses, so a configuration copied +without editing aborts at startup instead of running on a published secret. + +Configuration is read once at startup, and validation aborts the process with a message naming the +offending field. A node that exits immediately after start is usually a configuration error; read +the first lines of its log. + +## Run + +Start the built entry point in the foreground: + +```bash +node dist/index.js +``` + +The first log line reports the selected file, for example `Using config file: config.json5`. +Peering with the configured nodes runs before the API starts serving. Once the server is listening, +the node logs `Server is running on http://localhost:4000` followed by a warning that TLS is not +handled at the application level, and a warning that `trustProxy` is `false`. Both warnings are +expected on a fresh installation. + +The HTTP server binds every interface on `serverPort`. Upload and download are public routes, so +keep the port behind a firewall or a reverse proxy while evaluating rather than exposing it +directly. + +To run a named configuration: + +```bash +IPFS_NODE_CONFIG=production node dist/index.js +``` + +Under PM2: + +```bash +npm install --global pm2 +pm2 start dist/index.js --name="IPFS node" +``` + +`SIGINT` and `SIGTERM` are each handled once. The node stops its scheduled jobs, stops replication +repair and the health service, closes the HTTP server, stops Helia, and exits with code `0`. Idle +connections are force-closed after 12 s, and a hard deadline at 15 s exits with code `1` after +logging `Shutdown timed out`, so any supervisor that stops this process should allow at least 20 s. + +## Verify + +`GET /` answers with the text `IPFS node`, which is the cheapest check that the process is +listening. Everything below assumes the node from the configuration above, running on port 4000. + +### Health + +```bash +curl --fail-with-body http://localhost:4000/api/node/health +``` + +This route always returns HTTP `200`. The status code says only that the process answered, so a +consumer must read the `state` field, which is one of `starting`, `ready`, `stale`, or `degraded`. +A check that looks at the status code alone will call a `degraded` node healthy. + +```json +{ + "version": "0.1.0", + "uptimeMs": 123456, + "state": "ready", + "height": 1720614960000, + "timestamp": 1720614998797, + "evaluatedAt": 1720614998700, + "checkpoint": { + "intervalMs": 60000, + "observedAt": 1720614998700, + "ageMs": 97, + "maxAgeMs": 180000 + }, + "membership": { + "version": "d0f1...", + "requiredPeers": 0, + "attestedPeers": 0 + }, + "startup": { "complete": true, "healthy": true }, + "storage": { + "measuredAt": 1720614980000, + "measurementAgeMs": 18797, + "reserveHealthy": true + }, + "replication": { + "repairRequired": true, + "lastCompleteAt": 1720614900000, + "ageMs": 98797, + "backlog": 0 + }, + "checks": { + "checkpointFresh": true, + "clockConsistent": true, + "helia": true, + "startupReconciliation": true, + "storageFresh": true, + "storageReserve": true, + "repairFresh": true, + "peerAttestations": true + } +} +``` + +`membership.version` is a 64-character hex digest of the configured peer set, truncated above. A +new node reports `starting` until startup reconciliation finishes, and reaches `ready` only after a +checkpoint in which every prerequisite in `checks` passed. With the default replication settings +that includes a completed repair cycle, so allow for the first cycle and the first checkpoint +before expecting `ready`. + +With `health.requiredPeerCount` at `0`, `checks.peerAttestations` is always true and a checkpoint +proves only this node's own prerequisites. Network coverage is unverified on a standalone node. +[Monitoring](/operations/monitoring) describes the full contract. + +### Upload and download + +Upload accepts multipart parts named `files`. Text fields are rejected with `400`. + +```bash +curl --fail-with-body \ + --form 'files=@file.txt' \ + http://localhost:4000/api/file/upload +``` + +```json +{ + "filesNames": ["file.txt"], + "cids": ["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"], + "files": [ + { + "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", + "name": "file.txt", + "state": "confirmed", + "expiresAt": null, + "replication": { + "mode": "quorum", + "desiredCopies": 4, + "copies": 1, + "required": 1, + "acknowledged": 1, + "replicaCount": 0, + "cachedCount": 0, + "satisfied": true, + "networkTooSmall": true, + "failedAttemptCount": 0, + "attempts": [] + } + } + ], + "replication": { + "mode": "quorum", + "desiredCopies": 4, + "copies": 1, + "required": 1, + "acknowledged": 1, + "replicaCount": 0, + "cachedCount": 0, + "satisfied": true, + "networkTooSmall": true, + "failedAttemptCount": 0, + "attempts": [] + } +} +``` + +Placement is decided per CID, so each entry of `files` carries its own report; the top-level +`replication` object repeats the first file's report for clients written against it. On a +standalone node the policy asks for four copies, one copy can exist, the local copy satisfies the +quorum, and `networkTooSmall` is true. The report carries counts and outcomes only, never node +names or peer ids. + +Read the file back by CID: + +```bash +curl --fail-with-body \ + --output downloaded.bin \ + http://localhost:4000/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm + +cmp file.txt downloaded.bin +``` + +The response is the complete file, sent as an `application/octet-stream` attachment with content +sniffing disabled. It carries an `ETag` holding the quoted CID and a private one-hour +`Cache-Control` value that requires revalidation. Range requests are not served: the header is +ignored and responses carry `Accept-Ranges: none`. + +A CID this node cannot retrieve is never answered with `404`, because the node cannot know that the +content does not exist anywhere. Retrieval that finds no holder ends in `408` after +`findFileTimeout`, which on a standalone node is the expected answer for any CID it did not store +itself. + +### Upload and download status codes + +| Status | Upload | Download | +| ------ | ---------------------------------------------------------------- | -------------------------------------------------- | +| `200` | Every file was stored and pinned | The response carries the complete file | +| `304` | — | The `If-None-Match` validator matched | +| `400` | No file, too many files, or one file over `uploadLimitSizeBytes` | The CID is invalid | +| `408` | — | Discovery or retrieval timed out | +| `413` | Combined size over `storage.maxRequestSizeBytes` | — | +| `429` | Upload rate or concurrency limit | Read rate, per-client, or global concurrency limit | +| `503` | A required replication quorum was not reached | — | +| `507` | Storing the request would consume `storage.diskReserveBytes` | — | +| `500` | Storage or replica settlement failed; not a clean rejection | Failure before streaming started | + +### Effective limits + +```bash +curl --fail-with-body http://localhost:4000/api/storage/policy +``` + +The response reports the limits and the durability mode a client should expect before uploading, +which is the fastest way to confirm that the running process uses the file that was just edited: + +```json +{ + "maxFileCount": 10, + "uploadLimitSizeBytes": 268435456, + "maxRequestSizeBytes": 536870912, + "maxConcurrentUploads": 32, + "maxConcurrentDownloads": 64, + "maxConcurrentDownloadsPerClient": 8, + "confirmationRequired": false, + "temporaryTtlMs": 86400000, + "durability": { + "mode": "quorum", + "placement": [ + { "minAgeMs": 0, "copies": 4 }, + { "minAgeMs": 15552000000, "copies": 3 }, + { "minAgeMs": 31536000000, "copies": 2 } + ], + "ackQuorum": 1 + } +} +``` + +Every value outside `maxFileCount` and `uploadLimitSizeBytes` comes from a default, because the +minimal configuration sets neither `storage` nor `replication`. + +## Next steps + +- [Configuration](/guide/configuration) for every option, its default, and the validation rules +- [Security](/guide/security) for the TLS boundary, the administrative key, CORS, trusted proxies, + and what the public routes do and do not authorize +- [Docker](/guide/docker) for the same service as a container with mounted configuration and one + persistent volume +- [API reference](/reference/api) for the complete request and response contract +- [Monitoring](/operations/monitoring) for health states, checkpoints, and what to alert on diff --git a/docs/guide/security.md b/docs/guide/security.md new file mode 100644 index 0000000..dd0b6e2 --- /dev/null +++ b/docs/guide/security.md @@ -0,0 +1,358 @@ +--- +title: Security +description: Trust boundaries, HTTP access classes, TLS and proxy expectations, the administrative key, CORS, limits, and what the peer topology does not protect. +--- + +# Security + +This page describes the security boundaries the node enforces itself, the boundaries it expects a +deployment to enforce around it, and the privacy properties of the controlled peer topology. +Everything here is current behaviour of the code. Option names and defaults are described in +[Configuration](/guide/configuration); the composition of the libp2p host is described in +[Architecture](/guide/architecture). + +## Threat model + +Assets worth protecting in a deployment: + +- the blockstore and datastore under `$HOME/`, which hold content blocks, the pin set, + and the file lifecycle registry +- the libp2p private key, stored in the datastore at `/pkcs8/self`, which is the node's peer + identity +- the administrative API key in the configuration file +- free disk space on the filesystem that holds the blockstore + +Components the node assumes are trusted: + +- the host filesystem and the operating-system account the process runs as +- the reverse proxy in front of the HTTP port, when one is deployed +- the peers listed in `nodes`, which may place durable copies on this node and attest its health + +Components the node treats as untrusted: + +- every HTTP client, including uploaders +- every libp2p peer that is not listed in `nodes` +- the bytes of every uploaded file + +Within that model the node defends against unauthenticated use of administrative routes, unbounded +disk consumption, request floods from a single address, forged forwarding headers once trusted proxy +addresses are configured, internal detail leaking into HTTP error responses, and content identifiers +leaking into logs. It does not defend against the cases listed under +[what this does not protect against](#what-this-does-not-protect-against). + +## HTTP access policy + +Routers are mounted in classes by `src/security/accessPolicy.ts`. Administrative classes receive the +API-key middleware at mount time, so a route cannot be added to an administrative router and reach +the network unauthenticated. + +| Class | Routes | Policy | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| Public | `GET /`, `GET /api/node/health`, `GET /api/node/info` | No authentication | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | No authentication; rate and upload limits apply | +| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | No authentication | +| Administrative | `GET /api/node/details`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, `POST /api/storage/gc`, `POST /api/storage/repair`, all `/api/helia/*`, all `/api/libp2p/*` | `x-api-key` required | +| Disabled by default | `GET /api/debug/autopeering` | Only when `enableDebugApi` is true; still needs the key | +| Peer protocols | libp2p, not HTTP | Handshake-authenticated | + +Two properties hold across every class: + +- a path outside the documented surface returns `404` with the body `{"error":"Not found"}` +- thrown values pass through one error handler that maps them to a fixed set of statuses and + messages. The complete error is logged server-side; stack traces, filesystem paths, and dependency + messages are never returned to a client. + +`x-powered-by` is disabled, so the response set carries no framework banner. The full request and +response contract is in the [API reference](/reference/api) and the generated +[endpoint reference](/reference/endpoints). + +## TLS boundary + +The process serves plain HTTP. There is no TLS listener, no certificate handling, and no HTTPS +redirect at the application level. `app.listen(serverPort)` is called without a host, so the HTTP +port is bound on every interface of the machine. At startup the node logs a warning saying that TLS +is not handled at the application level and that the service belongs behind a TLS-terminating +reverse proxy. + +A deployment has to close that boundary itself: + +- restrict `serverPort` to a private interface or a firewall rule so that only the reverse proxy can + reach it +- terminate TLS at the proxy and forward plain HTTP to the node over a trusted network path +- have the proxy replace client-supplied forwarding headers rather than append to them. `X-Forwarded-For`, + `X-Forwarded-Proto`, and `X-Forwarded-Host` arriving from the internet must not survive into the + request the node sees. +- keep the libp2p listener out of the HTTP proxy. `peerDiscovery.listen` (template + `/ip4/0.0.0.0/tcp/4001`) carries Noise-encrypted libp2p streams, not HTTP, and must be reachable + by peers directly. + +The container image reflects the same split: it exposes `4000` for the HTTP API and `4001` for the +libp2p listener, and ships no TLS material. See [Docker](/guide/docker). + +## Reverse proxy and client identity + +`trustProxy` is passed to `app.set('trust proxy', ...)` after validation in +`src/security/trustProxy.ts`. + +| Value | Meaning | +| ---------------- | ------------------------------------------------------------------------ | +| `false` | Default. No forwarding header is trusted; `req.ip` is the socket address | +| Positive integer | Number of trusted proxy hops in front of the node | +| String | One exact address or CIDR range | +| Array of strings | Several exact addresses or CIDR ranges | + +Startup aborts on `true`, on `*`, `0.0.0.0/0`, or `::/0`, on comma-separated values, on strings with +surrounding whitespace, on a hop count below `1`, and on an empty array. The blanket `true` is +rejected because it makes the last value of `X-Forwarded-For` authoritative for any client that +sends one. + +Client identity matters because `req.ip` is the key for every rate limiter and for the per-client +download concurrency limit: + +- with `trustProxy: false` behind a proxy, every client shares the proxy address, so one client's + traffic is counted against all of them +- with a setting that is wider than the actual proxy chain, a client can forge a forwarding header + and reset its own counters + +The node logs a warning at startup whenever `trustProxy` is `false`, stating that all clients will +share the proxy address for rate limiting until exact trusted proxy addresses are configured. The +warning is expected when clients connect directly, and is a defect to fix when they do not. + +## Administrative API key + +Generate a key with: + +```bash +openssl rand -hex 32 +``` + +Validation in `src/security/config.ts` accepts an empty value or a string of at least 32 characters +that is not one of the placeholders shipped in documentation and templates +(`change-me-use-openssl-rand-hex-32`, `replace-with-output-of-openssl-rand-hex-32`, +`your-generated-key-here`). Anything else aborts startup with a message naming the field. + +The middleware fails closed. While `adminApiKey` is empty, the administrative routes stay mounted +and answer `503` with `{"error":"Service not configured"}`, so an unset key cannot silently leave +them open. With a key configured, a request must carry it in the `x-api-key` header: + +```bash +curl -sS -H "x-api-key: $ADM_IPFS_ADMIN_KEY" http://127.0.0.1:4000/api/node/details +``` + +A missing header, a non-string header, or a mismatch returns `401` with `{"error":"Unauthorized"}`. +The comparison runs `timingSafeEqual` over HMAC-SHA-256 digests keyed with the configured value, so +neither the length of the key nor a matching prefix is observable from response timing. A successful +check sets `Cache-Control: no-store` on the response. + +Operational notes: + +- the key is read only from the request header. Do not place it in a URL, a query string, or + browser-side code. +- request logs redact `x-api-key`, `authorization`, and `cookie` +- the value is read at startup, so rotating it means editing the configuration file and restarting + the process +- `enableDebugApi` is a second, independent switch. `/api/debug/*` is not mounted at all unless it is + `true`, and it still requires the key when it is. + +## CORS + +`cors.allowedOrigins` is required and must be a non-empty array. Each entry is either a canonical +HTTP or HTTPS origin, such as `https://adm.im` or `http://localhost:8080`, or an any-depth subdomain +wildcard of the form `https://*.adamant.im`. + +Startup rejects a bare `*`, an entry carrying a path, credentials, query string, or fragment, a +scheme other than `http` or `https`, an entry longer than 255 characters, a wildcard hostname +without a dot or longer than 253 characters, and a port above 65535. + +Matching for the rule `https://*.adamant.im`: + +| Origin | Result | +| ----------------------------- | ------------------------------------------------- | +| `https://msg.adamant.im` | Allowed | +| `https://a.b.adamant.im` | Allowed; the wildcard matches any depth | +| `https://adamant.im` | Not allowed; the suffix origin itself is excluded | +| `http://msg.adamant.im` | Not allowed; the scheme differs | +| `https://msg.adamant.im:8443` | Not allowed; the port differs | +| `https://notadamant.im` | Not allowed; the match requires the dot boundary | + +An exact entry matches that origin and nothing else. A request that carries no `Origin` header is +allowed through: those are non-browser clients such as `curl`, server-to-server calls, and mobile +applications, which the browser origin model never governed. + +The rest of the CORS configuration is fixed: methods `GET` and `POST`, allowed request headers +`content-type` and `x-api-key`, `credentials: false`, and a preflight `maxAge` of 600 seconds. + +CORS is never authentication. It tells a browser which pages may read a response; it stops no other +client, and it protects no route. Because `credentials: false` prevents a browser from attaching +cookies, a page can only reach an administrative route if the page itself holds the key, which is +one more reason to keep the key out of browsers. + +## Rate and concurrency limits + +Two independent mechanisms bound HTTP load, and both answer `429`. + +Rate limiters count requests per client address in a fixed window, using `express-rate-limit` with +`draft-8` standard headers and legacy headers disabled. + +| Limiter | Option | Default | Routes | +| ------- | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Upload | `rateLimits.upload` | 10 per 900000 ms | `POST /api/file/upload` | +| Pin | `rateLimits.pin` | 10 per 900000 ms | `POST /api/helia/pin/:cid` | +| Read | `rateLimits.read` | 100 per 60000 ms | `GET /api/file/:cid`, `GET /api/file/:cid/status`, `GET /api/node/info`, `GET /api/storage/metrics`, `GET /api/storage/policy`, `GET /api/helia/pins`, `GET /api/helia/pins/isPinned/:cid` | + +`GET /api/node/health` deliberately carries no rate limiter, so a load balancer or container probe +cannot exhaust one. + +Limiter state is in memory in one process. It is cleared by a restart, and a second process or a +second host keeps its own counters, so the configured limit is per process and not per deployment. A +deployment that runs more than one process, or that fronts the node with a proxy, has to enforce +rates at the proxy as well. + +Admission limiters are a different mechanism: they bound how many operations may be in flight at +once, not how often they may be requested. + +| Limiter | Option | Default | Refusal | +| --------------------------- | ------------------------------------------------------------ | ------- | ------------------------------------ | +| Uploads | `storage.maxConcurrentUploads` | 32 | `429` with `Retry-After: 5` | +| Downloads, global | `storage.maxConcurrentDownloads` | 64 | `429` with `Retry-After: 5` | +| Downloads, per client | `storage.maxConcurrentDownloadsPerClient` | 8 | `429` with `Retry-After: 5` | +| Incoming copies over libp2p | Derived as `max(4, floor(storage.maxConcurrentUploads / 4))` | 8 | Protocol refusal with code `no_room` | + +The per-client download share exists because a download slot is held for the whole transfer: without +it, one address can hold every global slot and leave the rest of the network with `429` until those +transfers end. Incoming copies get a limiter of their own because they arrive over libp2p, where the +HTTP rate limiters do not apply, and because a copy claims the aggregate request limit for its whole +duration. + +Since a rate refusal and an admission refusal are both `429`, the status alone cannot tell an +operator which one fired, and the two need opposite reactions. That is why `GET /api/node/details` +reports limiter occupancy under `concurrency`: active count and limit for uploads, incoming copies, +and downloads. See [Monitoring](/operations/monitoring). + +## Public upload decision + +`POST /api/file/upload` is unauthenticated on purpose, for compatibility with clients that upload +directly to the node. The decision is bounded rather than open-ended: + +- `uploadLimitSizeBytes` caps one file and `maxFileCount` caps the number of files in a request; both + are enforced by the streaming multipart parser, so an over-limit part never reaches the blockstore +- `storage.maxRequestSizeBytes` caps the combined size of one request and is checked from + `Content-Length` before the parser runs, answering `413` +- the multipart body accepts `files` parts only; any text field is rejected with `400` +- the upload rate limiter and the upload admission limiter apply, answering `429` +- `storage.diskReserveBytes` is claimed before any block is written, so a request that would consume + the reserve is refused with `507` +- `storage.confirmationRequired` keeps an upload temporary until an authenticated confirmation, and + `replication.requireQuorumOnUpload` refuses an upload with `503` when the required copies were not + acknowledged + +Those limits bound what an uploader can consume. They are not an authorization guarantee: anyone who +can reach the port can store bytes on the node. A deployment that needs signed upload authorization +must enforce it at a trusted gateway in front of the node. Uploader-signed deletion, and the +authorization protocol it depends on, are open work in [issue #27](https://github.com/Adamant-im/ipfs-node/issues/27) and are not implemented; see the +[issue tracker](https://github.com/Adamant-im/ipfs-node/issues). + +On the serving side, a download is sent as `application/octet-stream` with +`Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`, so a browser does not render +an uploaded file as a document in the node's origin. Range requests are not served and responses +carry `Accept-Ranges: none`. Lifecycle states and reclamation are described in +[the storage lifecycle](/storage-lifecycle). + +## Privacy properties + +The controlled topology keeps three things off the public network: + +- no CID is disclosed to a public gateway. There is no gateway integration, no `/ipfs/` path routing, + and no HTTP gateway block routing. +- no DHT provider records are published. No kad-DHT service is registered, so the node neither + announces content nor resolves provider records. +- block requests stay inside the configured peer set. Bitswap exchanges blocks with connected peers, + and the node dials only the multiaddrs in `nodes` and `peerDiscovery.bootstrap`. + +That is the whole of the benefit. It does not make a deployment private, anonymous, trustless, or +censorship-proof. Content is not confidential to the operator, the peers listed in `nodes` observe +the CIDs this node asks them for, and anyone who learns a CID and can reach the HTTP port can fetch +the file. + +Content handling: + +- the node stores whatever bytes it is given and does not encrypt content. Blocks on disk are + readable by anything with filesystem access, so an application that needs confidentiality has to + encrypt before upload and manage its own keys. +- access time is deliberately not recorded. Replication placement tiers on file age + (`replication.placement`), because recording when a file was last read would build a trail of user + activity, and sharing that trail between nodes would spread it further. + +Response surface: + +- public responses carry counts rather than peer identity. `GET /api/file/:cid/status` reports how + many holders acknowledged a file and whether this node still holds it, not which peers do, and it + omits the original filename. +- `GET /api/storage/metrics` reports capacity, watermarks, and background job state, with no + filenames, no CID lists, and no peer identity +- `GET /api/node/info` carries no node identity. Peer id, listen multiaddrs, byte-accurate storage + figures, HTTP counters, and limiter occupancy are on the authenticated `GET /api/node/details`. + +Logging: + +- request logs record the method, the route shape, and the status code only. A variable path segment + is masked, so `/api/file//status` is logged as `/api/file/:param/status`. +- application messages pass a scrubber that replaces content identifiers, peer ids, multiaddrs, and + stack traces with fixed placeholders before a record is written +- an upload logs how many files arrived, never their names +- `x-api-key`, `authorization`, and `cookie` are redacted in request and response logs + +## What this does not protect against + +- A compromised host. The blockstore, the datastore, the libp2p private key at `/pkcs8/self`, and the + configuration file holding the administrative key are ordinary files readable by the account that + runs the process. +- Anyone who learns a CID. Retrieval is unauthenticated, so the CID is the only thing a download + needs. +- The operator of any node listed in `nodes`. A configured peer is trusted to hold durable copies, to + answer for content, and to attest health. +- A peer that is not listed in `nodes` but can reach the libp2p listener. There is no connection + gater, so such a peer can connect, exchange blocks over Bitswap for content this node already + holds, and request a cache copy, which is bounded by intake room rather than by authorization. + Durable replication operations and health attestations are refused. Firewall the libp2p port to the + peer set when that distinction is not enough. +- Content abuse. Uploads are unauthenticated and there is no scanning, classification, or takedown + workflow; removal today is an operator action through the authenticated + `POST /api/file/:cid/unpin`. +- Distributed denial of service. Limiters are per process and in memory, so absorbing a flood from + many addresses needs a proxy or a network layer in front of the node. +- Traffic accounting and volume quotas. Per-client bandwidth accounting and monthly limits are open + work in [issue #29](https://github.com/Adamant-im/ipfs-node/issues/29) and are not implemented. +- Availability. Replication settings bound durability, not uptime: availability still depends on + independent nodes, independent operators, and deployment choices. + +## Dependency and static analysis + +| Command | What it does | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `npm run security:audit` | Runs `npm audit --omit=dev --omit=peer --json` and fails, naming the packages, when any advisory has `high` or `critical` severity | +| `npm run security:audit:raw` | The same scope through plain `npm audit` with `--audit-level=high`, for reading the report directly | +| `npm run security:audit:full` | Writes the full JSON report for that scope to `npm-audit-report.json` | +| `npm run security:semgrep` | Runs Semgrep over `src/` with `p/owasp-top-ten`, `p/nodejs`, `p/expressjs`, `p/typescript`, and the repository rules in `.semgrep/rules.yml` | + +The policy is that a `high` or `critical` advisory in the production dependency tree fails the check. +Development and peer dependencies are outside that scope on purpose: the container build runs +`npm prune --omit=dev --omit=peer`, so the audited tree is the tree that runs. + +Two practical notes: + +- Semgrep is not an npm dependency of this repository. The script calls the `semgrep` CLI, which has + to be installed separately. +- an install for auditing only may use `--ignore-scripts`, but the resulting tree cannot start the + node, because a native dependency of the WebRTC transport package needs its install script. Use a + normal `npm ci` for anything that has to run. + +## Reporting a vulnerability + +Report suspected vulnerabilities privately through GitHub security advisories on +[the repository](https://github.com/Adamant-im/ipfs-node) rather than by opening a public issue. + +A useful report states the affected version or commit, the configuration needed to reproduce the +problem, the request or peer interaction that triggers it, and the impact observed. Everything that +is not a vulnerability belongs in the +[issue tracker](https://github.com/Adamant-im/ipfs-node/issues). diff --git a/docs/guide/use-cases.md b/docs/guide/use-cases.md new file mode 100644 index 0000000..2de8223 --- /dev/null +++ b/docs/guide/use-cases.md @@ -0,0 +1,143 @@ +--- +title: Use cases +description: Where ADAMANT IPFS Node fits, where another storage system is the better answer, and how to decide between them. +--- + +# Use cases + +ADAMANT IPFS Node is a self-hosted IPFS storage node for application file delivery, with bounded +disk usage, deterministic replication, repair, health checkpoints, and a REST API. It is a +standalone Node.js service composed from Helia and libp2p, not a wrapper around Kubo and not a +Kubo-compatible API. + +The fit follows from the topology. The libp2p node uses TCP only, registers `identify` and `ping`, +and discovers peers exclusively from the multiaddrs listed in `nodes`. There is no DHT, no mDNS, +no relay, no NAT traversal, no IPNS, and no HTTP gateway routing. Retrieval dials the peers that +the placement policy says should hold the CID, so a read never depends on public content routing. + +## Good fit + +### Application-owned content-addressed file delivery + +An application uploads through `POST /api/file/upload`, receives a CID, and any node in the set +serves that CID through `GET /api/file/:cid`. The identifier is content-derived, so it is stable +and verifiable, and every accepted file gets a lifecycle record the node can report on through +`GET /api/file/:cid/status`. Placement is deterministic rather than negotiated: each node ranks +candidate holders for a CID with rendezvous hashing over the configured peer set, so every node +derives the same holders without coordination. That works when the storage nodes are known in +advance and administered by one team or by parties that already trust each other. + +### Privacy-conscious deployments preferring a controlled peer mesh + +Because no content router is registered, the node does not advertise CIDs to the public DHT and +does not serve content through a public gateway. Blocks move over Bitswap between connected peers +from the configured set. That reduces public exposure of content-routing metadata compared with a +node that announces every block it stores. + +It does not make a deployment private, anonymous, trustless, or censorship-proof. The libp2p +listener accepts connections from any dialer that can reach it, only durable replication and +health attestations are restricted to peers listed in `nodes`, uploads and reads are unauthenticated +by design, and TLS is terminated outside the process. See [Security](/guide/security) for the +boundaries an operator still has to draw. + +### Replication, repair, bounded storage, and health without a second control plane + +One process runs the HTTP API, the Helia node, the replication protocol, the repair cycle, the +garbage collector, and the health service, configured by one JSON5 file. Durability is expressed +as `replication.placement` tiers that reduce copies as a file ages, missing copies are re-placed +by the scheduled repair cycle in bounded passes, and disk usage is bounded by +`storage.gc` watermarks, `storage.diskReserveBytes`, and the upload admission limits. +`GET /api/node/health` reports a state and a persisted checkpoint height, and +`GET /api/storage/metrics` reports storage counters and job state. Teams that would otherwise +operate a separate pinning-orchestration control plane next to their IPFS daemon get this from a +single service. See [the storage lifecycle](/storage-lifecycle) and +[Monitoring](/operations/monitoring). + +### Messenger and media-attachment storage + +Attachments are written once and read by whoever holds the CID, which is exactly the access shape +the node implements. Uploads are accepted without authentication so that clients can post directly, +downloads are streamed as `application/octet-stream` attachments with sniffing disabled and an +`ETag` for revalidation, and per-client plus node-wide download concurrency limits keep one address +from occupying every transfer slot. Nothing is ever mutated in place, so the absence of mutable +naming costs such a deployment nothing. + +Unauthenticated upload is a compatibility decision, not an authorization guarantee. A deployment +that needs signed upload authorization must enforce it at a trusted gateway in front of the node. + +### Self-hosted services that prefer predictable limits to breadth + +Every limit is an explicit configuration field, and validation aborts startup naming the field that +is wrong or contradictory, so a misconfiguration surfaces before traffic does. Durable state lives +in one directory, and the container image keeps it on one volume. The trade is deliberate: this is +a focused storage service, not a full public IPFS implementation, and features outside that scope +are absent rather than partially implemented. See [Configuration](/guide/configuration) and +[Docker](/guide/docker). + +## Choose another solution when + +### You need a general-purpose public IPFS node + +Use [Kubo](https://github.com/ipfs/kubo). This node does not participate in the public DHT, does +not implement IPNS or any mutable naming, does not expose a gateway, and does not implement the +Kubo HTTP RPC surface. Existing Kubo clients and tooling will not work against it; the REST API is +its own contract, documented in [API reference](/reference/api). + +### The cluster is large, dynamic, or made of mutually untrusted operators + +Use [Kubo with IPFS Cluster](https://ipfscluster.io). Peers come from a static `nodes` list that +must be applied consistently on every member, bootstrap dials that list once and `peeringSchedule` +redials it, and changing the list changes the peer-set epoch, which resets persisted health +checkpoint heights. Durable placement is accepted only from configured peers, so every member is a +party the others have chosen to trust. Peer discovery instead of static lists is open work +([issue #28](https://github.com/Adamant-im/ipfs-node/issues/28)) and is not available. + +### End users must own their files and delete them + +Deletion is administrative. `POST /api/file/:cid/unpin` requires the admin key and releases the +file on the node that received the call; the blocks are reclaimed by the next collection, and other +holders are unaffected by that call. There is no protocol by which the uploader proves ownership +and requests removal: uploader-signed deletion is open work ([issue #27](https://github.com/Adamant-im/ipfs-node/issues/27)). Until it exists, an +application that must offer authenticated end-user deletion needs its own authorization layer in +front of the node, or a storage system that models per-object ownership. + +### You need built-in egress accounting and monthly quotas + +Use object storage with metering, or meter at a gateway in front of the node. The node enforces +per-endpoint rate limits and concurrency caps, but it does not account for transferred bytes per +client and has no monthly transfer limit. Traffic accounting and monthly limits are open work +([issue #29](https://github.com/Adamant-im/ipfs-node/issues/29)). + +### Content must be reachable from the public IPFS network + +Use [Kubo](https://github.com/ipfs/kubo) or a managed pinning provider. Nothing stored here is +announced to public content routing, and no public gateway serves it, so an outside client cannot +find the content unless it dials one of the deployment's nodes directly. Public-network +interoperability is under research ([issue #31](https://github.com/Adamant-im/ipfs-node/issues/31)) and must not be assumed. + +### You need the S3 ecosystem, mature cloud IAM, lifecycle tiers, or managed global delivery + +Use object storage. The node has one administrative API key rather than per-user identities, +roles, or policies; its lifecycle is a pin registry plus a garbage collector rather than storage +classes and transition rules; and it has no managed multi-region distribution. Applications built +around S3-compatible SDKs, bucket policies, or signed object URLs will be fighting the design. + +## Deciding + +| If you need | Use | +| ---------------------------------------------------------------------------------------------------------------- | -------------------------- | +| Content-addressed file delivery over a REST API, with bounded disk usage and replication across a known peer set | ADAMANT IPFS Node | +| Public DHT participation, IPNS, a gateway, or the Kubo RPC surface | Kubo | +| Pinning orchestration across a large, changing, or independently operated fleet | Kubo with IPFS Cluster | +| An IPFS node embedded in your own service, with your own storage, routing, and API choices | A custom Helia application | +| Content served by the public IPFS network without operating nodes yourself | A managed pinning provider | +| S3-compatible APIs, IAM, lifecycle tiers, or managed global delivery | Object storage | + +For a feature-by-feature breakdown of these options, see [Comparison](/guide/comparison). + +## Reference deployment + +ADAMANT Messenger stores chat attachments on a mesh of these nodes, and `config.default.json5` +ships with that mesh as its peer list. It is an adopter and a worked example rather than the +purpose of the project: the node is meant for any application with the same access shape. See +[ADAMANT Messenger](/guide/adamant-messenger). diff --git a/docs/guide/what-is-it.md b/docs/guide/what-is-it.md new file mode 100644 index 0000000..52612b8 --- /dev/null +++ b/docs/guide/what-is-it.md @@ -0,0 +1,115 @@ +--- +title: What it is +description: Overview of the ADAMANT IPFS node, a self-hosted storage service with a REST API, deterministic replication, repair, and health checkpoints. +--- + +# ADAMANT IPFS Node + +## What it is + +Self-hosted IPFS storage node for application file delivery, with bounded disk usage, deterministic +replication, repair, health checkpoints, and a REST API. + +The node is a standalone Node.js service. It embeds a Helia node and a libp2p host in its own +process and puts an Express REST API in front of them. Files are uploaded over HTTP, imported into +an on-disk blockstore as UnixFS content, addressed by CID, and served back over HTTP. It is not a +wrapper around Kubo, it does not speak the Kubo RPC API, and it is not an IPFS gateway: the HTTP +surface is a small, versioned application API, not a general-purpose IPFS interface. + +The libp2p host is composed explicitly from `createHeliaLight`, `withLibp2pLight`, and +`withBitswap` rather than from `createHelia`, so only the transports and services listed in the +configuration are registered. A deployment is a set of nodes that know each other by multiaddr: +each node lists its peers under `nodes`, dials them at startup and on a schedule, and exchanges +blocks, replicas, and health attestations with them. + +The audience is application developers who need file storage behind their own API, service +operators who run that storage themselves, and self-hosters who want content-addressed storage +with predictable disk behaviour. ADAMANT Messenger is an adopter and a reference deployment of the +node, not the reason it exists; see [ADAMANT Messenger](/guide/adamant-messenger) for how that +deployment is configured. + +## What it is not + +- Not a public IPFS node. It connects to the peers it is configured with, and content is not + announced to the wider network. +- No DHT participation. No kad-DHT service is registered, so the node neither publishes nor + resolves provider records. +- No IPNS. There is no naming layer, no record publishing, and no name resolution. +- No public gateway. There is no `/ipfs/` path routing and no HTTP gateway block routing; the + download route serves a CID as an attachment through the application API. +- No Kubo API compatibility. The RPC API of Kubo is not implemented and its client libraries do + not work against this service. +- No end-user identity or session layer. The only authentication is a single administrative + `x-api-key` for operator endpoints; public routes have no accounts, tokens, or sessions. +- No S3 API. There are no buckets, no object keys, and no S3-compatible request signing. +- No built-in TLS. The process serves plain HTTP and logs a warning at startup that TLS must be + terminated by a reverse proxy in front of it. + +## Feature overview + +| Capability | Description | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| REST upload and download | `POST /api/file/upload` accepts multipart `files` parts; `GET /api/file/:cid` streams the content back | +| Content addressing | Uploads are imported as UnixFS byte streams and identified by the resulting CID | +| Controlled peer mesh | TCP transport, Noise encryption, and Yamux multiplexing, with only the `identify` and `ping` libp2p services registered | +| Bitswap block exchange | Blocks move over Bitswap between connected peers; no DHT or HTTP gateway routing is registered, so requests are not published to the public network | +| Bounded storage lifecycle | Disk reserve, aggregate request limits, temporary uploads with a TTL, and watermark-driven garbage collection | +| Deterministic placement | Rendezvous hashing over the configured peer set, so every node computes the same holders for a CID without negotiating | +| Quorum or best-effort durability | `replication.ackQuorum` counts acknowledged copies; `requireQuorumOnUpload` decides whether a short upload fails or succeeds best effort | +| Replication repair | A scheduled, resumable cycle re-places copies for records that fall short of their placement | +| Health checkpoints | A monotonic checkpoint advances only when local prerequisites and the required peer attestations pass | +| Rate and concurrency limits | Per-endpoint rate limiters for upload, pin, and read, plus admission control on concurrent uploads and downloads | +| Structured logging | Pino writes newline-delimited JSON, with CIDs, peer identities, and credentials scrubbed from records | + +Defaults for every option named above are listed in [Configuration](/guide/configuration), and the +full request and response contract in [API overview](/reference/api). + +## Trust and topology boundaries + +A controlled peer list changes what the network can observe about a deployment, and it is worth +being precise about how much. Avoiding the public DHT and public gateways means the node does not +publish provider records and does not answer gateway traffic, which keeps content-routing metadata +off the public network. It does not make a deployment private, anonymous, trustless, or +censorship-proof: the operator of every node in the list sees the content it holds, the transport +is still ordinary TCP over the public internet, and a network observer still sees connections +between the nodes. + +Within that list, membership is what authorizes work. The libp2p handshake proves the remote peer +identity cryptographically, so no shared secret is distributed and no additional port is exposed. +Durable replication operations on `/adamant/replication/1.0.0` are refused with `not_authorized` +unless the calling peer id appears in `nodes`, and health attestations on +`/adamant/health/1.0.0` are counted only for the peers configured there. Holding an unpinned cache +copy is the one operation open to any peer, because it costs no more than serving a read to the +same peer and the blocks are reclaimed as soon as space is short. + +A controlled list is not a private-network keying model. There is no pre-shared network key and no +connection gater: the configured multiaddrs are the connection-manager `allow` list, which exempts +them from the connection ceiling rather than excluding anyone else, so any peer that can reach the +libp2p listener can still complete a handshake. Restricting who may reach that port is a network +boundary, not an application setting; see [Security and privacy](/guide/security). + +## Current limitations + +The following are tracked as open work and are **not available today**: + +- Uploader-signed deletion ([#27](https://github.com/Adamant-im/ipfs-node/issues/27)). Upload is unauthenticated, and there is no protocol by which an + uploader proves ownership in order to delete content. A deployment that needs authorized upload + or deletion must enforce it at a trusted gateway in front of the node. +- Peer discovery beyond static lists ([#28](https://github.com/Adamant-im/ipfs-node/issues/28)). Every node must be listed in `nodes` on every other + node, and changing that list resets the health checkpoint epoch. +- Traffic accounting and monthly limits ([#29](https://github.com/Adamant-im/ipfs-node/issues/29)). The node applies rate and concurrency limits, but + does not meter transferred bytes per client or enforce a volume budget. +- Absolute `dataDir` ([#30](https://github.com/Adamant-im/ipfs-node/issues/30)). The store is always resolved under the home directory as + `$HOME/`, so relocating it means changing `HOME` or the folder name. +- Public IPFS interoperability ([#31](https://github.com/Adamant-im/ipfs-node/issues/31)). Content stored here is not retrievable from the public IPFS + network, and public content is not retrievable through this node. + +Progress on each is tracked in the +[issue tracker](https://github.com/Adamant-im/ipfs-node/issues). + +## Where to go next + +- [Use cases](/guide/use-cases) for the deployments this node fits and the ones it does not +- [Comparison](/guide/comparison) for how it differs from Kubo and IPFS Cluster +- [Architecture](/guide/architecture) for the storage, replication, and health internals +- [Quick start](/guide/quick-start) to run a node and upload a first file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..68d9b67 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,86 @@ +--- +layout: home + +hero: + name: ADAMANT IPFS Node + text: Self-hosted content-addressed file storage + tagline: A self-hosted IPFS storage node for application file delivery, with bounded disk usage, deterministic replication, repair, health checkpoints, and a REST API. + image: + src: /logo.svg + alt: ADAMANT IPFS Node logo + actions: + - theme: brand + text: Quick start + link: /guide/quick-start + - theme: alt + text: What it is + link: /guide/what-is-it + - theme: alt + text: Compare alternatives + link: /guide/comparison + - theme: alt + text: View on GitHub + link: https://github.com/Adamant-im/ipfs-node + +features: + - title: REST API for files + details: Upload multipart files over HTTP and read them back by CID as a streamed attachment. Lifecycle status, upload policy, and storage metrics are readable without an API key; administrative routes require one. + link: /reference/api + linkText: API overview + - title: Controlled peer topology + details: Nodes talk over TCP with Noise and Yamux and run only identify and ping. There is no DHT, no mDNS, no relay, and no public gateway routing, which reduces public exposure of content-routing metadata without making a deployment private, anonymous, trustless, or censorship-proof. + link: /guide/architecture + linkText: Architecture + - title: Bounded storage lifecycle + details: Scheduled garbage collection releases unconfirmed uploads once they outlive their TTL, and evicts down to the low watermark when the blockstore passes the high watermark or free space falls into the disk reserve. + link: /storage-lifecycle + linkText: Storage lifecycle + - title: Deterministic placement + details: Rendezvous hashing ranks holders for every CID, so every node derives the same holder set without negotiating it. Age-based tiers ask for fewer copies as a file gets older. + link: /storage-lifecycle + linkText: How copies are placed + - title: Repair and health checkpoints + details: A scheduled repair cycle restores missing copies in bounded passes. The health endpoint reports a monotonic checkpoint height that advances only when every prerequisite passes, including peer attestations when they are required. + link: /operations/monitoring + linkText: Monitoring and health + - title: Rate limits and admission control + details: Per-endpoint address-based rate limits cover upload, pin, and read. Admission control bounds concurrent uploads and downloads, keeps a per-client download share, and refuses a request before any block is written. + link: /guide/security + linkText: Security and privacy + - title: Container and source distribution + details: Run the node as a container with one data volume and a mounted JSON5 configuration file, or build and run it from source on Node.js 24 under a process manager. + link: /guide/docker + linkText: Run with Docker +--- + +## Who it is for + +- Applications that upload user files and fetch them back by CID over HTTP +- Developers who want a REST API instead of embedding an IPFS client library in every client +- Service operators who need bounded disk usage, explicit replication, and an operational health signal +- Self-hosters who want file storage on their own hardware, under their own configuration and peer list + +## Where it is not the right tool + +- Publishing content to the public IPFS network. The node runs no DHT, no public gateway, and no IPNS +- Fetching arbitrary CIDs from the public network. Retrieval is limited to the configured peer set +- Orchestrating an existing Kubo fleet. This is a standalone Helia service, not a Kubo API and not a cluster controller +- Anonymity or censorship resistance. A controlled topology reduces public exposure of content-routing metadata; it does not make a deployment private, anonymous, trustless, or censorship-proof + +See [Comparison](/guide/comparison) for how the node relates to Kubo, IPFS Cluster, and object storage. + +## Adopters + +ADAMANT Messenger runs this node in production for attachment delivery, and its deployment is the +reference for the defaults shipped in the repository. The node itself is a general-purpose service: +the messenger is one adopter, not the reason the project exists. See +[ADAMANT Messenger](/guide/adamant-messenger) for what that deployment configures and why. + +## Quick links + +- [Quick start](/guide/quick-start) — get a node answering requests +- [Docker](/guide/docker) — container image, volume, and configuration mount +- [Configuration](/guide/configuration) — every option, default, and validation rule +- [API overview](/reference/api) — endpoint classes, authentication, and status codes +- [Storage lifecycle](/storage-lifecycle) — admission, garbage collection, replication, and repair +- [Monitoring and health](/operations/monitoring) — health states, checkpoint height, and metrics diff --git a/docs/operations/monitoring.md b/docs/operations/monitoring.md new file mode 100644 index 0000000..b7ab019 --- /dev/null +++ b/docs/operations/monitoring.md @@ -0,0 +1,185 @@ +--- +title: Monitoring and health +description: Health states, checkpoint semantics, storage metrics, HTTP counters, and what to alert on when running the node. +--- + +# Monitoring and health + +The node exposes a readiness contract, a storage report, and structured logs. This page describes +what each one means and which signals are worth an alert. + +## What to monitor + +| Signal | Source | Why | +| ----------------- | -------------------------------------------------- | ---------------------------------------------------------- | +| Node state | `GET /api/node/health` | Whether the node should receive traffic | +| Checkpoint age | `GET /api/node/health` | Whether the readiness evidence is current | +| Disk reserve | `GET /api/node/health`, `GET /api/storage/metrics` | Uploads are refused with `507` once the reserve is at risk | +| Repair backlog | `GET /api/node/health` | Durability shortfalls that have not been repaired | +| Limiter occupancy | `GET /api/node/details` | Distinguishes a capacity `429` from a rate-limit `429` | +| HTTP counters | `GET /api/node/details` | Error rates and aborted responses | +| Logs | stdout | Startup warnings, job failures, protocol errors | + +## Health states + +`GET /api/node/health` always answers HTTP `200`. A monitor that only checks the status code will +call a broken node healthy. + +| State | Meaning | What to do | +| ---------- | -------------------------------------------------------------------------- | -------------------------------------------------- | +| `starting` | Startup reconciliation has not finished | Wait; alert only if it persists far past startup | +| `ready` | Every prerequisite passed in the last checkpoint | Serve traffic | +| `degraded` | A prerequisite currently fails, but the last checkpoint is not yet expired | Investigate `checks`; the node still answers reads | +| `stale` | The last checkpoint is older than `health.maxCheckpointAgeMs` | Treat the node's readiness evidence as unusable | + +Recovery is never decided on a read. A node returns to `ready` only after a successful checkpoint, +so expect up to `health.checkpointIntervalMs` of lag once the underlying fault clears. + +## Reading a health response + +```json +{ + "version": "0.1.0", + "uptimeMs": 123456, + "state": "ready", + "height": 1720614960000, + "timestamp": 1720614998797, + "evaluatedAt": 1720614998700, + "checkpoint": { + "intervalMs": 60000, + "observedAt": 1720614998700, + "ageMs": 97, + "maxAgeMs": 180000 + }, + "membership": { + "version": "d0f1...", + "requiredPeers": 1, + "attestedPeers": 2 + }, + "startup": { "complete": true, "healthy": true }, + "storage": { + "measuredAt": 1720614980000, + "measurementAgeMs": 18797, + "reserveHealthy": true + }, + "replication": { + "repairRequired": true, + "lastCompleteAt": 1720614900000, + "ageMs": 98797, + "backlog": 0 + }, + "checks": { + "checkpointFresh": true, + "clockConsistent": true, + "helia": true, + "startupReconciliation": true, + "storageFresh": true, + "storageReserve": true, + "repairFresh": true, + "peerAttestations": true + } +} +``` + +Field by field: + +- `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It + advances only when every prerequisite passes and freezes on failure +- `timestamp` is when this response was produced; `evaluatedAt` is when the checkpoint attempt + behind it ran, which may have been a failed attempt +- `checkpoint.observedAt` dates the last attempt that succeeded, so it differs from `evaluatedAt` + whenever the most recent attempt failed +- `membership.version` is a 64-character digest of the configured peer set + +The `checks` object splits into two kinds. `checkpointFresh`, `storageFresh`, and `repairFresh` are +recomputed at read time, because elapsed time alone can invalidate them. Every other check, along +with `membership` and `startup`, describes `evaluatedAt`. + +`repairFresh` is automatically true when repair is not required — that is, when +`replication.enabled` or `replication.repairEnabled` is false. When repair is required, the check +demands a complete, healthy cycle with no backlog, completed within `health.repairMaxAgeMs`. + +A clock that moves behind the checkpoint this node already recorded clears `clockConsistent` and +stops advancement until it catches up, rather than persisting a round that starts after it finished. + +## Alerting rules + +Express thresholds in terms of the configured values, not in absolute seconds. + +- `state` is not `ready` for longer than a small multiple of `health.checkpointIntervalMs` +- `checkpoint.ageMs` exceeds `checkpoint.maxAgeMs`, which is the definition of `stale` +- `checks.storageReserve` is false: free space has fallen into `storage.diskReserveBytes`, and + uploads answer `507` +- `checks.clockConsistent` is false: the host clock moved backwards +- `replication.backlog` is non-zero across more than one repair cycle +- `replication.ageMs` approaches `health.repairMaxAgeMs`, which means a cycle is not completing + inside its window +- `membership.attestedPeers` below `membership.requiredPeers` +- `membership.version` changed without a configuration change being deployed +- `height` frozen while `state` is `ready` on other nodes of the same membership version + +Compare `height` only between nodes reporting the same `membership.version`. Peers may attest an +adjacent round across a boundary, so two healthy nodes can briefly report heights one +`checkpointIntervalMs` apart; that is normal and not an alert. + +## Storage metrics + +`GET /api/storage/metrics` is public and reports: + +- `pinnedBytes` — content protected by a pin, estimated from the full DAG of each registered file +- `reclaimableBytes` — blockstore bytes that no pin protects +- `availableBytes` — free space on the blockstore filesystem +- `reservedBytes` and `usableBytes` — the disk reserve and what is left for uploads +- `files` — how many files are in each lifecycle state +- `stagedReplicas` — registry entries still belonging to an in-flight strict upload +- the garbage collection and replication job state, including the last run and its errors +- `replication.protocol` — the libp2p protocol version this node offers + +Values refresh on the `diskUsageScanPeriod` schedule, because a directory scan and a full registry +sweep are too expensive for a request path. A metric that looks stale is usually as fresh as that +schedule allows. + +Watch `gc.lastRun.errors` after each collection. + +## HTTP and concurrency counters + +`GET /api/node/details` adds two objects behind the administrative key. + +`http` holds counters accumulated since process start — not a sliding window: `requests`, the +`responses` split by status family, `aborted` (responses whose connection closed before the body +finished, counted apart from the status families), `inFlight`, and response-time totals. Compute +rates by differencing successive samples. + +`concurrency` holds admission-limiter occupancy for uploads, incoming copies, and downloads, each +with `active` and `limit`, plus the per-client download limit. A rate-limit refusal and an admission +refusal are both `429` and need opposite reactions — one means a client is asking too often, the +other means the node has run out of slots — so this is the field that tells them apart. + +## Logs + +Logs are newline-delimited JSON from Pino on stdout. `logLevel` accepts `fatal`, `error`, `warn`, +`info`, `debug`, `trace`, and `silent`. Keep `prettyLogs: false` outside an interactive terminal so +a collector keeps the structured fields. + +Records carry no user content. Request logs record the method, a masked route shape, and the status +code; `x-api-key`, `authorization`, and `cookie` are redacted; and application messages pass a +scrubber that replaces content identifiers, peer ids, multiaddrs, and stack traces with fixed +placeholders. + +Two warnings are expected on a fresh installation and are not faults: the TLS notice at listen time, +and the `trustProxy is false` notice. Do not widen the proxy trust rule merely to silence the +second; see [Security and privacy](/guide/security). + +## Staged rollouts and membership epochs + +During a staged deployment older peers do not implement the health protocol. Set +`health.requiredPeerCount` to `0` on the transitioning fleet, then raise it once every required peer +is upgraded; otherwise prolonged `degraded` health is the expected fail-safe result. + +While it is `0`, `checks.peerAttestations` is always true and the checkpoint proves only this node's +own prerequisites. Network coverage is not validated in that window, so treat routing decisions that +depend on it as unverified. + +Changing `nodes` changes the membership epoch and resets the persisted checkpoint, so `height` +returns to `0` on every node for the new epoch. Plan for that when a monitor alerts on height +regressions. The upgrade sequence is in [Upgrades and rollback](/operations/upgrades). diff --git a/docs/operations/persistence.md b/docs/operations/persistence.md new file mode 100644 index 0000000..96467ed --- /dev/null +++ b/docs/operations/persistence.md @@ -0,0 +1,130 @@ +--- +title: Persistent state and backups +description: What the node stores, where it lives, and how to back up, restore, and move it without losing peer identity or content. +--- + +# Persistent state and backups + +Everything durable the node owns lives in one directory. Treat it as a single consistency unit: +peer identity, pins, lifecycle registry, repair evidence, and the health checkpoint describe each +other, and restoring a subset of them produces a node that misreports its own state. + +## What is persistent + +| State | Stored in | Losing it costs | +| -------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------- | +| Content blocks | `blockstore/` | The bytes, unless a peer still holds them | +| libp2p private key | `datastore/` at `/pkcs8/self` | The peer identity; every other operator's `nodes` entry stops matching | +| Helia pin set | `datastore/` | Protection from garbage collection | +| File lifecycle registry | `datastore/` under `/adm/files` | Storage reporting, dry runs, and repair coverage for those files | +| Pin-intent markers | `datastore/` under `/adm/pin-intent` | The ability to tell a crashed upload from legacy content | +| Health checkpoint | `datastore/` at `/adm/health/checkpoint` | Checkpoint height; the node restarts the epoch from `0` | +| Repair cursor and cycle evidence | `datastore/` at `/adm/health/repair-cycle` | Repair progress; the next cycle starts over and publishes no coverage | + +## Where it lives + +The store path is `$HOME/`, where `storeFolder` comes from the configuration file and +the home directory is that of the process user. With the shipped `storeFolder: '.adm-ipfs'` a node +running as `ipfs-node` with `HOME=/var/lib/ipfs-node` keeps its data in +`/var/lib/ipfs-node/.adm-ipfs`, holding `blockstore/` and `datastore/`. Both are created at startup +if they are missing. + +Because the path is derived from `HOME`, a service manager that changes the environment changes +where the data goes. Pin `HOME` explicitly in the unit or the container rather than relying on a +default; see [Installation](/guide/installation) and [Docker](/guide/docker). + +An optional absolute `dataDir` that would decouple the store from `HOME` is open work in [issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) +and is not implemented. + +## Backing up + +Back up the whole store directory as one unit. + +```bash +sudo systemctl stop ipfs-node +sudo tar --create --gzip \ + --file /backup/ipfs-node-$(date +%F).tar.gz \ + --directory /var/lib/ipfs-node .adm-ipfs +sudo systemctl start ipfs-node +``` + +Points worth planning for: + +- A stopped node gives a consistent copy. A filesystem or volume snapshot of a running node is + crash-consistent, which the stores tolerate, but a `tar` of a live directory can capture a + half-written record +- A datastore-only backup preserves what is pinned and what the registry knows, but not the blocks. + It is useful, and it is not a restore +- The backup contains the libp2p private key. Protect it like any other secret +- The configuration file holding `adminApiKey` lives outside the store directory; back it up + separately and with the same care + +## Restoring + +```bash +sudo systemctl stop ipfs-node +sudo rm -rf /var/lib/ipfs-node/.adm-ipfs +sudo tar --extract --gzip \ + --file /backup/ipfs-node-2026-09-04.tar.gz \ + --directory /var/lib/ipfs-node +sudo chown -R ipfs-node:ipfs-node /var/lib/ipfs-node/.adm-ipfs +sudo systemctl start ipfs-node +``` + +Verify afterwards: + +```bash +curl -s -H "x-api-key: $KEY" http://127.0.0.1:4000/api/node/details # peerId unchanged +curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4000/api/file/ +curl -s http://127.0.0.1:4000/api/node/health # state and height +``` + +Pins and lifecycle records return to the state of the backup. Blocks deleted since then have to be +re-fetched from a peer that still holds them. + +## Moving a node to another host or volume + +1. Stop the node. +2. Copy the whole store directory, preserving ownership and permissions + (`rsync -aHAX`, or `tar` as above). +3. Make sure the process user on the target resolves to the same `HOME`, and that + `storeFolder` is unchanged. The resolved path must be the same, or the node will not find the + data. +4. Fix ownership on the target so the process user owns every file. +5. Start the node and confirm the `peerId` is unchanged and a known CID still downloads. +6. Only then decommission the source. + +Starting against an empty directory is the failure mode to guard against: the node comes up +cleanly, generates a new peer identity, and reports every existing CID as unretrievable — a `408`, +not a `404`. Nothing warns about it, because a first start looks exactly the same. + +To roll back, stop the new node, start the old one against its untouched directory, and revert any +`nodes` entry that was changed. Keep the source intact until the target has been verified. + +## Losing the datastore + +| Situation | Recoverable | +| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Datastore lost, blockstore intact | The blocks are still on disk but unpinned and unregistered, so the collector may reclaim them. The node has a new peer identity | +| Blockstore lost, datastore intact | Pins and records survive. Re-pin each CID with `POST /api/helia/pin/:cid` while a peer that holds it is connected, and the DAG is pulled back over libp2p | +| Both lost, peers still hold the content | Restore from a peer by re-pinning, after distributing the node's new `multiAddr` to every other operator | +| Both lost, no peer holds the content | Not recoverable. Only re-uploading the original file restores it | + +After any identity change, every other node's `nodes` entry for this node is wrong: replication and +health attestations will not reach it until they are updated. + +## Containers + +The image pins `HOME=/data` and declares `/data` as a volume, so one volume holds the whole store — +`/data/.adm-ipfs` with the default `storeFolder`. + +- A named volume is initialized from the image, where `/data` is owned by uid 1000, so ownership is + correct with no extra step +- A host bind mount is not. Create the directory and `chown -R 1000:1000` it before the first start, + or the process cannot write its stores +- Replacing the container while keeping the volume preserves identity, pins, and content. This is + verified by `scripts/docker-smoke-test.sh` +- Back up a named volume by running a throwaway container that mounts it read-only and writes an + archive to a bind-mounted directory + +Losing the volume is the container form of losing the store directory, with the same consequences. diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md new file mode 100644 index 0000000..9ba771e --- /dev/null +++ b/docs/operations/troubleshooting.md @@ -0,0 +1,181 @@ +--- +title: Troubleshooting +description: Symptom-driven diagnosis for startup failures, health that never reaches ready, rejected uploads, download timeouts, replication, and container problems. +--- + +# Troubleshooting + +Each entry names a symptom, the likely cause, and the check that confirms it. + +## The process will not start + +**A message like `Invalid config: "storage.gc.lowWatermarkBytes" must be lower than storage.gc.highWatermarkBytes`.** +Configuration validation aborts the process and names the failing field. Fix that field. The full +list of rules is in [Configuration](/guide/configuration). + +**`Cannot read config file /app/config.json5` or the same path outside a container.** +The file is read from the repository root, located by walking up from the compiled entry point to +the nearest `package.json`. Confirm the file exists at that path, and that `IPFS_NODE_CONFIG` or the +first CLI argument names the suffix you intended: both select `config..json5`. + +**`Cannot parse config file ...`.** +The file is JSON5, so comments and trailing commas are allowed, but the message names the parse +position. A common cause is an unterminated string or a missing brace after hand-editing. + +**`adminApiKey must be an empty value or a unique secret of at least 32 characters`.** +The key is shorter than 32 characters, or it is one of the known placeholder values the validator +refuses. Generate a real one with `openssl rand -hex 32`. Leaving it empty is valid and makes +administrative routes answer `503`. + +**`health.requiredPeerCount cannot exceed the number of configured remote peers`.** +The bound is `nodes.length - 1`. A single-node deployment must use `0`. Removing a peer from `nodes` +may require lowering this value in the same change. + +**`nodes[N].multiAddr must contain a /p2p/ component`, or a duplicate-peer message.** +Every entry needs a full multiaddr ending in `/p2p/`, and no two entries may resolve to the +same peer id. + +**`cors.allowedOrigins must be a non-empty array`.** +The key is required. A configuration migrated from `cors.origin` or `cors.originRegexps` fails here +until it is rewritten; see the migration section of [Configuration](/guide/configuration). + +## Health never reaches ready + +Read `checks` in `GET /api/node/health`; the failing entry names the cause. + +| Failing check | Cause | Check | +| ----------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `startupReconciliation` | Registry backfill or admission recovery reported errors | Look for `Registry backfill:` or `Admission recovery:` warnings in the log | +| `storageFresh` | No storage measurement inside `health.storageMaxAgeMs` | Confirm `diskUsageScanPeriod` is more frequent than that window and that the scan is not erroring | +| `storageReserve` | Free space has fallen into `storage.diskReserveBytes` | `GET /api/storage/metrics`; free space or lower the reserve deliberately | +| `repairFresh` | No complete, healthy repair cycle inside `health.repairMaxAgeMs`, or a non-zero backlog | `GET /api/node/health` `replication.backlog` and `replication.ageMs`; raise `health.repairMaxAgeMs` above the largest expected cycle duration plus the schedule interval | +| `peerAttestations` | Fewer peers attested than `health.requiredPeerCount` | Confirm the peers are connected, upgraded, and share the same `membership.version` | +| `clockConsistent` | The host clock moved behind a recorded observation | Check NTP; advancement resumes once the clock catches up | +| `helia` | The Helia node is not started | Read the startup log | + +**The state is `ready` but `height` stays `0`.** +The membership epoch changed. Changing `nodes` resets the persisted checkpoint, so height restarts +from `0` for the new epoch. Compare heights only between nodes reporting the same +`membership.version`. + +**Two healthy nodes report heights one interval apart.** +Expected at a round boundary: peers may attest an adjacent round. + +**A node was upgraded ahead of its peers and stays `degraded`.** +That is the fail-safe result. Set `health.requiredPeerCount: 0` on the transitioning fleet and raise +it once every required peer is upgraded; see [Upgrades and rollback](/operations/upgrades). + +## Uploads are rejected + +| Status | Cause | Action | +| ------ | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `400` | No file was sent, more files than `maxFileCount`, one file over `uploadLimitSizeBytes`, or a non-file text field | Send `files` parts only, within both limits | +| `413` | Combined size over `storage.maxRequestSizeBytes` | Raise the limit or split the request; the check runs against `Content-Length` before parsing | +| `429` | Upload rate limit, or the concurrent upload limit | Compare `concurrency.uploads` on `GET /api/node/details` against the limit: a full limiter is capacity, an empty one is rate | +| `503` | `replication.requireQuorumOnUpload` is on and the quorum was not reached | Check peer connectivity; the upload was rolled back cleanly | +| `507` | The request would consume `storage.diskReserveBytes` | Free space, lower the reserve, or collect garbage with `POST /api/storage/gc?force=true` | +| `500` | Storage or replica settlement failed | Not a clean rejection. The content may be partially durable; re-uploading is safe because content-addressed dedup absorbs it | + +**A proxy rejects the upload before the node sees it.** +Set the proxy body limit at or below `uploadLimitSizeBytes` and disable request buffering for the +upload path. An nginx example is in [Installation](/guide/installation). + +## Downloads time out + +`408` is the only timeout answer, and three separate deadlines can produce it: discovery bounded by +`findFileTimeout`, a stalled transfer bounded by `downloadIdleTimeout`, and a size-aware complete +deadline derived from `downloadMinBytesPerSecond` and capped by `downloadMaxDurationMs`. A failing +request may spend more than one `findFileTimeout` before answering. + +Likely causes: + +- No connected peer holds the content. Check `GET /api/libp2p/peers` and the peering log. Bootstrap + dials once, so `peeringSchedule` is what keeps the mesh together after a restart +- The CID exists only on the public IPFS network. This node registers no content routing and will + never find it +- The content was released and its blocks were reclaimed on every holder + +The node never answers `404` for a CID it cannot retrieve, because it cannot know whether the +content exists elsewhere. A `404` on this API means an unknown registry record or an unrouted path. + +## Replication never places copies + +- `replication.enabled` is `false`. The node then stores content best effort, one local copy, and + `GET /api/storage/policy` reports `"mode": "best-effort"` +- The peers are not connected. Copies travel over libp2p, so the connection has to exist first +- The peers run a different replication protocol version. Compare `replication.protocol` from + `GET /api/storage/metrics` on each node; nodes on different versions cannot place copies on each + other +- This node is not listed in the peers' `nodes`. Durable placement is accepted only from configured + peers, so an unlisted node is offered the weaker unpinned `cache` operation instead and its + content stays in one durable copy +- The network is no larger than the desired copy count. `networkTooSmall` in the upload report says + so: asking for four copies on a three-node network places three, and the fourth is not looked for + +## Rate limiting behaves unexpectedly + +**Every client shares one bucket.** +`trustProxy` is `false` behind a reverse proxy, so Express uses the socket address, which is the +proxy. The process logs a warning about this at startup. Configure the exact proxy addresses or +CIDRs. Do not set `true` — it is rejected, because a client could then spoof `X-Forwarded-For`. + +**The configured limit seems higher than expected.** +Limiter state is in memory per process. A restart clears it, and a second process or host keeps its +own counters, so the effective limit is per process. Enforce rates at the proxy as well for any +multi-process deployment. + +**A `429` with no obvious rate abuse.** +It may be admission, not rate. `GET /api/node/details` reports limiter occupancy under +`concurrency`; an admission refusal also carries `Retry-After: 5`. + +## Disk fills up + +Garbage collection frees blocks only under pressure: when the blockstore passes +`storage.gc.highWatermarkBytes`, or when free space falls into `storage.diskReserveBytes`. Below +both thresholds it reclaims nothing, by design. + +- Confirmed files this node holds are never selected. If the disk is full of confirmed content, the + answer is more disk or a different placement policy, not the collector +- `POST /api/storage/gc?dryRun=true` reports exactly what would be released and retained without + touching a block +- `POST /api/storage/gc?force=true` collects even below the high watermark +- `storage.gc.enabled: false` disables only the scheduled collector; the endpoint still works +- On a disk smaller than the configured watermark the ceiling is never reached, and the disk-reserve + trigger is what protects the volume. Check that `diskReserveBytes` is realistic for the volume + +Watch `gc.lastRun.errors` in `GET /api/storage/metrics` after each run. + +## The peer identity changed + +The node started against an empty store directory. It generated a new libp2p private key, and every +other operator's `nodes` entry for it no longer matches, so replication and health attestations do +not reach it. + +Check the resolved path: it is `$HOME/` for the process user, so a changed `HOME`, a +changed service user, or a missing volume all produce this. Stop the node, restore or re-mount the +original directory, and start again. See +[Persistent state and backups](/operations/persistence). + +## Container-specific problems + +**The container exits immediately.** +Almost always a configuration error. `docker logs` carries the validation message. Confirm the file +is mounted at `/app/config.json5`; the image ships none. + +**`EACCES` writing the store.** +A host bind mount whose directory is not owned by uid 1000. `chown -R 1000:1000` it before the first +start. A named volume is initialized from the image and needs no such step. + +**A new peer identity after recreating the container.** +The `/data` volume was not reused. `HOME` is `/data` in the image, so the store is +`/data/`; without that volume every start is a first start. + +**The container is marked unhealthy while starting.** +The bundled check treats only `ready` as healthy, and the start period covers startup +reconciliation, the first disk scan, and the first checkpoint. Widen with +`IPFS_NODE_HEALTHCHECK_STATES` only when a deployment deliberately accepts a node whose +prerequisites currently fail. + +**Shutdown is not graceful.** +The stop timeout is shorter than the 15 s internal deadline. Use `--stop-timeout 20` or +`stop_grace_period: 20s`. See [Docker](/guide/docker). diff --git a/docs/operations/upgrades.md b/docs/operations/upgrades.md new file mode 100644 index 0000000..8a39267 --- /dev/null +++ b/docs/operations/upgrades.md @@ -0,0 +1,157 @@ +--- +title: Upgrades and rollback +description: How to stop the node cleanly, upgrade a source or container deployment, roll a fleet, and roll back safely. +--- + +# Upgrades and rollback + +An upgrade touches persistent state, so the order matters. Read +[Persistent state and backups](/operations/persistence) first; a current backup is the only +rollback that always works. + +## Before upgrading + +- Take a backup of the store directory, or a snapshot of the data volume +- Read the release notes for the target version +- Run `POST /api/storage/gc?dryRun=true` and confirm that every CID which must survive appears in + `retainedCids`, and that `repairedPins` and `unprotected` are empty +- Note the current `peerId` and `membership.version`, so a change afterwards is visible +- Plan capacity: the first start after an upgrade can copy a large amount of historical content + between nodes, as described below + +## Graceful shutdown + +`SIGINT` and `SIGTERM` are each handled once. On receipt the node: + +1. Logs `Received SIGTERM, shutting down` +2. Stops the disk-usage, peering, garbage-collection, and admission-recovery jobs +3. Begins stopping replication repair and the health service +4. Arms two timers: idle HTTP connections are force-closed at 12 s, and a hard deadline fires at 15 s +5. Closes the HTTP server, letting in-flight requests finish +6. Waits for the background jobs, stops Helia, and exits with code `0` + +If the hard deadline fires first, the node logs `Shutdown timed out` and exits with code `1`. + +Any supervisor or container stop timeout must therefore outlast 15 s. Give it at least 20 s: +`TimeoutStopSec=30` in systemd, `--stop-timeout 20` for `docker run`, `stop_grace_period: 20s` in +Compose. A shorter budget risks `SIGKILL` while persistent state is still being written. + +## Upgrading from source + +```bash +sudo systemctl stop ipfs-node +git -C /opt/ipfs-node fetch --tags +git -C /opt/ipfs-node checkout +cd /opt/ipfs-node +nvm use +npm ci +npm run build +sudo systemctl start ipfs-node +``` + +`npm ci` must run install scripts: the WebRTC transport package pulls a native module whose prebuilt +binary is downloaded during installation, and a tree installed with `--ignore-scripts` fails at +startup. + +Review the configuration file against [Configuration](/guide/configuration) before starting. +Unknown keys are ignored, so a removed option does not fail the start — it simply stops doing +anything. + +## Upgrading a container + +```bash +docker pull ghcr.io/adamant-im/ipfs-node: +docker stop --timeout 20 ipfs-node +docker rm ipfs-node +docker run -d --name ipfs-node ... ghcr.io/adamant-im/ipfs-node: +``` + +Keep the same `/data` volume and the same mounted configuration file. The container is replaceable; +the volume is the node. Pin an immutable version tag rather than tracking `latest`, so a rollback is +a matter of naming the previous tag. + +With Compose, change the image reference and run `docker compose up -d`. + +## The first start after an upgrade + +Startup walks the pin set once and records anything the registry does not know as `confirmed`. The +walk is idempotent, and a pin whose DAG is not fully local is skipped rather than recorded. + +It does not block the API. Reads, uploads, and incoming copies do not need a complete registry; the +collector and the repair sweep start only once the walk finishes, so neither acts on a half-built +picture. + +Two consequences are worth planning for: + +- The original upload time is not recoverable, so a backfilled record is dated at the backfill. + Every legacy file therefore counts as **fresh**, which is the widest placement tier: repair will + place it on as many nodes as that tier asks for. On a small node list that means historical + content is eventually copied to effectively every node. This is the durability guarantee arriving + for content that never had it, but it is real disk and real transfer on every node +- Repair works through a bounded, advancing batch per pass, so the copying is gradual. It is still + the largest transfer the upgrade causes + +The same startup phase clears admission tokens left by a request that can no longer resume. + +## Rolling a fleet + +The replication protocol offers only its current version, and libp2p negotiates the newest version +both ends support. With one entry in the list, nodes on different versions cannot place copies on +each other, so a partially upgraded fleet has reduced durability until it is finished. + +`GET /api/storage/metrics` reports the protocol each node offers under `replication.protocol`, which +is what makes a half-upgraded network visible rather than silent. + +Sequence: + +1. Set `health.requiredPeerCount: 0` on the nodes being upgraded first, so a node ahead of its peers + does not freeze its checkpoint waiting for attestations it cannot get +2. Upgrade one node at a time, confirming `state: ready` and an unchanged `peerId` before moving on +3. Once every node is upgraded, raise `health.requiredPeerCount` again and restart +4. Confirm that heights advance on every node and that `membership.version` matches across the fleet + +While `requiredPeerCount` is `0`, `checks.peerAttestations` is always true and network coverage is +unverified. Treat that window as unverified for routing decisions that depend on it. + +Changing the `nodes` list at the same time compounds the problem: it changes the membership epoch +and resets every persisted checkpoint, so `height` returns to `0` fleet-wide. Do membership changes +and version upgrades in separate steps. + +## Rolling back + +In increasing order of disruption: + +- **Disable scheduled collection.** Set `storage.gc.enabled: false` and restart. The scheduled + collector stops; nothing else changes +- **Undo a release before collection runs.** A released file is unpinned but its blocks are still on + disk. `POST /api/helia/pin/:cid` restores the pin and registers the file as confirmed again +- **Roll back the software.** Check out the previous tag and rebuild, or run the previous immutable + container tag against the same volume. Downgrading is safe only when the persistent formats did + not change in the version being left; the release notes are the authority +- **Recover content already deleted.** Re-pin the CID with `POST /api/helia/pin/:cid` while a peer + that still holds it is connected, and the DAG is pulled back over libp2p. Verify with + `GET /api/file/:cid` +- **Restore the store backup.** Stop the node, replace the directory, start it again. Pins and + lifecycle records return to the state of the backup; blocks deleted since then still have to be + re-fetched + +## Version and tag alignment + +One number identifies a release everywhere: + +- `version` in `package.json` +- the Git tag `vX.Y.Z` +- the GitHub Release +- the container tag `ghcr.io/adamant-im/ipfs-node:X.Y.Z` + +The publish workflow refuses a release whose tag does not match `package.json`, and refuses a tag +that is not an ancestor of the `dev` branch. Every release publishes its immutable version tag; a +stable release also moves `latest`; a prerelease publishes its version tag only, so `latest` never +points at one. + +The documentation site is built from the default branch and from published releases, so it tracks +the same source. + +The project is pre-1.0, and no release has been published yet. Take the exact version to pin from +the [releases page](https://github.com/Adamant-im/ipfs-node/releases) rather than from an example +in this documentation. diff --git a/docs/public/CNAME b/docs/public/CNAME new file mode 100644 index 0000000..19ef5b7 --- /dev/null +++ b/docs/public/CNAME @@ -0,0 +1 @@ +ipfs-node.docs.adamant.im diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 0000000..5e597ca --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/public/logo.svg b/docs/public/logo.svg new file mode 100644 index 0000000..318c539 --- /dev/null +++ b/docs/public/logo.svg @@ -0,0 +1,13 @@ + + ADAMANT IPFS Node + + + + + + + + + + + diff --git a/docs/reference/api.md b/docs/reference/api.md new file mode 100644 index 0000000..97b37ba --- /dev/null +++ b/docs/reference/api.md @@ -0,0 +1,307 @@ +--- +title: API overview +description: The REST contract of the node — authentication, worked examples, status codes, and where the machine-readable specification lives. +--- + +# API overview + +The stable client, lifecycle, and node-health contract is described here with worked examples. The +generated per-operation tables are on [Endpoint reference](/reference/endpoints), and the +machine-readable document is [`openapi.yaml`](/openapi.yaml). + +Examples use `https://ipfs.example.org`, which stands for a reverse proxy in front of the node. The +process itself serves plain HTTP; see [Security and privacy](/guide/security). + +## Contract and versioning + +`docs/openapi.yaml` covers the routes an application and an operator depend on. Low-level Helia and +libp2p routes and the optional debug route are deliberately outside it — they are operator tools +whose shape may change with the runtime. + +The `version` field in `GET /api/node/health`, `GET /api/node/info`, and `GET /api/node/details` +is the `version` from `package.json`. It is the software version, not the contract version, and it +is unrelated to the libp2p replication protocol version reported by +`GET /api/storage/metrics`. + +## Authentication + +There is one credential: the administrative key, sent as the `x-api-key` header and compared in +constant time against `adminApiKey`. + +| Class | Routes | Requirement | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| Public | `GET /`, `GET /api/node/health`, `GET /api/node/info` | None | +| Public file transfer | `POST /api/file/upload`, `GET /api/file/:cid` | None; rate, size, and admission limits apply | +| Public storage state | `GET /api/file/:cid/status`, `GET /api/storage/metrics`, `GET /api/storage/policy` | None | +| Administrative | `GET /api/node/details`, `POST /api/file/:cid/confirm`, `POST /api/file/:cid/unpin`, `POST /api/storage/gc`, `POST /api/storage/repair`, all `/api/helia/*`, all `/api/libp2p/*` | `x-api-key` | +| Disabled by default | `GET /api/debug/autopeering` | `enableDebugApi: true` and `x-api-key` | + +A missing or wrong key answers `401`. An unset `adminApiKey` answers `503 Service not configured`, +so administrative routes fail closed rather than open. There is no end-user identity or session +layer. + +`GET /` answers with the plain text `IPFS node`. + +## Upload a file + +Send one or more multipart parts named `files`. Text fields are rejected with `400`. + +```bash +curl --fail-with-body \ + --form 'files=@file.txt' \ + https://ipfs.example.org/api/file/upload +``` + +```json +{ + "filesNames": ["file.txt"], + "cids": ["bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm"], + "files": [ + { + "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", + "name": "file.txt", + "state": "confirmed", + "expiresAt": null, + "replication": { + "mode": "quorum", + "desiredCopies": 4, + "copies": 3, + "required": 1, + "acknowledged": 1, + "replicaCount": 0, + "cachedCount": 0, + "satisfied": true, + "networkTooSmall": true, + "failedAttemptCount": 0, + "attempts": [] + } + } + ], + "replication": { + "mode": "quorum", + "desiredCopies": 4, + "copies": 3, + "required": 1, + "acknowledged": 1, + "replicaCount": 0, + "cachedCount": 0, + "satisfied": true, + "networkTooSmall": true, + "failedAttemptCount": 0, + "attempts": [] + } +} +``` + +Placement is decided per CID, so each entry of `files` carries its own report. The top-level +`replication` object repeats the first file's report for clients written against the earlier shape; +new clients should read the per-file field. + +The report is counts and per-attempt outcomes only. It never carries node names, peer ids, or peer +error text. + +| Status | Meaning | +| ------ | ----------------------------------------------------------------------------- | +| `200` | Every file was stored and pinned | +| `400` | No file was sent, too many files, or one file exceeded `uploadLimitSizeBytes` | +| `413` | The combined size exceeded `storage.maxRequestSizeBytes` | +| `429` | The upload rate limit or the concurrent upload limit was exceeded | +| `503` | A required replication quorum could not be reached | +| `507` | Storing the request would consume `storage.diskReserveBytes` | +| `500` | Storage or replica settlement failed; do not treat it as a clean rejection | + +An interrupted upload leaves no blocks behind: each request owns a session that removes exactly the +blocks it created. See [the storage lifecycle](/storage-lifecycle). + +## Download a file + +```bash +curl --fail-with-body \ + --output file.bin \ + https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm +``` + +Responses are the complete representation, served as `application/octet-stream` with +`Content-Disposition: attachment; filename=""` and `X-Content-Type-Options: nosniff`. A +successful response carries an `ETag` holding the quoted CID, +`Cache-Control: private, max-age=3600, must-revalidate`, and `Accept-Ranges: none`. A matching +`If-None-Match` answers `304` after availability has been checked. + +| Status | Meaning | +| ------ | ------------------------------------------------------------------------------------------ | +| `200` | The response contains the requested file as an attachment | +| `304` | The `If-None-Match` validator matched | +| `400` | The CID is invalid | +| `408` | The file could not be found or retrieved before the configured timeout | +| `429` | The read rate limit, the per-client, or the global download concurrency limit was exceeded | +| `500` | An unexpected internal failure occurred before streaming started | + +Range headers are ignored. Discovery, idle transfer time, and the size-aware complete transfer all +have bounded deadlines and all end in `408 File request timed out`, so a failed request may spend +more than one `findFileTimeout` before answering. A client disconnect cancels the underlying +retrieval. If an error occurs after response bytes have started, the server terminates the +incomplete response, because a status and a JSON body can no longer be sent safely. + +The node never answers `404` for a CID it cannot retrieve: it cannot know whether the content exists +somewhere it cannot see. + +## File status + +```bash +curl --fail-with-body \ + https://ipfs.example.org/api/file/bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm/status +``` + +```json +{ + "cid": "bafkreif7v2d2wdyh6pz5y2pwmrpegfpdgh5u7n5vomxnbofraqhuk2wapm", + "state": "confirmed", + "pinned": true, + "createdAt": 1720614998797, + "expiresAt": null, + "confirmedAt": 1720614998797, + "replication": { "acknowledged": 1, "required": 1, "heldLocally": true } +} +``` + +`state` is `temporary`, `confirmed`, or `expired`. An unknown CID answers `404`. The response +carries no filename and no peer identity. + +## Storage metrics and policy + +```bash +curl --fail-with-body https://ipfs.example.org/api/storage/metrics +curl --fail-with-body https://ipfs.example.org/api/storage/policy +``` + +`metrics` reports pinned, reclaimable, available, and reserved bytes, the lifecycle counters, the +state of the background jobs, and the libp2p replication protocol this node speaks. Nodes on +different protocol versions cannot place copies on each other, so a mixed deployment is visible +here. Values refresh on `diskUsageScanPeriod`, not per request. + +`policy` reports the limits and the durability mode a client should expect before uploading: +`maxFileCount`, `uploadLimitSizeBytes`, `maxRequestSizeBytes`, the concurrency limits, +`confirmationRequired`, `temporaryTtlMs`, and a `durability` object holding `mode` +(`quorum` or `best-effort`), `placement`, and `ackQuorum`. + +Both routes are public and carry no filenames, CID lists, or peer identity. + +## Node health + +```bash +curl --fail-with-body https://ipfs.example.org/api/node/health +``` + +This route always returns HTTP `200`. Consumers must read `state`, which is `starting`, `ready`, +`stale`, or `degraded`. + +`height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It +advances only when startup reconciliation, storage freshness and reserve, a complete successful +repair cycle with no known backlog, and the configured peer attestations all pass, and it freezes on +failure. + +State changes are asymmetric. The response is served from the last checkpoint, and reading it +recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` +between checkpoints. Returning to `ready` requires a successful checkpoint, so expect up to +`health.checkpointIntervalMs` of lag after the underlying fault clears. + +`evaluatedAt` dates the last checkpoint attempt, which may have failed. `checkpoint.observedAt` +dates the last attempt that succeeded. `checks.checkpointFresh`, `checks.storageFresh`, and +`checks.repairFresh` are recomputed at read time; every other entry in `checks`, and `membership` +and `startup`, describe `evaluatedAt`. + +`membership.version` identifies the configured peer-set epoch. Changing the node list resets the +persisted checkpoint for that node, so `height` stays `0` until the first valid checkpoint under the +new membership. Compare heights only between nodes reporting the same membership version, and treat +a version change as a new epoch. + +A full annotated example and the alerting rules are in +[Monitoring and health](/operations/monitoring). + +## Legacy node information + +```bash +curl --fail-with-body https://ipfs.example.org/api/node/info +``` + +This public compatibility route retains `version`, `timestamp`, `heliaStatus`, `blockstoreSizeMb`, +`datastoreSizeMb`, and `availableSizeInMb` for the current PWA and iOS application. It exposes no +peer identity and no topology. + +## Administrative node details + +```bash +curl --fail-with-body \ + --header 'x-api-key: your-generated-key' \ + https://ipfs.example.org/api/node/details +``` + +The authenticated response adds `peerId`, `multiAddresses`, byte-accurate storage figures +(`pinnedBytes`, `reclaimableBytes`, `availableBytes`, `reservedBytes`), the complete `health` +object, bounded HTTP counters under `http`, and admission-limiter occupancy under `concurrency`. +Counters accumulate since process start and carry no paths, CIDs, IP addresses, or user-controlled +labels. + +## Garbage collection + +```bash +curl --fail-with-body --request POST \ + --header 'x-api-key: your-generated-key' \ + 'https://ipfs.example.org/api/storage/gc?dryRun=true' +``` + +`?dryRun=true` reports the exact CIDs that would be released and retained without touching a block, +which is the supported way to review a deletion policy before enabling the scheduled collector. +`?force=true` collects even when the blockstore is below the high watermark. A run already in +progress answers `409`. + +The report carries `trigger`, `blockstoreBytesBefore`, `estimatedBytesAfter`, `releasedCids`, +`retainedCids`, `demoted`, `removedBlocks`, `removedCids`, `repairedPins`, `unprotected`, and +`errors`. `demoted` lists files whose local copy was handed over to their designated holders. + +## Replication repair + +```bash +curl --fail-with-body --request POST \ + --header 'x-api-key: your-generated-key' \ + https://ipfs.example.org/api/storage/repair +``` + +Runs the next bounded repair pass and reports its progress and the cycle state. A run already in +progress answers `409`. Repair places copies of any confirmed file whose designated holders have not +all acknowledged. + +## Administrative and debug routes + +These are operator tools. They are not part of `docs/openapi.yaml` and may change with the runtime. + +| Route | Purpose | +| ----------------------------------- | ---------------------------------- | +| `GET /api/helia/pins` | List the pin set | +| `POST /api/helia/pin/:cid` | Pin and register arbitrary content | +| `GET /api/helia/pins/isPinned/:cid` | Report whether a CID is pinned | +| `GET /api/libp2p/status` | Report libp2p status | +| `GET /api/libp2p/peers` | List connected peers | +| `GET /api/libp2p/connections` | List connections | +| `GET /api/libp2p/peerStore` | Read peer-store data | +| `GET /api/libp2p/peerInfo` | Read information about one peer | +| `GET /api/libp2p/dial` | Dial a peer on request | +| `GET /api/libp2p/services/ping` | Ping a peer | +| `GET /api/debug/autopeering` | Run one peering pass on request | + +`GET /api/helia/routing/findProviders/:cid` no longer exists: provider lookup needs content routing, +and this node registers none. `/api/debug/*` is not mounted at all unless `enableDebugApi` is +`true`, and it still requires the key. + +## Error format + +Errors are JSON objects with a single `error` string. `x-powered-by` is disabled, and an unmatched +path answers `404` from the not-found handler. + +## Machine-readable specification + +The OpenAPI 3.1 document is published at [`openapi.yaml`](/openapi.yaml) and lives in the repository +at `docs/openapi.yaml`. + +Neither the node nor this site serves an interactive API explorer. There is no `/swagger` endpoint +and no Swagger UI; use the specification file with a client generator or an editor of your choice. diff --git a/package-lock.json b/package-lock.json index 8e4db13..ebe29ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,9 @@ "prettier": "^3.9.6", "rimraf": "^6.1.3", "typescript": "^5.9.3", - "typescript-eslint": "^8.67.0" + "typescript-eslint": "^8.67.0", + "vitepress": "1.6.4", + "yaml": "2.9.0" }, "engines": { "node": ">=24.0.0" @@ -99,6 +101,264 @@ "xml2js": "^0.6.2" } }, + "node_modules/@algolia/abtesting": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.23.0.tgz", + "integrity": "sha512-j45MBISstltys9QyQ4xf6quRiN1g7vMuwQL9VM4dx8YuRZvCQ173b9royZAx6iAbRX3IB1VnG1z//NuwyQ8jpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.57.0.tgz", + "integrity": "sha512-JVFFujiZUCguk5tz3LZr4fTQxqpIrj4/Jw3SI7kMljSqtfLxYn/s/TWH0J2s4iNfsDpxPhgFGMotCpmDI4kZ8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.57.0.tgz", + "integrity": "sha512-6KqECK4ED3JJQEoDrQWnGPQzElA828xAD4qK5ceawNNyP/LcSvzAoLHjFkoTPksZ/kxj6VUtCRH+IHZesLltng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.57.0.tgz", + "integrity": "sha512-uqpGF3oXYsoCbQq5d7BzNrNTfIfuvJyGP1CKvSW27T9boUg7KOwyxsAw1AX0a3jSW2HrYEJ/NN+Z4MiGivbpeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.57.0.tgz", + "integrity": "sha512-u5NboJVJXDEFplvNnqqX4CxkXPYysjJRj47hOSh9329H8kG5gFLKJBIiS5utMQ+GZm8xQl3Te7NInDk6elEADQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.57.0.tgz", + "integrity": "sha512-uzc0b2LmHAK9/QID4xeo35OG84AkZl4YewkCqawqAOGLjT2eZpM/OZx45ESygMHG30Ws+ZTSdluPtMJcUnrbWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.57.0.tgz", + "integrity": "sha512-dIAhnM6ue/ssa5PjgNfu4g8A4yTojl9ZOUzZU3wIaIKRerL2R/3Emuf9n/D6ICXXP167KC6XCeC7nliSw7cuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.57.0.tgz", + "integrity": "sha512-2TTPTTKSJmCptvhCm4Xf3bBYMqZni+Pgc2hVdqc4l9wsBpSJNVTVIKpnd10OubUgkGcmppVDj1XQqYaf6EnPSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.57.0.tgz", + "integrity": "sha512-W4JseHKt+pzOxlFV+T3MWEG0h4Z2Se5zjoXUD0ewlw8aOWMG/yjRdopUdLQsXULepB/My2tDuZjkwk2sMfsUrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.57.0.tgz", + "integrity": "sha512-BrxJVE0/eLinEPICCD7BKN/2xnt0nkjge70u8zzE2ISP3fuB3tjLgcwpanUycvlHBFLI4gK0l5ol54p6IYuR/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.57.0.tgz", + "integrity": "sha512-Gc29jkeiLKlVfHvyrIgyUHHE+aYTdXEeLfK42rjr5/1TTVsYwUJz0XkvoIBIqfMjcDg6gXeHb1jTUZ0H+SYIlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.57.0.tgz", + "integrity": "sha512-PIPnPN7MP3fp2VAi01BVXhCWmD366ZB2Hkq5TlYKtThd4KxUtMmaaNDpFgVCTXtSIqWVZLJntOHRvxg/sIPd8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.57.0.tgz", + "integrity": "sha512-AX3RlOudXMdTwtwUqdAf5hAVLvXfOZZH1FZh6ALDdrhVLT0TtAIe48N6nYcWcTnwoTxK/wDIQqZ19IMjK8zJAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.57.0.tgz", + "integrity": "sha512-cWZc1dKb7wy9/wPpwMtL1y89gK2G7y2A47Coa7zwf1ydtIeJm4+S+XxoQ2b/ZRiQnrC1YHavLjYUPDCdnT7Khg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@assemblyscript/loader": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", @@ -261,7 +521,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -304,7 +563,6 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.29.8" }, @@ -364,7 +622,6 @@ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" @@ -734,558 +991,611 @@ "node": ">=6" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", - "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@docsearch/js/node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=12" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", - "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=12" } }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=12" } }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=12" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=12" } }, - "node_modules/@helia/bitswap": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-4.0.14.tgz", - "integrity": "sha512-Fd9/KYi67PAqk98tw9uCIPTCjsLXaK+5Wsq8+z8zRn3dLfcYNtkj86vPN6IKrnL+A4FP3URz2q8CUrzYnHFp3Q==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@helia/interface": "^7.1.1", - "@helia/libp2p": "^2.1.3", - "@helia/utils": "^3.0.5", - "@libp2p/interface": "^3.2.3", - "@libp2p/logger": "^6.2.8", - "@libp2p/peer-collections": "^7.0.21", - "@libp2p/peer-id": "^6.0.10", - "@libp2p/utils": "^7.2.2", - "@multiformats/multiaddr": "^13.0.3", - "any-signal": "^4.2.0", - "interface-blockstore": "^7.0.1", - "interface-store": "^8.0.0", - "it-drain": "^3.0.12", - "it-length-prefixed": "^11.0.1", - "it-map": "^3.1.6", - "it-pushable": "^3.2.4", - "it-take": "^3.0.11", - "it-to-buffer": "^5.0.0", - "multiformats": "^14.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.1.0", - "protons-runtime": "^7.0.0", - "race-event": "^1.6.1", - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/bitswap/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/bitswap/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^6.0.0" + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/bitswap/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/delegated-routing-client": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@helia/delegated-routing-client/-/delegated-routing-client-1.0.6.tgz", - "integrity": "sha512-UoAIy+qsro1Qde4l3hGbRXdxID1nY53m4Oig0VCrlafNFOScxYBy93H4/T2bwk/mBONMxHa40NbpCxlYLMZznw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@helia/delegated-routing-v1-http-api-client": "^9.0.0", - "@helia/interface": "^7.1.1", - "@libp2p/interface": "^3.2.3", - "it-first": "^3.0.11", - "it-map": "^3.1.6", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/delegated-routing-client/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/delegated-routing-v1-http-api-client": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-9.0.2.tgz", - "integrity": "sha512-NVbX/8Uf+04g5gZkpqk6J6aFMCgMxzyQ2XBxW2gpYysGEuMSEESRyiYuWUrKysy9Oo08ST4tJJ19Vh/+4wjzOA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^3.2.2", - "@libp2p/peer-id": "^6.0.9", - "@libp2p/utils": "^7.2.2", - "@multiformats/multiaddr": "^13.0.3", - "any-signal": "^4.2.0", - "browser-readablestream-to-it": "^2.0.12", - "it-first": "^3.0.11", - "it-map": "^3.1.6", - "it-ndjson": "^2.0.0", - "multiformats": "^14.0.0", - "race-signal": "^2.0.2", - "uint8arrays": "^6.1.1" + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/delegated-routing-v1-http-api-client/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/fallback-router": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@helia/fallback-router/-/fallback-router-1.1.1.tgz", - "integrity": "sha512-dfkS7TzvCHOoiKafht7VIrXlESHLiWjbgsg3tEszK06UVG+C6sWhuSN+gD13VMHiyhPSKjAzupyoTFgglZ6FuA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@helia/interface": "^7.1.1", - "@multiformats/uri-to-multiaddr": "^10.0.0", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/fallback-router/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/http": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@helia/http/-/http-4.0.6.tgz", - "integrity": "sha512-jtpV0BuhPEnIAkBv/QKpsh9I6f3MDhDzLChJsgCi/wtN2ztQls/Xc2n4Qipr92Ytfmq0R4hai6ragxhq5O87Cg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@helia/delegated-routing-client": "^1.0.6", - "@helia/fallback-router": "^1.1.1", - "@helia/interface": "^7.1.1", - "@helia/trustless-gateway-client": "^1.0.5" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/interface": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-7.1.1.tgz", - "integrity": "sha512-L3uLlfEY8MYUV01YdJfv6C35FRhVwRiNx36Yh+8qCpJuCs+GmTmaxVrAb4wXXRg2yIjMR+HAAIAcJIOV/x5n0A==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@ipshipyard/crypto": "^1.1.0", - "@ipshipyard/keychain": "^1.0.2", - "@libp2p/interface": "^3.2.3", - "@multiformats/dns": "^1.0.13", - "@multiformats/multiaddr": "^13.0.3", - "abort-error": "^1.0.2", - "birnam": "^1.0.0", - "interface-blockstore": "^7.0.1", - "interface-datastore": "^10.0.1", - "main-event": "^1.0.4", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0" + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/libp2p": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@helia/libp2p/-/libp2p-2.1.3.tgz", - "integrity": "sha512-AqNqSv9N2YVWLhCdeRAGGc2OnxWFxBwLB9KT+WVCSBUjBbbLjKhPe4y+py6pK+LX6tXVqSY05mZM3X6DzFH04g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/libp2p-noise": "^17.0.0", - "@chainsafe/libp2p-yamux": "^8.0.1", - "@helia/delegated-routing-client": "^1.0.6", - "@helia/delegated-routing-v1-http-api-client": "^9.0.0", - "@helia/interface": "^7.1.1", - "@ipshipyard/libp2p-auto-tls": "^2.0.1", - "@libp2p/autonat": "^3.0.21", - "@libp2p/bootstrap": "^12.0.24", - "@libp2p/circuit-relay-v2": "^4.2.6", - "@libp2p/config": "^1.1.32", - "@libp2p/dcutr": "^3.0.21", - "@libp2p/http": "^2.0.2", - "@libp2p/identify": "^4.1.7", - "@libp2p/interface": "^3.2.3", - "@libp2p/kad-dht": "^16.3.2", - "@libp2p/keychain": "^6.1.2", - "@libp2p/mdns": "^12.0.24", - "@libp2p/mplex": "^12.0.24", - "@libp2p/peer-id": "^6.0.10", - "@libp2p/ping": "^3.1.6", - "@libp2p/tcp": "^11.0.21", - "@libp2p/tls": "^3.1.3", - "@libp2p/upnp-nat": "^4.0.21", - "@libp2p/webrtc": "^6.0.24", - "@libp2p/websockets": "^10.1.14", - "@multiformats/multiaddr": "^13.0.3", - "it-foreach": "^2.1.7", - "it-map": "^3.1.6", - "libp2p": "^3.3.3", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@helia/libp2p/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/trustless-gateway-client": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@helia/trustless-gateway-client/-/trustless-gateway-client-1.0.5.tgz", - "integrity": "sha512-7hmipIMOEK9x1pnCsA06gMxijDHn/0KjILQ6f9qy/Yfqg7aaUQfrC9OGpM59DTayGlo29QknoShsDY1LdrAEVg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@helia/interface": "^7.1.1", - "@helia/utils": "^3.0.5", - "@libp2p/utils": "^7.2.2", - "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "@multiformats/multiaddr-to-uri": "^12.0.0", - "@multiformats/uri-to-multiaddr": "^10.0.0", - "abort-error": "^1.0.2", - "birnam": "^1.0.0", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/trustless-gateway-client/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^6.0.0" + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/trustless-gateway-client/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@helia/unixfs": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-8.0.5.tgz", - "integrity": "sha512-fXloT3Wi045JyHEYBfyaulrDjEKzdFkdXSgxKRVlqd0aBaC6txAq4Io1ZAeoByNZ6sjI0wgo8loD1QA/QNQtsA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", "dependencies": { - "@helia/interface": "^7.1.1", - "@ipld/dag-pb": "^4.1.7", - "@libp2p/interface": "^3.2.3", - "@libp2p/logger": "^6.2.8", - "@libp2p/utils": "^7.2.2", - "@multiformats/murmur3": "^2.2.5", - "interface-blockstore": "^7.0.1", - "ipfs-unixfs": "^13.0.0", - "ipfs-unixfs-exporter": "^16.0.2", - "ipfs-unixfs-importer": "^17.0.1", - "it-all": "^3.0.11", - "it-first": "^3.0.11", - "it-glob": "^3.0.6", - "it-last": "^3.0.11", - "it-pipe": "^3.0.1", - "it-to-buffer": "^5.0.0", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "sparse-array": "^1.3.2", - "uint8arrays": "^6.1.1" + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@helia/unixfs/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@helia/utils": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-3.0.5.tgz", - "integrity": "sha512-vcJ8FltQ5vNHiULhx3WO/0nm4/ljWDpZdwIS9cwibF6hfDYna5w2IZXFMu1czI6wHt26fR+EtyUOCFIoS51xcA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@helia/interface": "^7.1.1", - "@libp2p/interface": "^3.2.3", - "@libp2p/utils": "^7.2.2", - "@multiformats/multiaddr": "^13.0.3", - "it-filter": "^3.1.6", - "it-to-buffer": "^5.0.0", - "multiformats": "^14.0.0", - "p-defer": "^4.0.1", - "progress-events": "^1.1.0", - "race-signal": "^2.0.0" + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/types": "^0.15.0" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": ">=18.18.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=18.18.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { - "node": ">=18.18.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=12.22" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "url": "https://eslint.org/donate" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@ipld/dag-cbor": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-10.0.2.tgz", - "integrity": "sha512-1eCD4YMTlkx5i36oQUmeAojLS/wu+XCd1Gs31BA0afk0TaVkhnyMofIAT/pi0wV3HS3/G7QSN/1NibhiksMyZQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "cborg": "^6.1.1", - "multiformats": "^14.0.0" + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@ipld/dag-json": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-11.0.1.tgz", - "integrity": "sha512-cMDD0yWkUruqhbLDwAWaV5q92hYKQDP2xP0n+yZp0oxsC68Pw+xFZNc9rj0v2n5m5m/Aa1A+Qlb2tY3NjQMsWg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "cborg": "^6.1.1", - "multiformats": "^14.0.0" + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@ipld/dag-pb": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.7.tgz", - "integrity": "sha512-/i/13trFihjWfDyXlylRwhuYjtzYjvOFw0vlRjYGnZuv7d7MOgA2lV/vRuL5RfeUajM03aZfFLdq4S7cTbbTRg==", - "license": "Apache-2.0 OR MIT", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "multiformats": "^14.0.0" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@ipshipyard/crypto": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@ipshipyard/crypto/-/crypto-1.1.0.tgz", - "integrity": "sha512-xMnzqJFuDirmjdMlmHb9r/olj++oFv3Pf5utF49+MvNQqKjYZ2YHMiO8Y8xk8nh6E+xa4g9+r/hij0FpNVZusw==", + "node_modules/@helia/bitswap": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-4.0.14.tgz", + "integrity": "sha512-Fd9/KYi67PAqk98tw9uCIPTCjsLXaK+5Wsq8+z8zRn3dLfcYNtkj86vPN6IKrnL+A4FP3URz2q8CUrzYnHFp3Q==", "license": "Apache-2.0 OR MIT", "dependencies": { - "abort-error": "^1.0.2", + "@helia/interface": "^7.1.1", + "@helia/libp2p": "^2.1.3", + "@helia/utils": "^3.0.5", + "@libp2p/interface": "^3.2.3", + "@libp2p/logger": "^6.2.8", + "@libp2p/peer-collections": "^7.0.21", + "@libp2p/peer-id": "^6.0.10", + "@libp2p/utils": "^7.2.2", + "@multiformats/multiaddr": "^13.0.3", + "any-signal": "^4.2.0", + "interface-blockstore": "^7.0.1", + "interface-store": "^8.0.0", + "it-drain": "^3.0.12", + "it-length-prefixed": "^11.0.1", + "it-map": "^3.1.6", + "it-pushable": "^3.2.4", + "it-take": "^3.0.11", + "it-to-buffer": "^5.0.0", "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.1.0", "protons-runtime": "^7.0.0", + "race-event": "^1.6.1", + "uint8-varint": "^3.0.0", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@ipshipyard/crypto/node_modules/protons-runtime": { + "node_modules/@helia/bitswap/node_modules/protons-runtime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", @@ -1296,7 +1606,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@ipshipyard/crypto/node_modules/uint8arraylist": { + "node_modules/@helia/bitswap/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -1305,7 +1615,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@ipshipyard/crypto/node_modules/uint8arrays": { + "node_modules/@helia/bitswap/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1314,21 +1624,22 @@ "multiformats": "^14.0.0" } }, - "node_modules/@ipshipyard/keychain": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@ipshipyard/keychain/-/keychain-1.0.2.tgz", - "integrity": "sha512-otMCFOItScEX3VNy/gHF+CVd2QGPNo75uwZBoYUj2Rr8xnF5eq1nlbk6iEqH6cwqu2j/UuzFvtD2s2lmGo0mDA==", + "node_modules/@helia/delegated-routing-client": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-client/-/delegated-routing-client-1.0.6.tgz", + "integrity": "sha512-UoAIy+qsro1Qde4l3hGbRXdxID1nY53m4Oig0VCrlafNFOScxYBy93H4/T2bwk/mBONMxHa40NbpCxlYLMZznw==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@ipshipyard/crypto": "^1.1.0", - "abort-error": "^1.0.2", - "interface-datastore": "^10.0.1", + "@helia/delegated-routing-v1-http-api-client": "^9.0.0", + "@helia/interface": "^7.1.1", + "@libp2p/interface": "^3.2.3", + "it-first": "^3.0.11", + "it-map": "^3.1.6", "multiformats": "^14.0.0", - "sanitize-filename": "^1.6.4", "uint8arrays": "^6.1.1" } }, - "node_modules/@ipshipyard/keychain/node_modules/uint8arrays": { + "node_modules/@helia/delegated-routing-client/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1337,32 +1648,27 @@ "multiformats": "^14.0.0" } }, - "node_modules/@ipshipyard/libp2p-auto-tls": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@ipshipyard/libp2p-auto-tls/-/libp2p-auto-tls-2.0.2.tgz", - "integrity": "sha512-jnGqf/eif9GpjH03PaenekRWRvlJgoVZ1JsAawW0dJxcIW8qHiSMIbVh49xOjUGTK8vUbN26bsO+Rustg0n72A==", + "node_modules/@helia/delegated-routing-v1-http-api-client": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-9.0.2.tgz", + "integrity": "sha512-NVbX/8Uf+04g5gZkpqk6J6aFMCgMxzyQ2XBxW2gpYysGEuMSEESRyiYuWUrKysy9Oo08ST4tJJ19Vh/+4wjzOA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^5.0.9", - "@libp2p/http": "^2.0.0", - "@libp2p/interface": "^3.0.2", - "@libp2p/interface-internal": "^3.0.4", - "@libp2p/keychain": "^6.0.4", - "@libp2p/utils": "^7.0.4", - "@multiformats/multiaddr": "^13.0.1", - "@multiformats/multiaddr-matcher": "^3.0.1", - "@peculiar/x509": "^2.0.0", - "acme-client": "^5.4.0", - "any-signal": "^4.1.1", - "delay": "^7.0.0", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.2", - "reflect-metadata": "^0.2.2", + "@libp2p/interface": "^3.2.2", + "@libp2p/peer-id": "^6.0.9", + "@libp2p/utils": "^7.2.2", + "@multiformats/multiaddr": "^13.0.3", + "any-signal": "^4.2.0", + "browser-readablestream-to-it": "^2.0.12", + "it-first": "^3.0.11", + "it-map": "^3.1.6", + "it-ndjson": "^2.0.0", + "multiformats": "^14.0.0", + "race-signal": "^2.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@ipshipyard/libp2p-auto-tls/node_modules/uint8arrays": { + "node_modules/@helia/delegated-routing-v1-http-api-client/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1371,154 +1677,99 @@ "multiformats": "^14.0.0" } }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "peer": true, + "node_modules/@helia/fallback-router": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@helia/fallback-router/-/fallback-router-1.1.1.tgz", + "integrity": "sha512-dfkS7TzvCHOoiKafht7VIrXlESHLiWjbgsg3tEszK06UVG+C6sWhuSN+gD13VMHiyhPSKjAzupyoTFgglZ6FuA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@helia/interface": "^7.1.1", + "@multiformats/uri-to-multiaddr": "^10.0.0", + "multiformats": "^14.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "peer": true, + "node_modules/@helia/fallback-router/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "peer": true, + "node_modules/@helia/http": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@helia/http/-/http-4.0.6.tgz", + "integrity": "sha512-jtpV0BuhPEnIAkBv/QKpsh9I6f3MDhDzLChJsgCi/wtN2ztQls/Xc2n4Qipr92Ytfmq0R4hai6ragxhq5O87Cg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@helia/delegated-routing-client": "^1.0.6", + "@helia/fallback-router": "^1.1.1", + "@helia/interface": "^7.1.1", + "@helia/trustless-gateway-client": "^1.0.5" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "peer": true, + "node_modules/@helia/interface": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-7.1.1.tgz", + "integrity": "sha512-L3uLlfEY8MYUV01YdJfv6C35FRhVwRiNx36Yh+8qCpJuCs+GmTmaxVrAb4wXXRg2yIjMR+HAAIAcJIOV/x5n0A==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0.0" + "@ipshipyard/crypto": "^1.1.0", + "@ipshipyard/keychain": "^1.0.2", + "@libp2p/interface": "^3.2.3", + "@multiformats/dns": "^1.0.13", + "@multiformats/multiaddr": "^13.0.3", + "abort-error": "^1.0.2", + "birnam": "^1.0.0", + "interface-blockstore": "^7.0.1", + "interface-datastore": "^10.0.1", + "main-event": "^1.0.4", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT", - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@libp2p/autonat": { - "version": "3.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-3.0.26.tgz", - "integrity": "sha512-gA1eJU4p7I6Dn8uvn6GsKSxpD5kbcxres3cht+LVG/Kga4UGX+yE/os0RT+4uNGyZGIfXqLxNUy8Hq1xQTJCmQ==", + "node_modules/@helia/libp2p": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@helia/libp2p/-/libp2p-2.1.3.tgz", + "integrity": "sha512-AqNqSv9N2YVWLhCdeRAGGc2OnxWFxBwLB9KT+WVCSBUjBbbLjKhPe4y+py6pK+LX6tXVqSY05mZM3X6DzFH04g==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/peer-collections": "^7.0.26", - "@libp2p/peer-id": "^6.0.14", - "@libp2p/utils": "^7.3.2", + "@chainsafe/libp2p-noise": "^17.0.0", + "@chainsafe/libp2p-yamux": "^8.0.1", + "@helia/delegated-routing-client": "^1.0.6", + "@helia/delegated-routing-v1-http-api-client": "^9.0.0", + "@helia/interface": "^7.1.1", + "@ipshipyard/libp2p-auto-tls": "^2.0.1", + "@libp2p/autonat": "^3.0.21", + "@libp2p/bootstrap": "^12.0.24", + "@libp2p/circuit-relay-v2": "^4.2.6", + "@libp2p/config": "^1.1.32", + "@libp2p/dcutr": "^3.0.21", + "@libp2p/http": "^2.0.2", + "@libp2p/identify": "^4.1.7", + "@libp2p/interface": "^3.2.3", + "@libp2p/kad-dht": "^16.3.2", + "@libp2p/keychain": "^6.1.2", + "@libp2p/mdns": "^12.0.24", + "@libp2p/mplex": "^12.0.24", + "@libp2p/peer-id": "^6.0.10", + "@libp2p/ping": "^3.1.6", + "@libp2p/tcp": "^11.0.21", + "@libp2p/tls": "^3.1.3", + "@libp2p/upnp-nat": "^4.0.21", + "@libp2p/webrtc": "^6.0.24", + "@libp2p/websockets": "^10.1.14", "@multiformats/multiaddr": "^13.0.3", - "any-signal": "^4.1.1", - "main-event": "^1.0.1", + "it-foreach": "^2.1.7", + "it-map": "^3.1.6", + "libp2p": "^3.3.3", "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2" - } - }, - "node_modules/@libp2p/autonat/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/autonat/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^6.0.0" + "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/autonat/node_modules/uint8arrays": { + "node_modules/@helia/libp2p/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1527,58 +1778,28 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/bootstrap": { - "version": "12.0.29", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-12.0.29.tgz", - "integrity": "sha512-HJqRyr75io10F/zaJCMGhrdOi7an4bJmgw12DLrBR/hPteeFAIZ4bXeiV+GNxw5vmRJoEpfUTVzIwPTsRJMViw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/peer-id": "^6.0.14", - "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "main-event": "^1.0.1" - } - }, - "node_modules/@libp2p/circuit-relay-v2": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-4.2.11.tgz", - "integrity": "sha512-q47qWuJxmNocAX4oRAUWpp2d56aiymsih9ORCkBdQGUkBzynPyBsucvCX5bnF8tb80Ji5wBHl4jCa1DmrW1T1A==", + "node_modules/@helia/trustless-gateway-client": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@helia/trustless-gateway-client/-/trustless-gateway-client-1.0.5.tgz", + "integrity": "sha512-7hmipIMOEK9x1pnCsA06gMxijDHn/0KjILQ6f9qy/Yfqg7aaUQfrC9OGpM59DTayGlo29QknoShsDY1LdrAEVg==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^5.1.22", - "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/peer-collections": "^7.0.26", - "@libp2p/peer-id": "^6.0.14", - "@libp2p/peer-record": "^9.0.15", - "@libp2p/utils": "^7.3.2", + "@helia/interface": "^7.1.1", + "@helia/utils": "^3.0.5", + "@libp2p/utils": "^7.2.2", "@multiformats/multiaddr": "^13.0.3", "@multiformats/multiaddr-matcher": "^3.0.2", - "any-signal": "^4.1.1", - "main-event": "^1.0.1", + "@multiformats/multiaddr-to-uri": "^12.0.0", + "@multiformats/uri-to-multiaddr": "^10.0.0", + "abort-error": "^1.0.2", + "birnam": "^1.0.0", "multiformats": "^14.0.0", - "nanoid": "^6.0.0", "progress-events": "^1.1.0", - "protons-runtime": "^7.0.0", - "retimeable-signal": "^1.0.1", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/circuit-relay-v2/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/circuit-relay-v2/node_modules/uint8arraylist": { + "node_modules/@helia/trustless-gateway-client/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -1587,7 +1808,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/circuit-relay-v2/node_modules/uint8arrays": { + "node_modules/@helia/trustless-gateway-client/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1596,55 +1817,35 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/config": { - "version": "1.1.36", - "resolved": "https://registry.npmjs.org/@libp2p/config/-/config-1.1.36.tgz", - "integrity": "sha512-DWP4CRU/Datb8t8mvGF1LQtc45Z3kZBEzG1Ua+1CuLcm+unLkRr2UcNyb4AG8nSA9jdQQocO2sQHofQ/9LTmBg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.22", - "@libp2p/interface": "^3.2.5", - "@libp2p/keychain": "^6.1.6", - "@libp2p/logger": "^6.2.12", - "interface-datastore": "^10.0.1" - } - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.22", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.22.tgz", - "integrity": "sha512-yVk4BefSiEz044kjsiLSEoJTbhRoyamqx4kFI9LnX8/asr4wnqDWKTXcn6mSx/RVNEzmDO70l3vOvcMCl4aLCQ==", + "node_modules/@helia/unixfs": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@helia/unixfs/-/unixfs-8.0.5.tgz", + "integrity": "sha512-fXloT3Wi045JyHEYBfyaulrDjEKzdFkdXSgxKRVlqd0aBaC6txAq4Io1ZAeoByNZ6sjI0wgo8loD1QA/QNQtsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", + "@helia/interface": "^7.1.1", + "@ipld/dag-pb": "^4.1.7", + "@libp2p/interface": "^3.2.3", + "@libp2p/logger": "^6.2.8", + "@libp2p/utils": "^7.2.2", + "@multiformats/murmur3": "^2.2.5", + "interface-blockstore": "^7.0.1", + "ipfs-unixfs": "^13.0.0", + "ipfs-unixfs-exporter": "^16.0.2", + "ipfs-unixfs-importer": "^17.0.1", + "it-all": "^3.0.11", + "it-first": "^3.0.11", + "it-glob": "^3.0.6", + "it-last": "^3.0.11", + "it-pipe": "^3.0.1", + "it-to-buffer": "^5.0.0", "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", + "progress-events": "^1.1.0", + "sparse-array": "^1.3.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/crypto/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/crypto/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/crypto/node_modules/uint8arrays": { + "node_modules/@helia/unixfs/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1653,133 +1854,165 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/dcutr": { - "version": "3.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-3.0.26.tgz", - "integrity": "sha512-8knF7Ppx69mSgljdSz7xrYZHl3KQmJs4u04kMk1oCDlzq8ehxcKPdbYbUnDTlsBoKsFV7WSHQfW1y6xqTg4srw==", + "node_modules/@helia/utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-3.0.5.tgz", + "integrity": "sha512-vcJ8FltQ5vNHiULhx3WO/0nm4/ljWDpZdwIS9cwibF6hfDYna5w2IZXFMu1czI6wHt26fR+EtyUOCFIoS51xcA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/utils": "^7.3.2", + "@helia/interface": "^7.1.1", + "@libp2p/interface": "^3.2.3", + "@libp2p/utils": "^7.2.2", "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "delay": "^7.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2" + "it-filter": "^3.1.6", + "it-to-buffer": "^5.0.0", + "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "progress-events": "^1.1.0", + "race-signal": "^2.0.0" } }, - "node_modules/@libp2p/dcutr/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@libp2p/dcutr/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "uint8arrays": "^6.0.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@libp2p/dcutr/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^14.0.0" + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@libp2p/http": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@libp2p/http/-/http-2.0.6.tgz", - "integrity": "sha512-z3RRdZP/+/kjYREY9MyZ5E90bKrGThQAw9sTZyv0ahxzUM+7I0/JPFLj98Wu0tgI9LStA6vtweVi0Ffwp0u9bA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/http-fetch": "^4.0.0", - "@libp2p/http-peer-id-auth": "^2.0.0", - "@libp2p/http-utils": "^2.0.0", - "@libp2p/http-websocket": "^2.0.0", - "@libp2p/interface": "^3.2.0", - "@libp2p/interface-internal": "^3.1.0", - "@multiformats/multiaddr": "^13.0.3", - "cookie": "^1.1.1", - "undici": "^8.0.3" + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@libp2p/http-fetch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@libp2p/http-fetch/-/http-fetch-4.0.4.tgz", - "integrity": "sha512-MHROqwXP4KnRIZguzUIyrexRWuEF6InXKzUCFktergqgkkkkkt45ZiGx8wOa/1xbdUEZmiDoOITpHsNhU5GWBQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.94", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.94.tgz", + "integrity": "sha512-l8UWzVxKaqZd9ABsE/M/9p6NyGkQnmCnOoZyhQmjlXCtY5PuL2rcWxOFk2l9pk7ux3ERMPkTLE4jl6kQpTkwxA==", + "dev": true, + "license": "CC0-1.0", "dependencies": { - "@achingbrain/http-parser-js": "^0.5.9", - "@libp2p/http-utils": "^2.0.0", - "@libp2p/interface": "^3.2.0", - "uint8arrays": "^6.1.1" + "@iconify/types": "*" } }, - "node_modules/@libp2p/http-fetch/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ipld/dag-cbor": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-10.0.2.tgz", + "integrity": "sha512-1eCD4YMTlkx5i36oQUmeAojLS/wu+XCd1Gs31BA0afk0TaVkhnyMofIAT/pi0wV3HS3/G7QSN/1NibhiksMyZQ==", "license": "Apache-2.0 OR MIT", "dependencies": { + "cborg": "^6.1.1", "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/http-peer-id-auth": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@libp2p/http-peer-id-auth/-/http-peer-id-auth-2.0.3.tgz", - "integrity": "sha512-lBzwbnqIJa4SnMssseEtbgdGHB+sH2CQr3HxhSwEhLRRpU1iCmkUR1L9X+FTH6A5Xo62y/JYnnhBO7CG3YTSdg==", + "node_modules/@ipld/dag-json": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-11.0.1.tgz", + "integrity": "sha512-cMDD0yWkUruqhbLDwAWaV5q92hYKQDP2xP0n+yZp0oxsC68Pw+xFZNc9rj0v2n5m5m/Aa1A+Qlb2tY3NjQMsWg==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^5.1.15", - "@libp2p/interface": "^3.2.0", - "@libp2p/peer-id": "^6.0.6", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" + "cborg": "^6.1.1", + "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/http-peer-id-auth/node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "node_modules/@ipld/dag-pb": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.7.tgz", + "integrity": "sha512-/i/13trFihjWfDyXlylRwhuYjtzYjvOFw0vlRjYGnZuv7d7MOgA2lV/vRuL5RfeUajM03aZfFLdq4S7cTbbTRg==", "license": "Apache-2.0 OR MIT", "dependencies": { "multiformats": "^14.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@libp2p/http-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@libp2p/http-utils/-/http-utils-2.0.6.tgz", - "integrity": "sha512-WIYT1luITS4AZl0/b3tB5c7Kw9MKHAX2Lumx83oKImBPI9lPpRNafOmVVVRvwfjPq77t5nVt4ha8y1UDEtRN6g==", + "node_modules/@ipshipyard/crypto": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ipshipyard/crypto/-/crypto-1.1.0.tgz", + "integrity": "sha512-xMnzqJFuDirmjdMlmHb9r/olj++oFv3Pf5utF49+MvNQqKjYZ2YHMiO8Y8xk8nh6E+xa4g9+r/hij0FpNVZusw==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@achingbrain/http-parser-js": "^0.5.9", - "@libp2p/interface": "^3.2.0", - "@libp2p/peer-id": "^6.0.6", - "@libp2p/utils": "^7.0.15", - "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-to-uri": "^12.0.0", - "@multiformats/uri-to-multiaddr": "^10.0.0", - "it-to-browser-readablestream": "^2.0.14", + "abort-error": "^1.0.2", "multiformats": "^14.0.0", - "race-event": "^1.6.1", - "readable-stream": "^4.7.0", + "protons-runtime": "^7.0.0", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/http-utils/node_modules/uint8arraylist": { + "node_modules/@ipshipyard/crypto/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" + } + }, + "node_modules/@ipshipyard/crypto/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -1788,7 +2021,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/http-utils/node_modules/uint8arrays": { + "node_modules/@ipshipyard/crypto/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1797,34 +2030,21 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/http-websocket": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@libp2p/http-websocket/-/http-websocket-2.0.4.tgz", - "integrity": "sha512-AEATJ6MERtxY/UZkFWCgNpB7bhcVTmeeBKZkVl2RJbVYTk8CSv3fO3uXLcYFQ9W4rOa0eG4Gvr/USBU+41xB8A==", + "node_modules/@ipshipyard/keychain": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ipshipyard/keychain/-/keychain-1.0.2.tgz", + "integrity": "sha512-otMCFOItScEX3VNy/gHF+CVd2QGPNo75uwZBoYUj2Rr8xnF5eq1nlbk6iEqH6cwqu2j/UuzFvtD2s2lmGo0mDA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@achingbrain/http-parser-js": "^0.5.9", - "@libp2p/http-utils": "^2.0.0", - "@libp2p/interface": "^3.2.0", - "@libp2p/interface-internal": "^3.1.0", - "@libp2p/utils": "^7.0.15", - "@multiformats/multiaddr": "^13.0.3", + "@ipshipyard/crypto": "^1.1.0", + "abort-error": "^1.0.2", + "interface-datastore": "^10.0.1", "multiformats": "^14.0.0", - "race-event": "^1.6.1", - "uint8arraylist": "^3.0.2", + "sanitize-filename": "^1.6.4", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/http-websocket/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/http-websocket/node_modules/uint8arrays": { + "node_modules/@ipshipyard/keychain/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1833,49 +2053,32 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/identify": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-4.1.12.tgz", - "integrity": "sha512-nNBnxSclPVsvRsaLlwX9cz7gO020KjWEsaSzqDryWz6VStbLLdcwJYkVUP3do7VoFHkkF8PMQkScW7XCA27ZOA==", + "node_modules/@ipshipyard/libp2p-auto-tls": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@ipshipyard/libp2p-auto-tls/-/libp2p-auto-tls-2.0.2.tgz", + "integrity": "sha512-jnGqf/eif9GpjH03PaenekRWRvlJgoVZ1JsAawW0dJxcIW8qHiSMIbVh49xOjUGTK8vUbN26bsO+Rustg0n72A==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^5.1.22", - "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/peer-id": "^6.0.14", - "@libp2p/peer-record": "^9.0.15", - "@libp2p/utils": "^7.3.2", - "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "it-drain": "^3.0.10", - "it-parallel": "^3.0.13", - "main-event": "^1.0.1", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", + "@chainsafe/is-ip": "^2.0.2", + "@libp2p/crypto": "^5.0.9", + "@libp2p/http": "^2.0.0", + "@libp2p/interface": "^3.0.2", + "@libp2p/interface-internal": "^3.0.4", + "@libp2p/keychain": "^6.0.4", + "@libp2p/utils": "^7.0.4", + "@multiformats/multiaddr": "^13.0.1", + "@multiformats/multiaddr-matcher": "^3.0.1", + "@peculiar/x509": "^2.0.0", + "acme-client": "^5.4.0", + "any-signal": "^4.1.1", + "delay": "^7.0.0", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.2", + "reflect-metadata": "^0.2.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/identify/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/identify/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/identify/node_modules/uint8arrays": { + "node_modules/@ipshipyard/libp2p-auto-tls/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1884,33 +2087,144 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/interface": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", - "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@libp2p/autonat": { + "version": "3.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/autonat/-/autonat-3.0.26.tgz", + "integrity": "sha512-gA1eJU4p7I6Dn8uvn6GsKSxpD5kbcxres3cht+LVG/Kga4UGX+yE/os0RT+4uNGyZGIfXqLxNUy8Hq1xQTJCmQ==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@multiformats/dns": "^1.0.6", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", "@multiformats/multiaddr": "^13.0.3", + "any-signal": "^4.1.1", "main-event": "^1.0.1", "multiformats": "^14.0.0", - "progress-events": "^1.1.0", + "protons-runtime": "^7.0.0", "uint8arraylist": "^3.0.2" } }, - "node_modules/@libp2p/interface-internal": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-3.1.11.tgz", - "integrity": "sha512-lHJueWKdfkf/nrbVkGNUWrqDvF3T9Brn4hyONQmhTqG2mWyWKntRR/CkPjVgMRDSF2fuyzDJGg+n9tC0Gphk8g==", + "node_modules/@libp2p/autonat/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/peer-collections": "^7.0.26", - "@multiformats/multiaddr": "^13.0.3", - "progress-events": "^1.0.1" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/interface/node_modules/uint8arraylist": { + "node_modules/@libp2p/autonat/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -1919,7 +2233,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/interface/node_modules/uint8arrays": { + "node_modules/@libp2p/autonat/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1928,10 +2242,24 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/kad-dht": { - "version": "16.4.3", - "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-16.4.3.tgz", - "integrity": "sha512-i3FDNr9XLHcl8X27MRWrra3UyZw4o1esIen+66CNwFzZOTe85ndv6zNDOpgd2di1JT+E/xhKrghd+0wO3zfC6w==", + "node_modules/@libp2p/bootstrap": { + "version": "12.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-12.0.29.tgz", + "integrity": "sha512-HJqRyr75io10F/zaJCMGhrdOi7an4bJmgw12DLrBR/hPteeFAIZ4bXeiV+GNxw5vmRJoEpfUTVzIwPTsRJMViw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-id": "^6.0.14", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "main-event": "^1.0.1" + } + }, + "node_modules/@libp2p/circuit-relay-v2": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@libp2p/circuit-relay-v2/-/circuit-relay-v2-4.2.11.tgz", + "integrity": "sha512-q47qWuJxmNocAX4oRAUWpp2d56aiymsih9ORCkBdQGUkBzynPyBsucvCX5bnF8tb80Ji5wBHl4jCa1DmrW1T1A==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.1.22", @@ -1939,35 +2267,22 @@ "@libp2p/interface-internal": "^3.1.11", "@libp2p/peer-collections": "^7.0.26", "@libp2p/peer-id": "^6.0.14", - "@libp2p/ping": "^3.1.11", - "@libp2p/record": "^4.0.15", + "@libp2p/peer-record": "^9.0.15", "@libp2p/utils": "^7.3.2", "@multiformats/multiaddr": "^13.0.3", "@multiformats/multiaddr-matcher": "^3.0.2", "any-signal": "^4.1.1", - "interface-datastore": "^10.0.1", - "it-all": "^3.0.9", - "it-drain": "^3.0.10", - "it-length": "^3.0.9", - "it-map": "^3.1.4", - "it-merge": "^3.0.12", - "it-parallel": "^3.0.13", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-take": "^3.0.9", "main-event": "^1.0.1", "multiformats": "^14.0.0", - "p-defer": "^4.0.1", - "p-event": "^7.0.0", - "progress-events": "^1.0.1", + "nanoid": "^6.0.0", + "progress-events": "^1.1.0", "protons-runtime": "^7.0.0", - "race-signal": "^2.0.0", - "uint8-varint": "^3.0.0", + "retimeable-signal": "^1.0.1", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/kad-dht/node_modules/protons-runtime": { + "node_modules/@libp2p/circuit-relay-v2/node_modules/protons-runtime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", @@ -1978,7 +2293,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/kad-dht/node_modules/uint8arraylist": { + "node_modules/@libp2p/circuit-relay-v2/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -1987,7 +2302,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/kad-dht/node_modules/uint8arrays": { + "node_modules/@libp2p/circuit-relay-v2/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -1996,23 +2311,55 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/keychain": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-6.1.6.tgz", - "integrity": "sha512-G4xT3A8M5yiOJAcYT4YTnq9Gd3HOzHOIig1/KzcXzxaeuYCwXOmzreqUYabuEMmExkn966g5jiJ7RkCDQWO3UA==", + "node_modules/@libp2p/config": { + "version": "1.1.36", + "resolved": "https://registry.npmjs.org/@libp2p/config/-/config-1.1.36.tgz", + "integrity": "sha512-DWP4CRU/Datb8t8mvGF1LQtc45Z3kZBEzG1Ua+1CuLcm+unLkRr2UcNyb4AG8nSA9jdQQocO2sQHofQ/9LTmBg==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.1.22", "@libp2p/interface": "^3.2.5", + "@libp2p/keychain": "^6.1.6", + "@libp2p/logger": "^6.2.12", + "interface-datastore": "^10.0.1" + } + }, + "node_modules/@libp2p/crypto": { + "version": "5.1.22", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.22.tgz", + "integrity": "sha512-yVk4BefSiEz044kjsiLSEoJTbhRoyamqx4kFI9LnX8/asr4wnqDWKTXcn6mSx/RVNEzmDO70l3vOvcMCl4aLCQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.2.5", + "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", - "asn1js": "^3.0.6", - "interface-datastore": "^10.0.1", "multiformats": "^14.0.0", - "sanitize-filename": "^1.6.3", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/keychain/node_modules/uint8arrays": { + "node_modules/@libp2p/crypto/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" + } + }, + "node_modules/@libp2p/crypto/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" + } + }, + "node_modules/@libp2p/crypto/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2021,51 +2368,34 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/logger": { - "version": "6.2.12", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.12.tgz", - "integrity": "sha512-FwCdJ5hGFDvWI/SULZ4Iez+k+JtdMAj4/EK6bWmkh08ORIztwKpt0IXCOe8nIfKhBKwLYJu00BnZDRsedX7+7Q==", + "node_modules/@libp2p/dcutr": { + "version": "3.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/dcutr/-/dcutr-3.0.26.tgz", + "integrity": "sha512-8knF7Ppx69mSgljdSz7xrYZHl3KQmJs4u04kMk1oCDlzq8ehxcKPdbYbUnDTlsBoKsFV7WSHQfW1y6xqTg4srw==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/utils": "^7.3.2", "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.0", - "weald": "^1.1.0" - } - }, - "node_modules/@libp2p/mdns": { - "version": "12.0.29", - "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-12.0.29.tgz", - "integrity": "sha512-EiDvAm0rgxlGDTeWFpBBX7IL8tE/Yxq3GyqGpYYdySSHqtbkKLxU7v/kf1uBRqbItQN5xgag8kFTpkr+89xHZQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/peer-id": "^6.0.14", - "@libp2p/utils": "^7.3.2", - "@multiformats/multiaddr": "^13.0.3", - "@types/multicast-dns": "^7.2.4", - "dns-packet": "^5.6.1", - "main-event": "^1.0.1", - "multicast-dns": "^7.2.5" + "@multiformats/multiaddr-matcher": "^3.0.2", + "delay": "^7.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2" } }, - "node_modules/@libp2p/mplex": { - "version": "12.0.29", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-12.0.29.tgz", - "integrity": "sha512-28u86IgMqgs8Y8OfnhV8yVaThD1881PwTIAuuIlwbBRj92Y+LAZbPBxFUJeauIYartT+5u3/PuMJ7wurSocioA==", + "node_modules/@libp2p/dcutr/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/utils": "^7.3.2", - "it-pushable": "^3.2.3", "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/mplex/node_modules/uint8arraylist": { + "node_modules/@libp2p/dcutr/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2074,7 +2404,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/mplex/node_modules/uint8arrays": { + "node_modules/@libp2p/dcutr/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2083,29 +2413,36 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/multistream-select": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-7.0.26.tgz", - "integrity": "sha512-bbKMgu7W/J6eknQODGpySshZq9QWEYbIM/fMYrihbdIT/eS1h+8U6ko2qylfwbDyYlnUoYP45Cqi+oxoNwGH3Q==", + "node_modules/@libp2p/http": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/http/-/http-2.0.6.tgz", + "integrity": "sha512-z3RRdZP/+/kjYREY9MyZ5E90bKrGThQAw9sTZyv0ahxzUM+7I0/JPFLj98Wu0tgI9LStA6vtweVi0Ffwp0u9bA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/utils": "^7.3.2", - "it-length-prefixed": "^11.0.1", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" + "@libp2p/http-fetch": "^4.0.0", + "@libp2p/http-peer-id-auth": "^2.0.0", + "@libp2p/http-utils": "^2.0.0", + "@libp2p/http-websocket": "^2.0.0", + "@libp2p/interface": "^3.2.0", + "@libp2p/interface-internal": "^3.1.0", + "@multiformats/multiaddr": "^13.0.3", + "cookie": "^1.1.1", + "undici": "^8.0.3" } }, - "node_modules/@libp2p/multistream-select/node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "node_modules/@libp2p/http-fetch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/http-fetch/-/http-fetch-4.0.4.tgz", + "integrity": "sha512-MHROqwXP4KnRIZguzUIyrexRWuEF6InXKzUCFktergqgkkkkkt45ZiGx8wOa/1xbdUEZmiDoOITpHsNhU5GWBQ==", "license": "Apache-2.0 OR MIT", "dependencies": { - "uint8arrays": "^6.0.0" + "@achingbrain/http-parser-js": "^0.5.9", + "@libp2p/http-utils": "^2.0.0", + "@libp2p/interface": "^3.2.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/multistream-select/node_modules/uint8arrays": { + "node_modules/@libp2p/http-fetch/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2114,31 +2451,59 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/peer-collections": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-7.0.26.tgz", - "integrity": "sha512-hjJbC/Jo1TjxqaKph2W6ARRnrNoBCEbSQMUsGT17JXQktJc7ImVGLoFy2VsZoJGMX4RLN+gKp6f8McFGb5Vi1w==", + "node_modules/@libp2p/http-peer-id-auth": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@libp2p/http-peer-id-auth/-/http-peer-id-auth-2.0.3.tgz", + "integrity": "sha512-lBzwbnqIJa4SnMssseEtbgdGHB+sH2CQr3HxhSwEhLRRpU1iCmkUR1L9X+FTH6A5Xo62y/JYnnhBO7CG3YTSdg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/crypto": "^5.1.15", + "@libp2p/interface": "^3.2.0", + "@libp2p/peer-id": "^6.0.6", + "uint8-varint": "^3.0.0", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/http-peer-id-auth/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.2.5", - "@libp2p/peer-id": "^6.0.14", - "@libp2p/utils": "^7.3.2", "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/peer-id": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.14.tgz", - "integrity": "sha512-tquBouwbUXgntPtT5q4vfaXbNWmSzh2zo4p2u8IzclVvggXOpmyBlAUNPXDiFqZvQpsxVBrTTemmn0+AwDwecg==", + "node_modules/@libp2p/http-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/http-utils/-/http-utils-2.0.6.tgz", + "integrity": "sha512-WIYT1luITS4AZl0/b3tB5c7Kw9MKHAX2Lumx83oKImBPI9lPpRNafOmVVVRvwfjPq77t5nVt4ha8y1UDEtRN6g==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^5.1.22", - "@libp2p/interface": "^3.2.5", + "@achingbrain/http-parser-js": "^0.5.9", + "@libp2p/interface": "^3.2.0", + "@libp2p/peer-id": "^6.0.6", + "@libp2p/utils": "^7.0.15", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-to-uri": "^12.0.0", + "@multiformats/uri-to-multiaddr": "^10.0.0", + "it-to-browser-readablestream": "^2.0.14", "multiformats": "^14.0.0", + "race-event": "^1.6.1", + "readable-stream": "^4.7.0", + "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/peer-id/node_modules/uint8arrays": { + "node_modules/@libp2p/http-utils/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" + } + }, + "node_modules/@libp2p/http-utils/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2147,35 +2512,25 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/peer-record": { - "version": "9.0.15", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-9.0.15.tgz", - "integrity": "sha512-WZH9Xkdvpr6TlM6eDdE/KZJmk558jrAC/ufNHnbWXdEUxF/GTzx7RsrBgaC6cPGBTNTjzICfO6vZKR2US0Fl9Q==", + "node_modules/@libp2p/http-websocket": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/http-websocket/-/http-websocket-2.0.4.tgz", + "integrity": "sha512-AEATJ6MERtxY/UZkFWCgNpB7bhcVTmeeBKZkVl2RJbVYTk8CSv3fO3uXLcYFQ9W4rOa0eG4Gvr/USBU+41xB8A==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^5.1.22", - "@libp2p/interface": "^3.2.5", - "@libp2p/peer-id": "^6.0.14", + "@achingbrain/http-parser-js": "^0.5.9", + "@libp2p/http-utils": "^2.0.0", + "@libp2p/interface": "^3.2.0", + "@libp2p/interface-internal": "^3.1.0", + "@libp2p/utils": "^7.0.15", "@multiformats/multiaddr": "^13.0.3", "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8-varint": "^3.0.0", + "race-event": "^1.6.1", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/peer-record/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/peer-record/node_modules/uint8arraylist": { + "node_modules/@libp2p/http-websocket/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2184,7 +2539,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/peer-record/node_modules/uint8arrays": { + "node_modules/@libp2p/http-websocket/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2193,29 +2548,29 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/peer-store": { - "version": "12.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-12.0.26.tgz", - "integrity": "sha512-VoLjd7SQrYN45w3pnGl8YQQn+9tzMsoZxdmSG7VWoMiQhMLyxCtkctsVi9OCdT1IkdelPRq84KHklkK0D4FG9w==", + "node_modules/@libp2p/identify": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-4.1.12.tgz", + "integrity": "sha512-nNBnxSclPVsvRsaLlwX9cz7gO020KjWEsaSzqDryWz6VStbLLdcwJYkVUP3do7VoFHkkF8PMQkScW7XCA27ZOA==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.1.22", "@libp2p/interface": "^3.2.5", - "@libp2p/peer-collections": "^7.0.26", + "@libp2p/interface-internal": "^3.1.11", "@libp2p/peer-id": "^6.0.14", "@libp2p/peer-record": "^9.0.15", + "@libp2p/utils": "^7.3.2", "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "it-all": "^3.0.9", + "@multiformats/multiaddr-matcher": "^3.0.2", + "it-drain": "^3.0.10", + "it-parallel": "^3.0.13", "main-event": "^1.0.1", - "mortice": "^3.3.1", - "multiformats": "^14.0.0", "protons-runtime": "^7.0.0", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/peer-store/node_modules/protons-runtime": { + "node_modules/@libp2p/identify/node_modules/protons-runtime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", @@ -2226,7 +2581,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/peer-store/node_modules/uint8arraylist": { + "node_modules/@libp2p/identify/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2235,7 +2590,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/peer-store/node_modules/uint8arrays": { + "node_modules/@libp2p/identify/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2244,21 +2599,33 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/ping": { + "node_modules/@libp2p/interface": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", + "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^13.0.3", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "progress-events": "^1.1.0", + "uint8arraylist": "^3.0.2" + } + }, + "node_modules/@libp2p/interface-internal": { "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-3.1.11.tgz", - "integrity": "sha512-3ByJjpzxMAEJv8ZjWeLYv9k0Ba3eVxPRcWc8H79/RQhTQwY2zZvHYX4gQUmyqA9Sv0Q6peL4TwUW7E3wgN27Mw==", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-3.1.11.tgz", + "integrity": "sha512-lHJueWKdfkf/nrbVkGNUWrqDvF3T9Brn4hyONQmhTqG2mWyWKntRR/CkPjVgMRDSF2fuyzDJGg+n9tC0Gphk8g==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "p-event": "^7.0.0", - "race-signal": "^2.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" + "@libp2p/peer-collections": "^7.0.26", + "@multiformats/multiaddr": "^13.0.3", + "progress-events": "^1.0.1" } }, - "node_modules/@libp2p/ping/node_modules/uint8arraylist": { + "node_modules/@libp2p/interface/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2267,7 +2634,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/ping/node_modules/uint8arrays": { + "node_modules/@libp2p/interface/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2276,18 +2643,46 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/record": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.15.tgz", - "integrity": "sha512-EaHFtQAlZuM9vCpd+0KnZhTVgV26Uzje7DkyUseGNKTy+Wit9Q94C/+wDThpZuRHnK1FH/CvPxJ4kLj7AoQ1WA==", + "node_modules/@libp2p/kad-dht": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-16.4.3.tgz", + "integrity": "sha512-i3FDNr9XLHcl8X27MRWrra3UyZw4o1esIen+66CNwFzZOTe85ndv6zNDOpgd2di1JT+E/xhKrghd+0wO3zfC6w==", "license": "Apache-2.0 OR MIT", "dependencies": { + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-collections": "^7.0.26", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/ping": "^3.1.11", + "@libp2p/record": "^4.0.15", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "any-signal": "^4.1.1", + "interface-datastore": "^10.0.1", + "it-all": "^3.0.9", + "it-drain": "^3.0.10", + "it-length": "^3.0.9", + "it-map": "^3.1.4", + "it-merge": "^3.0.12", + "it-parallel": "^3.0.13", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-take": "^3.0.9", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.0.1", "protons-runtime": "^7.0.0", + "race-signal": "^2.0.0", + "uint8-varint": "^3.0.0", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/record/node_modules/protons-runtime": { + "node_modules/@libp2p/kad-dht/node_modules/protons-runtime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", @@ -2298,7 +2693,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/record/node_modules/uint8arraylist": { + "node_modules/@libp2p/kad-dht/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2307,7 +2702,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/record/node_modules/uint8arrays": { + "node_modules/@libp2p/kad-dht/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2316,23 +2711,76 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/tcp": { - "version": "11.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-11.0.26.tgz", - "integrity": "sha512-OOLb1Ov6zQRo983fEPRUvfcWmFrm2oJlwPgf7M6pzLyQOsTxpgb2MV37Az8ANy5GEAG4JJ+KPZn3g7JfEp33xA==", + "node_modules/@libp2p/keychain": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@libp2p/keychain/-/keychain-6.1.6.tgz", + "integrity": "sha512-G4xT3A8M5yiOJAcYT4YTnq9Gd3HOzHOIig1/KzcXzxaeuYCwXOmzreqUYabuEMmExkn966g5jiJ7RkCDQWO3UA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@noble/hashes": "^2.0.1", + "asn1js": "^3.0.6", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.0", + "sanitize-filename": "^1.6.3", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/keychain/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } + }, + "node_modules/@libp2p/logger": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.12.tgz", + "integrity": "sha512-FwCdJ5hGFDvWI/SULZ4Iez+k+JtdMAj4/EK6bWmkh08ORIztwKpt0IXCOe8nIfKhBKwLYJu00BnZDRsedX7+7Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.2.5", + "@multiformats/multiaddr": "^13.0.3", + "interface-datastore": "^10.0.1", + "multiformats": "^14.0.0", + "weald": "^1.1.0" + } + }, + "node_modules/@libp2p/mdns": { + "version": "12.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/mdns/-/mdns-12.0.29.tgz", + "integrity": "sha512-EiDvAm0rgxlGDTeWFpBBX7IL8tE/Yxq3GyqGpYYdySSHqtbkKLxU7v/kf1uBRqbItQN5xgag8kFTpkr+89xHZQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-id": "^6.0.14", "@libp2p/utils": "^7.3.2", "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", + "@types/multicast-dns": "^7.2.4", + "dns-packet": "^5.6.1", "main-event": "^1.0.1", - "p-event": "^7.0.0", - "progress-events": "^1.0.1", - "uint8arraylist": "^3.0.2" + "multicast-dns": "^7.2.5" } }, - "node_modules/@libp2p/tcp/node_modules/uint8arraylist": { + "node_modules/@libp2p/mplex": { + "version": "12.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-12.0.29.tgz", + "integrity": "sha512-28u86IgMqgs8Y8OfnhV8yVaThD1881PwTIAuuIlwbBRj92Y+LAZbPBxFUJeauIYartT+5u3/PuMJ7wurSocioA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "it-pushable": "^3.2.3", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/mplex/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2341,7 +2789,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/tcp/node_modules/uint8arrays": { + "node_modules/@libp2p/mplex/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2350,40 +2798,20 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/tls": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@libp2p/tls/-/tls-3.1.8.tgz", - "integrity": "sha512-Orch13vBdr6EgsNLMCnBWTW7ff0+LwgdC3PTQyubalTxzFiYGfC3BZgugYU+L36GSOKMRR3QML/GzMocKegRUQ==", + "node_modules/@libp2p/multistream-select": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-7.0.26.tgz", + "integrity": "sha512-bbKMgu7W/J6eknQODGpySshZq9QWEYbIM/fMYrihbdIT/eS1h+8U6ko2qylfwbDyYlnUoYP45Cqi+oxoNwGH3Q==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^5.1.22", "@libp2p/interface": "^3.2.5", - "@libp2p/peer-id": "^6.0.14", "@libp2p/utils": "^7.3.2", - "@peculiar/asn1-schema": "^2.4.0", - "@peculiar/asn1-x509": "^2.4.0", - "@peculiar/webcrypto": "^1.5.0", - "@peculiar/x509": "^2.0.0", - "asn1js": "^3.0.6", - "p-event": "^7.0.0", - "protons-runtime": "^7.0.0", - "reflect-metadata": "^0.2.2", + "it-length-prefixed": "^11.0.1", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/tls/node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, - "node_modules/@libp2p/tls/node_modules/uint8arraylist": { + "node_modules/@libp2p/multistream-select/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2392,7 +2820,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/tls/node_modules/uint8arrays": { + "node_modules/@libp2p/multistream-select/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2401,58 +2829,57 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/upnp-nat": { - "version": "4.0.26", - "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-4.0.26.tgz", - "integrity": "sha512-WFWRwvAZVU437flozWEzjPqSNHwlkczmd/aoeR1VpQ0h4MFWJySlSTuUBvS/1dypDDRiSrtGAesMFW28Dc4buQ==", + "node_modules/@libp2p/peer-collections": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-7.0.26.tgz", + "integrity": "sha512-hjJbC/Jo1TjxqaKph2W6ARRnrNoBCEbSQMUsGT17JXQktJc7ImVGLoFy2VsZoJGMX4RLN+gKp6f8McFGb5Vi1w==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@achingbrain/nat-port-mapper": "^4.0.4", - "@chainsafe/is-ip": "^2.1.0", "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", + "@libp2p/peer-id": "^6.0.14", "@libp2p/utils": "^7.3.2", - "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "main-event": "^1.0.1", - "p-defer": "^4.0.1", - "race-signal": "^2.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/utils": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-7.3.2.tgz", - "integrity": "sha512-nVfJkTE3QMa/LVKVD5sKDB8birKua0BR0oKoqnLSXsX1ez89PvhR1zZjBfARSGVZdv5qUmblcdcvjJ76HD0CWA==", + "node_modules/@libp2p/peer-id": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.14.tgz", + "integrity": "sha512-tquBouwbUXgntPtT5q4vfaXbNWmSzh2zo4p2u8IzclVvggXOpmyBlAUNPXDiFqZvQpsxVBrTTemmn0+AwDwecg==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@chainsafe/is-ip": "^2.1.0", - "@chainsafe/netmask": "^2.0.0", + "@libp2p/crypto": "^5.1.22", "@libp2p/interface": "^3.2.5", - "@libp2p/logger": "^6.2.12", + "multiformats": "^14.0.0", + "uint8arrays": "^6.1.1" + } + }, + "node_modules/@libp2p/peer-id/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } + }, + "node_modules/@libp2p/peer-record": { + "version": "9.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-9.0.15.tgz", + "integrity": "sha512-WZH9Xkdvpr6TlM6eDdE/KZJmk558jrAC/ufNHnbWXdEUxF/GTzx7RsrBgaC6cPGBTNTjzICfO6vZKR2US0Fl9Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "@sindresorhus/fnv1a": "^3.1.0", - "any-signal": "^4.1.1", - "cborg": "^6.0.0", - "delay": "^7.0.0", - "is-loopback-addr": "^2.0.2", - "it-length-prefixed": "^11.0.1", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "netmask": "^2.0.2", - "p-defer": "^4.0.1", - "p-event": "^7.0.0", - "progress-events": "^1.1.0", + "multiformats": "^14.0.0", "protons-runtime": "^7.0.0", - "race-signal": "^2.0.0", "uint8-varint": "^3.0.0", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/utils/node_modules/protons-runtime": { + "node_modules/@libp2p/peer-record/node_modules/protons-runtime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", @@ -2463,7 +2890,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/utils/node_modules/uint8arraylist": { + "node_modules/@libp2p/peer-record/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2472,7 +2899,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/utils/node_modules/uint8arrays": { + "node_modules/@libp2p/peer-record/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2481,48 +2908,29 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/webrtc": { - "version": "6.0.29", - "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-6.0.29.tgz", - "integrity": "sha512-RzsDn4+d/PhHIplWu7rDWMMpZtkF6kFB+fZayUZQxsApxuL3TdNF/mBR93Y5nEY1GswmAERQzTPr0si91xTXOw==", + "node_modules/@libp2p/peer-store": { + "version": "12.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-12.0.26.tgz", + "integrity": "sha512-VoLjd7SQrYN45w3pnGl8YQQn+9tzMsoZxdmSG7VWoMiQhMLyxCtkctsVi9OCdT1IkdelPRq84KHklkK0D4FG9w==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@chainsafe/is-ip": "^2.1.0", - "@chainsafe/libp2p-noise": "^17.0.0", "@libp2p/crypto": "^5.1.22", "@libp2p/interface": "^3.2.5", - "@libp2p/interface-internal": "^3.1.11", - "@libp2p/keychain": "^6.1.6", + "@libp2p/peer-collections": "^7.0.26", "@libp2p/peer-id": "^6.0.14", - "@libp2p/utils": "^7.3.2", + "@libp2p/peer-record": "^9.0.15", "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "@peculiar/webcrypto": "^1.5.0", - "@peculiar/x509": "^2.0.0", - "get-port": "^7.1.0", "interface-datastore": "^10.0.1", - "it-length-prefixed": "^11.0.1", - "it-protobuf-stream": "^3.0.0", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", + "it-all": "^3.0.9", "main-event": "^1.0.1", + "mortice": "^3.3.1", "multiformats": "^14.0.0", - "node-datachannel": "^0.32.3", - "p-defer": "^4.0.1", - "p-event": "^7.0.0", - "p-timeout": "^7.0.0", - "p-wait-for": "^6.0.0", - "progress-events": "^1.0.1", "protons-runtime": "^7.0.0", - "race-signal": "^2.0.0", - "react-native-webrtc": "^124.0.6", - "reflect-metadata": "^0.2.2", - "uint8-varint": "^3.0.0", "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/webrtc/node_modules/protons-runtime": { + "node_modules/@libp2p/peer-store/node_modules/protons-runtime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", @@ -2533,7 +2941,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/webrtc/node_modules/uint8arraylist": { + "node_modules/@libp2p/peer-store/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2542,7 +2950,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/webrtc/node_modules/uint8arrays": { + "node_modules/@libp2p/peer-store/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2551,26 +2959,21 @@ "multiformats": "^14.0.0" } }, - "node_modules/@libp2p/websockets": { - "version": "10.1.19", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-10.1.19.tgz", - "integrity": "sha512-inY5dWhm+mCrnS/wcHIWtDZ908c1W7UkpXE9i6uvnJVapWbvonvX9GEsTLvh4NbL7SOfnOHGkwDVgx1M2RxcRA==", + "node_modules/@libp2p/ping": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-3.1.11.tgz", + "integrity": "sha512-3ByJjpzxMAEJv8ZjWeLYv9k0Ba3eVxPRcWc8H79/RQhTQwY2zZvHYX4gQUmyqA9Sv0Q6peL4TwUW7E3wgN27Mw==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^3.2.5", - "@libp2p/utils": "^7.3.2", - "@multiformats/multiaddr": "^13.0.3", - "@multiformats/multiaddr-matcher": "^3.0.2", - "@multiformats/multiaddr-to-uri": "^12.0.0", - "main-event": "^1.0.1", + "@libp2p/interface-internal": "^3.1.11", "p-event": "^7.0.0", - "progress-events": "^1.0.1", + "race-signal": "^2.0.0", "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1", - "ws": "^8.18.3" + "uint8arrays": "^6.1.1" } }, - "node_modules/@libp2p/websockets/node_modules/uint8arraylist": { + "node_modules/@libp2p/ping/node_modules/uint8arraylist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", @@ -2579,7 +2982,7 @@ "uint8arrays": "^6.0.0" } }, - "node_modules/@libp2p/websockets/node_modules/uint8arrays": { + "node_modules/@libp2p/ping/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2588,21 +2991,38 @@ "multiformats": "^14.0.0" } }, - "node_modules/@multiformats/dns": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", - "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", + "node_modules/@libp2p/record": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@libp2p/record/-/record-4.0.15.tgz", + "integrity": "sha512-EaHFtQAlZuM9vCpd+0KnZhTVgV26Uzje7DkyUseGNKTy+Wit9Q94C/+wDThpZuRHnK1FH/CvPxJ4kLj7AoQ1WA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", + "protons-runtime": "^7.0.0", + "uint8arraylist": "^3.0.2", "uint8arrays": "^6.1.1" } }, - "node_modules/@multiformats/dns/node_modules/uint8arrays": { + "node_modules/@libp2p/record/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" + } + }, + "node_modules/@libp2p/record/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^6.0.0" + } + }, + "node_modules/@libp2p/record/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2611,37 +3031,32 @@ "multiformats": "^14.0.0" } }, - "node_modules/@multiformats/multiaddr": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", - "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", + "node_modules/@libp2p/tcp": { + "version": "11.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/tcp/-/tcp-11.0.26.tgz", + "integrity": "sha512-OOLb1Ov6zQRo983fEPRUvfcWmFrm2oJlwPgf7M6pzLyQOsTxpgb2MV37Az8ANy5GEAG4JJ+KPZn3g7JfEp33xA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^14.0.0", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "main-event": "^1.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.0.1", + "uint8arraylist": "^3.0.2" } }, - "node_modules/@multiformats/multiaddr-matcher": { + "node_modules/@libp2p/tcp/node_modules/uint8arraylist": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-3.0.2.tgz", - "integrity": "sha512-iphGQJliZxe2yKu57bdRDgeS+3znc5uXtMybDO1Wau3rIjas4zjrjlyxmFz3wqyUL9f3VDQwas/ZqA7N4QeSfw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^13.0.0" - } - }, - "node_modules/@multiformats/multiaddr-to-uri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-12.0.0.tgz", - "integrity": "sha512-3uIEBCiy8tfzxYYBl81x1tISiNBQ7mHU4pGjippbJRoQYHzy/ZdZM/7JvTldr8pc/dzpkaNJxnsuxxlhsPOJsA==", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@multiformats/multiaddr": "^13.0.0" + "uint8arrays": "^6.0.0" } }, - "node_modules/@multiformats/multiaddr/node_modules/uint8arrays": { + "node_modules/@libp2p/tcp/node_modules/uint8arrays": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", @@ -2650,583 +3065,1379 @@ "multiformats": "^14.0.0" } }, - "node_modules/@multiformats/murmur3": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.8.tgz", - "integrity": "sha512-CrsYnnoWbTYPkLl9D6zKaFPlRbnOekofvi93/1IrXc5eaT5QChRscEFV93kVqHwL2phi8u1hj9UnaeFOq2PYMQ==", + "node_modules/@libp2p/tls": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@libp2p/tls/-/tls-3.1.8.tgz", + "integrity": "sha512-Orch13vBdr6EgsNLMCnBWTW7ff0+LwgdC3PTQyubalTxzFiYGfC3BZgugYU+L36GSOKMRR3QML/GzMocKegRUQ==", "license": "Apache-2.0 OR MIT", "dependencies": { - "multiformats": "^14.0.0" + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "@peculiar/asn1-schema": "^2.4.0", + "@peculiar/asn1-x509": "^2.4.0", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^2.0.0", + "asn1js": "^3.0.6", + "p-event": "^7.0.0", + "protons-runtime": "^7.0.0", + "reflect-metadata": "^0.2.2", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@multiformats/uri-to-multiaddr": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@multiformats/uri-to-multiaddr/-/uri-to-multiaddr-10.0.0.tgz", - "integrity": "sha512-QsmwLmY6iB1wDU1e1wyctqF0eP/2KD1QPLQ+APISuqETbCTSpaq159S/K/ssmWlBpSEkhH0SUfBUgGi014Ttfw==", + "node_modules/@libp2p/tls/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@multiformats/multiaddr": "^13.0.0", - "is-ip": "^5.0.0" - } - }, - "node_modules/@noble/ciphers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.3.0.tgz", - "integrity": "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@noble/curves": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", - "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", - "license": "MIT", + "node_modules/@libp2p/tls/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@noble/hashes": "2.3.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "uint8arrays": "^6.0.0" } }, - "node_modules/@noble/hashes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", - "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node_modules/@libp2p/tls/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", + "node_modules/@libp2p/upnp-nat": { + "version": "4.0.26", + "resolved": "https://registry.npmjs.org/@libp2p/upnp-nat/-/upnp-nat-4.0.26.tgz", + "integrity": "sha512-WFWRwvAZVU437flozWEzjPqSNHwlkczmd/aoeR1VpQ0h4MFWJySlSTuUBvS/1dypDDRiSrtGAesMFW28Dc4buQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "@achingbrain/nat-port-mapper": "^4.0.4", + "@chainsafe/is-ip": "^2.1.0", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "main-event": "^1.0.1", + "p-defer": "^4.0.1", + "race-signal": "^2.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" + "node_modules/@libp2p/utils": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-7.3.2.tgz", + "integrity": "sha512-nVfJkTE3QMa/LVKVD5sKDB8birKua0BR0oKoqnLSXsX1ez89PvhR1zZjBfARSGVZdv5qUmblcdcvjJ76HD0CWA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.1.0", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/interface": "^3.2.5", + "@libp2p/logger": "^6.2.12", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@sindresorhus/fnv1a": "^3.1.0", + "any-signal": "^4.1.1", + "cborg": "^6.0.0", + "delay": "^7.0.0", + "is-loopback-addr": "^2.0.2", + "it-length-prefixed": "^11.0.1", + "it-pipe": "^3.0.1", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "netmask": "^2.0.2", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.1.0", + "protons-runtime": "^7.0.0", + "race-signal": "^2.0.0", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", + "node_modules/@libp2p/utils/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@peculiar/asn1-cms": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.9.4.tgz", - "integrity": "sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw==", - "license": "MIT", + "node_modules/@libp2p/utils/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "@peculiar/asn1-x509-attr": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "uint8arrays": "^6.0.0" } }, - "node_modules/@peculiar/asn1-csr": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.9.4.tgz", - "integrity": "sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw==", - "license": "MIT", + "node_modules/@libp2p/utils/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "multiformats": "^14.0.0" } }, - "node_modules/@peculiar/asn1-ecc": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.9.4.tgz", - "integrity": "sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ==", - "license": "MIT", + "node_modules/@libp2p/webrtc": { + "version": "6.0.29", + "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-6.0.29.tgz", + "integrity": "sha512-RzsDn4+d/PhHIplWu7rDWMMpZtkF6kFB+fZayUZQxsApxuL3TdNF/mBR93Y5nEY1GswmAERQzTPr0si91xTXOw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "@chainsafe/is-ip": "^2.1.0", + "@chainsafe/libp2p-noise": "^17.0.0", + "@libp2p/crypto": "^5.1.22", + "@libp2p/interface": "^3.2.5", + "@libp2p/interface-internal": "^3.1.11", + "@libp2p/keychain": "^6.1.6", + "@libp2p/peer-id": "^6.0.14", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^2.0.0", + "get-port": "^7.1.0", + "interface-datastore": "^10.0.1", + "it-length-prefixed": "^11.0.1", + "it-protobuf-stream": "^3.0.0", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "multiformats": "^14.0.0", + "node-datachannel": "^0.32.3", + "p-defer": "^4.0.1", + "p-event": "^7.0.0", + "p-timeout": "^7.0.0", + "p-wait-for": "^6.0.0", + "progress-events": "^1.0.1", + "protons-runtime": "^7.0.0", + "race-signal": "^2.0.0", + "react-native-webrtc": "^124.0.6", + "reflect-metadata": "^0.2.2", + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1" } }, - "node_modules/@peculiar/asn1-pfx": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.9.4.tgz", - "integrity": "sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw==", - "license": "MIT", + "node_modules/@libp2p/webrtc/node_modules/protons-runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", + "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.9.4", - "@peculiar/asn1-pkcs8": "^2.9.4", - "@peculiar/asn1-rsa": "^2.9.4", - "@peculiar/asn1-schema": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "uint8-varint": "^3.0.0", + "uint8arraylist": "^3.0.0", + "uint8arrays": "^6.0.0" } }, - "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.9.4.tgz", - "integrity": "sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ==", - "license": "MIT", + "node_modules/@libp2p/webrtc/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "uint8arrays": "^6.0.0" } }, - "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.9.4.tgz", - "integrity": "sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ==", - "license": "MIT", + "node_modules/@libp2p/webrtc/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.9.4", - "@peculiar/asn1-pfx": "^2.9.4", - "@peculiar/asn1-pkcs8": "^2.9.4", - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "@peculiar/asn1-x509-attr": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "multiformats": "^14.0.0" } }, - "node_modules/@peculiar/asn1-rsa": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.9.4.tgz", - "integrity": "sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA==", - "license": "MIT", + "node_modules/@libp2p/websockets": { + "version": "10.1.19", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-10.1.19.tgz", + "integrity": "sha512-inY5dWhm+mCrnS/wcHIWtDZ908c1W7UkpXE9i6uvnJVapWbvonvX9GEsTLvh4NbL7SOfnOHGkwDVgx1M2RxcRA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "@libp2p/interface": "^3.2.5", + "@libp2p/utils": "^7.3.2", + "@multiformats/multiaddr": "^13.0.3", + "@multiformats/multiaddr-matcher": "^3.0.2", + "@multiformats/multiaddr-to-uri": "^12.0.0", + "main-event": "^1.0.1", + "p-event": "^7.0.0", + "progress-events": "^1.0.1", + "uint8arraylist": "^3.0.2", + "uint8arrays": "^6.1.1", + "ws": "^8.18.3" } }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", - "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", - "license": "MIT", + "node_modules/@libp2p/websockets/node_modules/uint8arraylist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", + "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/utils": "^2.0.2", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "uint8arrays": "^6.0.0" } }, - "node_modules/@peculiar/asn1-x509": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.9.4.tgz", - "integrity": "sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q==", - "license": "MIT", + "node_modules/@libp2p/websockets/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/utils": "^2.0.2", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "multiformats": "^14.0.0" } }, - "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.9.4.tgz", - "integrity": "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==", - "license": "MIT", + "node_modules/@multiformats/dns": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", + "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "@dnsquery/dns-packet": "^6.1.1", + "@libp2p/interface": "^3.1.0", + "hashlru": "^2.3.0", + "p-queue": "^9.0.0", + "progress-events": "^1.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "license": "MIT", + "node_modules/@multiformats/dns/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=8.0.0" + "multiformats": "^14.0.0" } }, - "node_modules/@peculiar/utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", - "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", - "license": "MIT", + "node_modules/@multiformats/multiaddr": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", + "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "tslib": "^2.8.1" + "@chainsafe/is-ip": "^2.0.1", + "multiformats": "^14.0.0", + "uint8-varint": "^3.0.0", + "uint8arrays": "^6.1.1" } }, - "node_modules/@peculiar/webcrypto": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", - "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", - "license": "MIT", + "node_modules/@multiformats/multiaddr-matcher": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-3.0.2.tgz", + "integrity": "sha512-iphGQJliZxe2yKu57bdRDgeS+3znc5uXtMybDO1Wau3rIjas4zjrjlyxmFz3wqyUL9f3VDQwas/ZqA7N4QeSfw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.7.0", - "@peculiar/json-schema": "^1.1.12", - "@peculiar/utils": "^2.0.2", - "tslib": "^2.8.1", - "webcrypto-core": "^1.9.2" - }, - "engines": { - "node": ">=14.18.0" + "@multiformats/multiaddr": "^13.0.0" } }, - "node_modules/@peculiar/x509": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-2.0.0.tgz", - "integrity": "sha512-r10lkuy6BNfRmyYdRAfgu6dq0HOmyIV2OLhXWE3gDEPBdX1b8miztJVyX/UxWhLwemNyDP3CLZHpDxDwSY0xaA==", - "license": "MIT", + "node_modules/@multiformats/multiaddr-to-uri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-12.0.0.tgz", + "integrity": "sha512-3uIEBCiy8tfzxYYBl81x1tISiNBQ7mHU4pGjippbJRoQYHzy/ZdZM/7JvTldr8pc/dzpkaNJxnsuxxlhsPOJsA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-csr": "^2.6.0", - "@peculiar/asn1-ecc": "^2.6.0", - "@peculiar/asn1-pkcs9": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "pvtsutils": "^1.3.6", - "tslib": "^2.8.1", - "tsyringe": "^4.10.0" - }, - "engines": { - "node": ">=20.0.0" + "@multiformats/multiaddr": "^13.0.0" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" + "node_modules/@multiformats/multiaddr/node_modules/uint8arrays": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", + "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } }, - "node_modules/@react-native/asset-utils": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/asset-utils/-/asset-utils-0.87.0.tgz", - "integrity": "sha512-XUSXMnBmQuBMuMWNWaXMGt4tLzb2yraCVO4b7TFCvEqiCg3ByhbQA0QNmPp+cdWgvmPmptCJwOrBWjPxppXgOA==", + "node_modules/@multiformats/murmur3": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.8.tgz", + "integrity": "sha512-CrsYnnoWbTYPkLl9D6zKaFPlRbnOekofvi93/1IrXc5eaT5QChRscEFV93kVqHwL2phi8u1hj9UnaeFOq2PYMQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^14.0.0" + } + }, + "node_modules/@multiformats/uri-to-multiaddr": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@multiformats/uri-to-multiaddr/-/uri-to-multiaddr-10.0.0.tgz", + "integrity": "sha512-QsmwLmY6iB1wDU1e1wyctqF0eP/2KD1QPLQ+APISuqETbCTSpaq159S/K/ssmWlBpSEkhH0SUfBUgGi014Ttfw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/multiaddr": "^13.0.0", + "is-ip": "^5.0.0" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "peer": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + "node": "^22.20 || ^24.12 || >=25" } }, - "node_modules/@react-native/codegen": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.87.0.tgz", - "integrity": "sha512-odcBGR0A9Ee83A/XvzOp9juGRkSQOQcI4ZYs+3H20MySMxvZJ34/2nVHoxdmj0YY0Mn6gF8BjLzYkL0axbHuCA==", + "node_modules/@noble/ciphers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.3.0.tgz", + "integrity": "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw==", "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.29.0", - "hermes-parser": "0.36.1", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, "engines": { - "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + "node": ">= 20.19.0" }, - "peerDependencies": { - "@babel/core": "*" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@react-native/codegen/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "peer": true, + "node_modules/@noble/curves": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@noble/hashes": "2.3.0" }, "engines": { - "node": ">=12" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@react-native/codegen/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/@noble/hashes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "license": "MIT", - "peer": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/@react-native/codegen/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", - "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/@react-native/codegen/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/@peculiar/asn1-cms": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.9.4.tgz", + "integrity": "sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw==", "license": "MIT", - "peer": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-attr": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=14" } }, - "node_modules/@react-native/codegen/node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "node_modules/@peculiar/asn1-csr": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.9.4.tgz", + "integrity": "sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw==", "license": "MIT", - "peer": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/@react-native/codegen/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "peer": true, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.9.4.tgz", + "integrity": "sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.87.0.tgz", - "integrity": "sha512-Voaq6e6aSjLsFmOVofP1RHM0wD0Wo51AlIheIDAzXqgBKwEHEz2na4swNZ6sjWLZH/4Wm2ydy+8A/6RCyfht6w==", + "node_modules/@peculiar/asn1-pfx": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.9.4.tgz", + "integrity": "sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw==", "license": "MIT", - "peer": true, "dependencies": { - "@react-native/asset-utils": "0.87.0", - "@react-native/dev-middleware": "0.87.0", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "metro": "^0.87.0", - "semver": "^7.1.3" + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-pkcs8": "^2.9.4", + "@peculiar/asn1-rsa": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.9.4.tgz", + "integrity": "sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.9.4.tgz", + "integrity": "sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-pfx": "^2.9.4", + "@peculiar/asn1-pkcs8": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-attr": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.9.4.tgz", + "integrity": "sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.9.4.tgz", + "integrity": "sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.9.4.tgz", + "integrity": "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@peculiar/x509": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-2.0.0.tgz", + "integrity": "sha512-r10lkuy6BNfRmyYdRAfgu6dq0HOmyIV2OLhXWE3gDEPBdX1b8miztJVyX/UxWhLwemNyDP3CLZHpDxDwSY0xaA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@react-native/asset-utils": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/asset-utils/-/asset-utils-0.87.0.tgz", + "integrity": "sha512-XUSXMnBmQuBMuMWNWaXMGt4tLzb2yraCVO4b7TFCvEqiCg3ByhbQA0QNmPp+cdWgvmPmptCJwOrBWjPxppXgOA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.87.0.tgz", + "integrity": "sha512-odcBGR0A9Ee83A/XvzOp9juGRkSQOQcI4ZYs+3H20MySMxvZJ34/2nVHoxdmj0YY0Mn6gF8BjLzYkL0axbHuCA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.1", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/codegen/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/codegen/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/codegen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@react-native/codegen/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/codegen/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.87.0.tgz", + "integrity": "sha512-Voaq6e6aSjLsFmOVofP1RHM0wD0Wo51AlIheIDAzXqgBKwEHEz2na4swNZ6sjWLZH/4Wm2ydy+8A/6RCyfht6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/asset-utils": "0.87.0", + "@react-native/dev-middleware": "0.87.0", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.87.0", + "semver": "^7.1.3" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.87.0" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.87.0.tgz", + "integrity": "sha512-TSUVjiX1cYganE6DcWVblDxs67x96u8/KqwhiHHViR6hGGjSIfIdNQcRLOEdXRpucuHB0awIFepBlw+34nTSUg==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.87.0.tgz", + "integrity": "sha512-8Om5Qm8Ln9zGZ93DJj7gZtlfT0y1wAFc/iWr3GFkIXSa6zwmC3DGzm4sm5eBTtdMl27EXmKACIOGsqztPH3Cvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.87.0.tgz", + "integrity": "sha512-Fy4RMTCcAn402KGgxQZ4CwoURJdVkoPVxUbN7OTf9xy9xCOmBsOQQ+Xs1n6ULkNMUGcL3ptajpb39ual6r5ixg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.87.0", + "@react-native/debugger-shell": "0.87.0", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" }, "engines": { "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" }, "peerDependencies": { - "@react-native-community/cli": "*", - "@react-native/metro-config": "0.87.0" + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { - "@react-native-community/cli": { + "bufferutil": { "optional": true }, - "@react-native/metro-config": { + "utf-8-validate": { "optional": true } } }, - "node_modules/@react-native/debugger-frontend": { + "node_modules/@react-native/gradle-plugin": { "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.87.0.tgz", - "integrity": "sha512-TSUVjiX1cYganE6DcWVblDxs67x96u8/KqwhiHHViR6hGGjSIfIdNQcRLOEdXRpucuHB0awIFepBlw+34nTSUg==", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.87.0.tgz", + "integrity": "sha512-CFOhPsxN4yS6vYjShSGKQiJHDPQFKmbiJG17zZfJuHTvJiaWz7JkpznE/P1RRKXdgvCmACrj31vxJiCHLlI7Gg==", + "license": "MIT", "peer": true, "engines": { "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native/debugger-shell": { + "node_modules/@react-native/normalize-colors": { "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.87.0.tgz", - "integrity": "sha512-8Om5Qm8Ln9zGZ93DJj7gZtlfT0y1wAFc/iWr3GFkIXSa6zwmC3DGzm4sm5eBTtdMl27EXmKACIOGsqztPH3Cvg==", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.87.0.tgz", + "integrity": "sha512-7aEGyrrquUqpbp+aEgOCp2xr0cTLYhJFCMfUqI4IoJddfwosra9Y5qtbrJU5Z/vBLndCmSIzABrWnHK0lxpwcA==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.87.0.tgz", + "integrity": "sha512-rYQCtZupN7Iczm8fdGg3uKZUXhrxdZPRkg4BZK3RaQJ2HJ7n5tJNR2QPR3vSgpp5CATQggiGCuom1fNEC3T/8g==", "license": "MIT", "peer": true, "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" }, "engines": { "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.87.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" } }, - "node_modules/@react-native/dev-middleware": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.87.0.tgz", - "integrity": "sha512-Fy4RMTCcAn402KGgxQZ4CwoURJdVkoPVxUbN7OTf9xy9xCOmBsOQQ+Xs1n6ULkNMUGcL3ptajpb39ual6r5ixg==", + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.87.0", - "@react-native/debugger-shell": "0.87.0", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.3.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^7.5.10" - }, - "engines": { - "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", - "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "dependencies": { + "@shikijs/types": "2.5.0" } }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.87.0.tgz", - "integrity": "sha512-CFOhPsxN4yS6vYjShSGKQiJHDPQFKmbiJG17zZfJuHTvJiaWz7JkpznE/P1RRKXdgvCmACrj31vxJiCHLlI7Gg==", + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + "dependencies": { + "@shikijs/types": "2.5.0" } }, - "node_modules/@react-native/normalize-colors": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.87.0.tgz", - "integrity": "sha512-7aEGyrrquUqpbp+aEgOCp2xr0cTLYhJFCMfUqI4IoJddfwosra9Y5qtbrJU5Z/vBLndCmSIzABrWnHK0lxpwcA==", + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.87.0.tgz", - "integrity": "sha512-rYQCtZupN7Iczm8fdGg3uKZUXhrxdZPRkg4BZK3RaQJ2HJ7n5tJNR2QPR3vSgpp5CATQggiGCuom1fNEC3T/8g==", + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" - }, - "peerDependencies": { - "@types/react": "^19.2.0", - "react": "*", - "react-native": "0.87.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" } }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@simple-libs/child-process-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", @@ -3355,6 +4566,16 @@ "@types/send": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -3396,12 +4617,47 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/luxon": { "version": "3.7.5", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.5.tgz", "integrity": "sha512-jJ41Q4z6ZVO260MNDdHfW7+7a5iMiX8Mr6ZJHcmgrvhZha6dz5704o/lF2kKl6URjH6ivEL97w9xS/MgpJEphg==", "license": "MIT" }, + "node_modules/@types/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -3485,6 +4741,20 @@ "@types/node": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -3714,22 +4984,292 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", - "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.42", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "vue": "^3.5.13" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/antfu" } }, "node_modules/abort-controller": { @@ -3852,6 +5392,32 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/algoliasearch": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.57.0.tgz", + "integrity": "sha512-HpND7MBGctOAkd1GoQoDZCGoCpqNTS5NG1LuhElFet3RdLJkwnyTYZXZhXwtpAQPrI36fqQ3eT6KQrdKDTKu3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.23.0", + "@algolia/client-abtesting": "5.57.0", + "@algolia/client-analytics": "5.57.0", + "@algolia/client-common": "5.57.0", + "@algolia/client-insights": "5.57.0", + "@algolia/client-personalization": "5.57.0", + "@algolia/client-query-suggestions": "5.57.0", + "@algolia/client-search": "5.57.0", + "@algolia/ingestion": "1.57.0", + "@algolia/monitoring": "1.57.0", + "@algolia/recommend": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/anser": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", @@ -4078,6 +5644,16 @@ "weald": "^1.1.2" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/bl": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", @@ -4382,6 +5958,17 @@ "cborg": "lib/bin.js" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4422,6 +6009,28 @@ "node": ">=8" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4599,6 +6208,17 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -4825,6 +6445,19 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/copy-anything": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.1.0.tgz", + "integrity": "sha512-ufbM3smX/Jbnpk5wcQjzd1MgBpzmqfNETUAyZNrGwU9foRlyHoGzMMBBCRzEhQLBjZfFDE1W2ufPXX2vdWkV8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -4918,6 +6551,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/datastore-core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-12.0.1.tgz", @@ -5045,6 +6685,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5064,6 +6714,20 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -5110,6 +6774,13 @@ "license": "MIT", "peer": true }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -5128,6 +6799,19 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -5222,6 +6906,45 @@ "tests/browser-compat" ] }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5434,6 +7157,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5812,6 +7542,16 @@ "license": "MIT", "peer": true }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -6164,6 +7904,44 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/helia": { "version": "7.1.10", "resolved": "https://registry.npmjs.org/helia/-/helia-7.1.10.tgz", @@ -6242,6 +8020,24 @@ "hermes-estree": "0.36.1" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -7548,6 +9344,16 @@ "node": ">=12" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/main-event": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.5.tgz", @@ -7564,6 +9370,13 @@ "tmpl": "1.0.5" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/marky": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", @@ -7580,6 +9393,28 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8140,6 +9975,100 @@ "node": ">=12" } }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -8245,6 +10174,20 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -8549,6 +10492,18 @@ "wrappy": "1" } }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -8798,6 +10753,13 @@ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8880,14 +10842,81 @@ "strip-json-comments": "^5.0.2" }, "bin": { - "pino-pretty": "bin.js" + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } }, "node_modules/prebuild-install": { "version": "7.1.3", @@ -9011,6 +11040,17 @@ "asap": "~2.0.6" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/protons-runtime": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.6.0.tgz", @@ -9589,6 +11629,33 @@ "license": "MIT", "peer": true }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9635,6 +11702,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", @@ -9655,6 +11729,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9738,6 +11858,14 @@ "license": "MIT", "peer": true }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/secure-json-parse": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", @@ -9870,6 +11998,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", @@ -10019,6 +12164,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -10040,12 +12195,33 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sparse-array": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==", "license": "ISC" }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -10140,6 +12316,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", @@ -10198,6 +12389,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -10211,6 +12415,13 @@ "node": ">=4" } }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, "node_modules/tar-fs": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", @@ -10430,6 +12641,17 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -10642,6 +12864,79 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unlimited-timeout": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/unlimited-timeout/-/unlimited-timeout-0.1.0.tgz", @@ -10740,6 +13035,138 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", @@ -10747,6 +13174,28 @@ "license": "MIT", "peer": true }, + "node_modules/vue": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -10989,7 +13438,6 @@ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", - "optional": true, "bin": { "yaml": "bin.mjs" }, @@ -11040,6 +13488,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 3833e36..4d66dcd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,34 @@ { "name": "ipfs-node", "version": "0.1.0", - "description": "IPFS decentralized file storage and transfers", + "description": "Self-hosted IPFS storage node for application file delivery, with bounded disk usage, deterministic replication, repair, health checkpoints, and a REST API", + "keywords": [ + "ipfs", + "helia", + "libp2p", + "bitswap", + "unixfs", + "content-addressed-storage", + "decentralized-storage", + "distributed-storage", + "file-storage", + "file-sharing", + "replication", + "self-hosted", + "storage-node", + "rest-api", + "nodejs", + "typescript", + "adamant" + ], + "homepage": "https://ipfs-node.docs.adamant.im", + "repository": { + "type": "git", + "url": "git+https://github.com/Adamant-im/ipfs-node.git" + }, + "bugs": { + "url": "https://github.com/Adamant-im/ipfs-node/issues" + }, "main": "dist/index.js", "private": true, "type": "module", @@ -14,6 +41,10 @@ "format": "prettier --check .", "format:fix": "prettier --write .", "typecheck": "tsc -p tsconfig.json --noEmit", + "docs:api": "node scripts/generate-api-reference.mjs", + "docs:dev": "npm run docs:api && vitepress dev docs", + "docs:build": "npm run docs:api && vitepress build docs && node scripts/check-docs-artifact.mjs", + "docs:preview": "vitepress preview docs", "test:build": "rimraf dist-test && tsc -p tsconfig.test.json", "test": "npm run test:build && npm run test:unit && npm run test:integration", "test:unit": "IPFS_NODE_CONFIG=test node --test \"dist-test/test/*.test.js\"", @@ -28,7 +59,7 @@ "security:semgrep": "semgrep --config p/owasp-top-ten --config p/nodejs --config p/expressjs --config p/typescript --config .semgrep/rules.yml --no-strict src/" }, "author": "ADAMANT Foundation ", - "license": "GPLv3", + "license": "GPL-3.0", "devDependencies": { "@commitlint/cli": "^21.2.2", "@commitlint/config-conventional": "^21.0.0", @@ -47,7 +78,9 @@ "prettier": "^3.9.6", "rimraf": "^6.1.3", "typescript": "^5.9.3", - "typescript-eslint": "^8.67.0" + "typescript-eslint": "^8.67.0", + "vitepress": "1.6.4", + "yaml": "2.9.0" }, "dependencies": { "@chainsafe/libp2p-noise": "^17.0.0", diff --git a/scripts/check-docs-artifact.mjs b/scripts/check-docs-artifact.mjs new file mode 100755 index 0000000..7a35eec --- /dev/null +++ b/scripts/check-docs-artifact.mjs @@ -0,0 +1,107 @@ +#!/usr/bin/env node +/** + * Validate the built documentation site before it can be deployed. + * + * GitHub Pages serves whatever the artifact contains, so a missing custom + * domain, a missing generated API reference, or a leaked absolute build path + * would only be discovered in production. Each of those fails the build here + * instead. + */ +import { readFile, readdir, stat } from 'node:fs/promises' +import { homedir } from 'node:os' +import { join, dirname, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..') +const distDir = join(rootDir, 'docs', '.vitepress', 'dist') + +/** Custom domain the Pages deployment must serve; see docs/public/CNAME. */ +const EXPECTED_CNAME = 'ipfs-node.docs.adamant.im' + +/** Files the artifact must contain for the site to be complete. */ +const REQUIRED_FILES = [ + 'index.html', + 'CNAME', + 'openapi.yaml', + 'sitemap.xml', + 'favicon.svg', + join('reference', 'endpoints.html'), + join('reference', 'api.html') +] + +const failures = [] + +/** + * Collect every file below `dir`, relative to the artifact root. + * + * @param dir Directory to walk + */ +async function walk(dir) { + const found = [] + for (const entry of await readdir(dir, { withFileTypes: true })) { + const full = join(dir, entry.name) + if (entry.isDirectory()) { + found.push(...(await walk(full))) + } else if (entry.isFile()) { + found.push(full) + } + } + return found +} + +try { + const stats = await stat(distDir) + if (!stats.isDirectory()) { + throw new Error('not a directory') + } +} catch { + console.error(`check-docs-artifact: ${distDir} is missing; run "npm run docs:build" first`) + process.exit(1) +} + +for (const file of REQUIRED_FILES) { + try { + await stat(join(distDir, file)) + } catch { + failures.push(`missing required artifact file: ${file}`) + } +} + +try { + const cname = (await readFile(join(distDir, 'CNAME'), 'utf8')).trim() + if (cname !== EXPECTED_CNAME) { + failures.push(`CNAME is "${cname}"; expected "${EXPECTED_CNAME}"`) + } +} catch { + // The missing-file check above already reported this. +} + +// A build path leaks the machine that produced the artifact, and a home +// directory is the form that shows up in stack traces and source maps. +const home = homedir() +const files = await walk(distDir) +for (const file of files) { + if (!/\.(html|js|css|json|xml|yaml)$/.test(file)) { + continue + } + const contents = await readFile(file, 'utf8') + if (home.length > 1 && contents.includes(home)) { + failures.push(`${relative(distDir, file)} contains the build machine home directory`) + } + if ( + contents.includes('adminApiKey') && + /adminApiKey['"]?\s*[:=]\s*['"][0-9a-f]{32,}/.test(contents) + ) { + failures.push(`${relative(distDir, file)} appears to contain a concrete administrative key`) + } +} + +if (failures.length > 0) { + console.error('check-docs-artifact: the built site is not publishable') + for (const failure of failures) { + console.error(` - ${failure}`) + } + process.exit(1) +} + +console.log(`check-docs-artifact: ${files.length} files verified in docs/.vitepress/dist`) diff --git a/scripts/docker-smoke-test.sh b/scripts/docker-smoke-test.sh new file mode 100755 index 0000000..d8b023f --- /dev/null +++ b/scripts/docker-smoke-test.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash +# Runtime verification for the ADAMANT IPFS Node container image. +# +# scripts/docker-smoke-test.sh [platform] +# +# The image is exercised the way the documentation says it should be run: +# configuration mounted at /app/config.json5, persistent state on a single +# /data volume, unprivileged user, and readiness taken from the JSON health +# state rather than the HTTP status code. +# +# It verifies that the image +# +# 1. starts from a mounted configuration and does not run as root +# 2. reaches the documented "ready" state, with "starting" observed first +# 3. accepts an upload and returns the same bytes on download +# 4. honours a configuration override without a rebuild +# 5. keeps content and the libp2p peer identity across container replacement +# 6. shuts down gracefully on SIGTERM +# 7. carries no configuration secret in its layers, history, or metadata +# +# Requires: docker, curl, node. Everything it creates is removed on exit. + +set -euo pipefail + +IMAGE="${1:?usage: scripts/docker-smoke-test.sh [platform]}" +PLATFORM="${2:-}" + +SUFFIX="$$" +CONTAINER="ipfs-node-smoke-${SUFFIX}" +VOLUME="ipfs-node-smoke-${SUFFIX}" +WORKDIR="$(mktemp -d)" +PORT="" +# A unique 64-character secret. It must appear in no image layer or metadata. +ADMIN_KEY="$(node -e 'console.log(require("node:crypto").randomBytes(32).toString("hex"))')" + +# Expanding an empty array under `set -u` is an error in bash 3.2, which is what +# macOS ships, so every use below goes through the `${arr[@]+...}` guard. +PLATFORM_ARGS=() +if [ -n "$PLATFORM" ]; then + PLATFORM_ARGS=(--platform "$PLATFORM") +fi + +step() { printf '\n=== %s\n' "$1"; } +fail() { + printf '\nFAILED: %s\n' "$1" >&2 + if docker container inspect "$CONTAINER" >/dev/null 2>&1; then + printf '\n--- container logs ---\n' >&2 + docker logs --tail 120 "$CONTAINER" >&2 || true + fi + exit 1 +} + +cleanup() { + # Preserve the script's exit status: without this, the last command of the + # trap would become the status the caller sees, and a failed run would look + # successful in CI. + status=$? + docker rm -f "$CONTAINER" >/dev/null 2>&1 || true + docker volume rm -f "$VOLUME" >/dev/null 2>&1 || true + rm -rf "$WORKDIR" + exit "$status" +} +trap cleanup EXIT + +# Pick a free loopback port so the test can run next to a local node. +pick_port() { + PORT="$(node -e ' +const net = require("node:net") +const server = net.createServer() +server.listen(0, "127.0.0.1", () => { + process.stdout.write(String(server.address().port)) + server.close() +})')" +} + +# Write a self-contained single-node configuration. +# +# $1 target file, $2 maxFileCount (used to prove a configuration override) +write_config() { + cat >"$1" </dev/null +} + +# Read one field out of GET /api/node/health. +# +# A container that is starting answers with a refused connection or an empty +# reply, and `set -o pipefail` would turn that into a fatal error inside the +# polling loop, so the pipeline is allowed to fail and yields an empty value. +health_field() { + { curl --silent --max-time 5 "http://127.0.0.1:${PORT}/api/node/health" 2>/dev/null || true; } | + node -e ' +let raw = "" +process.stdin.on("data", (chunk) => (raw += chunk)) +process.stdin.on("end", () => { + try { + const value = JSON.parse(raw)[process.argv[1]] + process.stdout.write(value === undefined ? "" : String(value)) + } catch { + process.stdout.write("") + } +})' "$1" || true +} + +# Fail unless the container is still running. A configuration error aborts the +# process, and every later check would then report a confusing symptom. +assert_running() { + sleep 3 + docker container inspect --format '{{.State.Running}}' "$CONTAINER" 2>/dev/null | grep -q true || + fail "$1" +} + +# Block until the health state is one of the states listed in $1. +wait_for_state() { + local wanted="$1" deadline=$((SECONDS + ${2:-180})) state="" + while [ "$SECONDS" -lt "$deadline" ]; do + if ! docker container inspect --format '{{.State.Running}}' "$CONTAINER" 2>/dev/null | grep -q true; then + fail "the container exited while waiting for state ${wanted}" + fi + state="$(health_field state)" + case " $wanted " in + *" $state "*) + printf 'state=%s\n' "$state" + return 0 + ;; + esac + sleep 2 + done + fail "health state stayed \"${state:-unreachable}\" instead of reaching ${wanted}" +} + +step "1/9 start from a mounted configuration" +write_config "$WORKDIR/config.json5" 10 +docker volume create "$VOLUME" >/dev/null +start_container "$WORKDIR/config.json5" + +assert_running "the container exited immediately after start" + +RUNTIME_UID="$(docker exec "$CONTAINER" id -u)" +RUNTIME_USER="$(docker exec "$CONTAINER" id -un)" +[ "$RUNTIME_UID" != "0" ] || fail "the container runs as root" +printf 'running as %s (uid %s)\n' "$RUNTIME_USER" "$RUNTIME_UID" + +step "2/9 the health endpoint reports a documented state" +wait_for_state "starting degraded stale ready" 120 + +step "3/9 the node reaches the documented ready state" +wait_for_state "ready" 240 +HEIGHT="$(health_field height)" +[ -n "$HEIGHT" ] && [ "$HEIGHT" != "0" ] || fail "a ready node reported no checkpoint height" +printf 'checkpoint height=%s\n' "$HEIGHT" + +step "4/9 the container health check agrees" +docker exec "$CONTAINER" node /app/docker/healthcheck.mjs || fail "the image health check reported unhealthy" + +step "5/9 upload and download a file through the documented API" +head -c 262144 /dev/urandom >"$WORKDIR/payload.bin" +UPLOAD="$(curl --silent --show-error --fail-with-body \ + --form "files=@${WORKDIR}/payload.bin" \ + "http://127.0.0.1:${PORT}/api/file/upload")" || fail "upload was rejected: $UPLOAD" +CID="$(printf '%s' "$UPLOAD" | node -e ' +let raw = "" +process.stdin.on("data", (chunk) => (raw += chunk)) +process.stdin.on("end", () => process.stdout.write(JSON.parse(raw).cids[0]))')" +[ -n "$CID" ] || fail "the upload response carried no CID" +printf 'cid=%s\n' "$CID" + +curl --silent --show-error --fail-with-body --output "$WORKDIR/download.bin" \ + "http://127.0.0.1:${PORT}/api/file/${CID}" || fail "download failed" +cmp "$WORKDIR/payload.bin" "$WORKDIR/download.bin" || fail "the downloaded bytes differ from the upload" + +STATE="$(curl --silent --retry 5 --retry-connrefused --retry-all-errors "http://127.0.0.1:${PORT}/api/file/${CID}/status" | node -e ' +let raw = "" +process.stdin.on("data", (chunk) => (raw += chunk)) +process.stdin.on("end", () => process.stdout.write(JSON.parse(raw).state))')" +[ "$STATE" = "confirmed" ] || fail "the uploaded file is \"$STATE\" instead of confirmed" + +PEER_ID="$(curl --silent --retry 5 --retry-connrefused --retry-all-errors --header "x-api-key: ${ADMIN_KEY}" \ + "http://127.0.0.1:${PORT}/api/node/details" | node -e ' +let raw = "" +process.stdin.on("data", (chunk) => (raw += chunk)) +process.stdin.on("end", () => process.stdout.write(JSON.parse(raw).peerId))')" +[ -n "$PEER_ID" ] || fail "the administrative route returned no peer id" +printf 'peerId=%s\n' "$PEER_ID" + +step "6/9 SIGTERM shuts the node down gracefully" +docker stop --timeout 25 "$CONTAINER" >/dev/null +EXIT_CODE="$(docker container inspect --format '{{.State.ExitCode}}' "$CONTAINER")" +[ "$EXIT_CODE" = "0" ] || fail "the container exited with code ${EXIT_CODE} instead of 0" +docker logs "$CONTAINER" 2>&1 | grep -q 'Received SIGTERM, shutting down' || + fail "no graceful shutdown was logged" + +step "7/9 content, peer identity, and state survive container replacement" +docker rm -f "$CONTAINER" >/dev/null +write_config "$WORKDIR/config-override.json5" 7 +start_container "$WORKDIR/config-override.json5" +assert_running "the replacement container exited immediately after start" +wait_for_state "ready" 240 + +NEW_PEER_ID="$(curl --silent --retry 5 --retry-connrefused --retry-all-errors --header "x-api-key: ${ADMIN_KEY}" \ + "http://127.0.0.1:${PORT}/api/node/details" | node -e ' +let raw = "" +process.stdin.on("data", (chunk) => (raw += chunk)) +process.stdin.on("end", () => process.stdout.write(JSON.parse(raw).peerId))')" +[ "$NEW_PEER_ID" = "$PEER_ID" ] || fail "the peer identity changed: ${PEER_ID} -> ${NEW_PEER_ID}" + +curl --silent --show-error --fail-with-body --output "$WORKDIR/download2.bin" \ + "http://127.0.0.1:${PORT}/api/file/${CID}" || fail "content did not survive container replacement" +cmp "$WORKDIR/payload.bin" "$WORKDIR/download2.bin" || fail "restored content differs from the upload" + +step "8/9 configuration overrides apply without rebuilding the image" +MAX_FILES="$(curl --silent --retry 5 --retry-connrefused --retry-all-errors "http://127.0.0.1:${PORT}/api/storage/policy" | node -e ' +let raw = "" +process.stdin.on("data", (chunk) => (raw += chunk)) +process.stdin.on("end", () => process.stdout.write(String(JSON.parse(raw).maxFileCount)))')" +[ "$MAX_FILES" = "7" ] || fail "maxFileCount is ${MAX_FILES}; the mounted override was not applied" + +step "9/9 no configuration secret reached the image" +INSPECTION="$(docker image inspect "$IMAGE"; docker image history --no-trunc "$IMAGE")" +if printf '%s' "$INSPECTION" | grep -q "$ADMIN_KEY"; then + fail "the administrative key appears in the image metadata or history" +fi +docker exec "$CONTAINER" sh -c 'ls /app/config.json5 >/dev/null 2>&1 && cat /app/config.json5' | + grep -q "$ADMIN_KEY" || fail "the mounted configuration was not readable at /app/config.json5" +docker run --rm ${PLATFORM_ARGS[@]+"${PLATFORM_ARGS[@]}"} --entrypoint sh "$IMAGE" -c \ + 'test ! -e /app/config.json5' || fail "the image itself ships a config.json5" + +printf '\nAll container checks passed for %s%s\n' "$IMAGE" "${PLATFORM:+ (${PLATFORM})}" diff --git a/scripts/generate-api-reference.mjs b/scripts/generate-api-reference.mjs new file mode 100755 index 0000000..97c163d --- /dev/null +++ b/scripts/generate-api-reference.mjs @@ -0,0 +1,201 @@ +#!/usr/bin/env node +/** + * Generate the documentation-site API reference from `docs/openapi.yaml`. + * + * The specification is the single source of truth for the public contract, so + * the endpoint tables are derived from it instead of being retyped. Two + * artifacts are produced: + * + * - `docs/reference/endpoints.md`, the generated endpoint reference + * - `docs/public/openapi.yaml`, the machine-readable copy the site serves + * + * Both are generated, so both are ignored by Git. A missing or unusable + * specification fails the build rather than publishing a site without its API + * reference. + */ +import { readFile, writeFile, mkdir } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { parse } from 'yaml' + +const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..') +const specPath = join(rootDir, 'docs', 'openapi.yaml') +const referencePath = join(rootDir, 'docs', 'reference', 'endpoints.md') +const publicSpecPath = join(rootDir, 'docs', 'public', 'openapi.yaml') + +const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'] + +/** Fail with a message an operator can act on rather than a stack trace. */ +function fail(message) { + console.error(`generate-api-reference: ${message}`) + process.exit(1) +} + +/** + * Escape the characters that would break a Markdown table cell. + * + * @param value Text taken from the specification + */ +function cell(value) { + return String(value ?? '') + .replace(/\r?\n+/g, ' ') + .replace(/\|/g, '\\|') + .trim() +} + +/** + * Describe the authentication a specification operation requires. + * + * The node has exactly one scheme, the administrative `x-api-key` header, so an + * operation either carries a `security` block or is public. + * + * @param operation Parsed OpenAPI operation object + */ +function authentication(operation) { + const security = operation.security ?? [] + if (security.length === 0) { + return 'Public' + } + const names = security.flatMap((requirement) => Object.keys(requirement)) + return names.includes('AdminApiKey') ? '`x-api-key`' : names.join(', ') +} + +/** + * Render the response table of one operation. + * + * @param operation Parsed OpenAPI operation object + */ +function responseRows(operation) { + const responses = operation.responses ?? {} + return Object.entries(responses).map(([status, response]) => { + const description = + response?.description ?? + (typeof response?.$ref === 'string' ? response.$ref.split('/').pop() : '') + return `| \`${cell(status)}\` | ${cell(description)} |` + }) +} + +/** + * Render every path of the specification as a Markdown section. + * + * @param spec Parsed OpenAPI document + */ +function renderOperations(spec) { + const lines = [] + const summary = [] + + for (const [path, item] of Object.entries(spec.paths ?? {})) { + for (const method of METHODS) { + const operation = item?.[method] + if (operation === undefined) { + continue + } + + const verb = method.toUpperCase() + const anchor = `${verb.toLowerCase()}-${path + .replace(/[{}]/g, '') + .replace(/[^a-zA-Z0-9]+/g, '-') + .replace(/^-|-$/g, '')}` + + summary.push( + `| [\`${verb} ${cell(path)}\`](#${anchor}) | ${cell(operation.summary)} | ${authentication(operation)} |` + ) + + lines.push(`### \`${verb} ${path}\` {#${anchor}}`) + lines.push('') + if (operation.summary) { + lines.push(cell(operation.summary)) + lines.push('') + } + if (operation.description) { + lines.push(String(operation.description).trim()) + lines.push('') + } + lines.push(`Authentication: ${authentication(operation)}`) + lines.push('') + + const parameters = [...(item.parameters ?? []), ...(operation.parameters ?? [])].filter( + (parameter) => parameter.$ref === undefined + ) + if (parameters.length > 0) { + lines.push('| Parameter | In | Required | Description |') + lines.push('| --------- | -- | -------- | ----------- |') + for (const parameter of parameters) { + lines.push( + `| \`${cell(parameter.name)}\` | ${cell(parameter.in)} | ${parameter.required === true ? 'yes' : 'no'} | ${cell(parameter.description ?? '')} |` + ) + } + lines.push('') + } + + const rows = responseRows(operation) + if (rows.length > 0) { + lines.push('| Status | Meaning |') + lines.push('| ------ | ------- |') + lines.push(...rows) + lines.push('') + } + } + } + + return { lines, summary } +} + +const raw = await readFile(specPath, 'utf8').catch((err) => + fail(`cannot read ${specPath}: ${err.message}`) +) + +let spec +try { + spec = parse(raw) +} catch (err) { + fail(`cannot parse ${specPath}: ${err.message}`) +} + +if (spec?.paths === undefined || Object.keys(spec.paths).length === 0) { + fail(`${specPath} declares no paths; the API reference would be empty`) +} + +const { lines, summary } = renderOperations(spec) + +if (summary.length === 0) { + fail(`${specPath} declares no operations; the API reference would be empty`) +} + +const page = [ + '---', + 'title: Endpoint reference', + 'editLink: false', + '---', + '', + '', + '', + '# Endpoint reference', + '', + `Generated from [\`docs/openapi.yaml\`](/openapi.yaml) — \`${cell(spec.info?.title ?? 'API')}\`, contract version \`${cell(spec.info?.version ?? 'unversioned')}\`.`, + '', + 'This page lists the stable client, lifecycle, and node-health contract. Low-level Helia,', + 'libp2p, and optional debug operator routes are intentionally left out of the specification;', + 'they are described in [Administrative and debug routes](./api#administrative-and-debug-routes).', + '', + 'Download the machine-readable specification: [`openapi.yaml`](/openapi.yaml).', + '', + '## Operations', + '', + '| Operation | Summary | Authentication |', + '| --------- | ------- | -------------- |', + ...summary, + '', + '## Details', + '', + ...lines +].join('\n') + +await mkdir(dirname(referencePath), { recursive: true }) +await mkdir(dirname(publicSpecPath), { recursive: true }) +await writeFile(referencePath, `${page.trimEnd()}\n`, 'utf8') +await writeFile(publicSpecPath, raw, 'utf8') + +console.log( + `generate-api-reference: wrote ${summary.length} operations to docs/reference/endpoints.md` +) From b65c93975269ca281414df799d7d37a4dba6aaa6 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sat, 12 Sep 2026 23:22:32 +0300 Subject: [PATCH 147/167] fix: unblock the production audit gate and lint the site build out Three unrelated blockers stopped `git push` from running its pre-push hook to completion. `multer` 2.2.0 is covered by three high-severity advisories (GHSA-wc9g-mqfw-jrwm, GHSA-qfvm-cv95-jqjf, GHSA-535w-7cp7-47q4), all fixed in 2.3.0. The bump stays inside the existing caret range, so it is a lockfile change with no API surface to migrate. `npm run` exports every resolved setting as an `npm_config_*` variable, and the nested `npm audit` re-reads them as flags. npm 12 rejects `--allow-scripts` outside a global install, so a developer with `allow-scripts` in a personal `.npmrc` got `EALLOWSCRIPTS` and an unusable report rather than a finding. The audit script now drops that one inherited setting and reports npm's own error summary instead of the generic "report is incomplete". `npm run docs:build` writes the bundled site into `docs/.vitepress/dist`, which ESLint then walked, reporting close to two thousand errors in generated output. Both that directory and the VitePress cache are now ignored. Co-Authored-By: Claude Opus 5 --- eslint.config.js | 15 +++++++++++++-- package-lock.json | 10 +++++----- package.json | 2 +- scripts/audit-production.mjs | 26 +++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 962a260..2bf5cd4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,8 +6,19 @@ import prettier from 'eslint-config-prettier' export default tseslint.config( { // `.claude/` holds scratch worktrees with their own checked-out copies of - // this repository; linting them would report the same file many times - ignores: ['dist/', 'dist-test/', 'node_modules/', '.claude/', '.ai-ignored/', '.ai-tasks/'] + // this repository; linting them would report the same file many times. + // `docs/.vitepress/dist` and `cache` hold the bundled documentation site, + // which is generated output rather than source. + ignores: [ + 'dist/', + 'dist-test/', + 'node_modules/', + '.claude/', + '.ai-ignored/', + '.ai-tasks/', + 'docs/.vitepress/dist/', + 'docs/.vitepress/cache/' + ] }, js.configs.recommended, tseslint.configs.recommended, diff --git a/package-lock.json b/package-lock.json index ebe29ed..b40bb06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "ipfs-node", "version": "0.1.0", - "license": "GPLv3", + "license": "GPL-3.0", "dependencies": { "@chainsafe/libp2p-noise": "^17.0.0", "@chainsafe/libp2p-yamux": "^8.0.1", @@ -31,7 +31,7 @@ "interface-blockstore": "^7.0.1", "interface-datastore": "^10.0.1", "json5": "^2.2.3", - "multer": "^2.2.0", + "multer": "^2.3.0", "multiformats": "^14.0.5", "pino": "^10.3.1", "pino-http": "^11.0.0", @@ -10225,9 +10225,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", - "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.3.0.tgz", + "integrity": "sha512-cjNbm3sttszgZeGfJR124D+jFEfkXCVAsoPBmFn9X7UxmDSFHWqE2CoEj0vrmSpuAFnqWR1Szcm9QTsiHr60Xw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", diff --git a/package.json b/package.json index 4d66dcd..6b6e104 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "interface-blockstore": "^7.0.1", "interface-datastore": "^10.0.1", "json5": "^2.2.3", - "multer": "^2.2.0", + "multer": "^2.3.0", "multiformats": "^14.0.5", "pino": "^10.3.1", "pino-http": "^11.0.0", diff --git a/scripts/audit-production.mjs b/scripts/audit-production.mjs index 8d6c094..15d6a2c 100644 --- a/scripts/audit-production.mjs +++ b/scripts/audit-production.mjs @@ -1,8 +1,23 @@ import { spawnSync } from 'node:child_process' +/** + * Environment for the audit child process. + * + * `npm run` exports every resolved setting as an `npm_config_*` variable, and a + * nested `npm` re-reads them as command-line flags. npm 12 rejects + * `--allow-scripts` outside a global install, so a developer who sets + * `allow-scripts` in a personal `.npmrc` makes this script — and therefore the + * pre-push hook — fail with `EALLOWSCRIPTS` instead of producing a report. + * Dropping that one inherited setting keeps the child reading the same config + * files and cache as the parent. + */ +const env = { ...process.env } +delete env.npm_config_allow_scripts + const result = spawnSync('npm', ['audit', '--omit=dev', '--omit=peer', '--json'], { encoding: 'utf8', - maxBuffer: 10 * 1024 * 1024 + maxBuffer: 10 * 1024 * 1024, + env }) if (result.error) { @@ -20,6 +35,15 @@ try { throw new Error('npm audit produced invalid JSON') } +// npm reports its own failures as `{ "error": { code, summary } }`, which parses +// cleanly but carries no findings. Surfacing that summary is what turns an +// unusable report into a message an operator can act on. +if (report.error) { + throw new Error( + `npm audit failed: ${report.error.summary || report.error.code || 'unknown error'}` + ) +} + if ( !report.metadata || report.vulnerabilities === null || From 3e4f827297a85eae92f0f0c3d05216872204bb7e Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sat, 12 Sep 2026 23:38:02 +0300 Subject: [PATCH 148/167] ci: drop the unreachable docs release trigger and keep the image title The `github-pages` environment admits deployments from the `dev` branch only. A release run carries the tag as its ref, so the deploy job was rejected before its first step and the run failed while the site was already live from the push that preceded it. Every release is cut from `dev`, which the push trigger already publishes, so removing the trigger loses no content. docker/metadata-action passes its own label set to the build, where it wins over the Dockerfile. Its generated title is the bare repository name, which is how `org.opencontainers.image.title` reached the 0.1.0 image as `ipfs-node`. The descriptive labels are now restated in the workflow so the published image matches the Dockerfile. Co-Authored-By: Claude Opus 5 --- .github/workflows/docs.yml | 8 ++++++-- .github/workflows/publish-docker.yml | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 910fad6..e1874d8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,6 +6,12 @@ name: Docs # `dev` is the default branch of this repository, so the branch trigger points # there. Repository settings must have Pages source set to "GitHub Actions" and # the custom domain set to ipfs-node.docs.adamant.im with HTTPS enforced. +# +# There is deliberately no `release` trigger. The `github-pages` environment +# admits deployments from the `dev` branch only, and a release run's ref is the +# tag, so such a run is rejected before its first step. Every release is cut +# from `dev`, and the push above has already published that commit, so the +# trigger would add a guaranteed failure and no new content. on: push: @@ -26,8 +32,6 @@ on: - 'package.json' - 'package-lock.json' - '.github/workflows/docs.yml' - release: - types: [published] workflow_dispatch: permissions: diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index ca85704..b16dbf5 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -81,7 +81,16 @@ jobs: tags: | type=semver,pattern={{version}} type=raw,value=latest,enable=${{ github.event.release.prerelease == false }} + # metadata-action derives a label set of its own and passes it to the + # build, where it wins over the Dockerfile's LABEL instruction. Its + # generated title is the bare repository name, so the descriptive + # labels are restated here to keep the published image consistent with + # the Dockerfile. labels: | + org.opencontainers.image.title=ADAMANT IPFS Node + org.opencontainers.image.vendor=ADAMANT Developer Community + org.opencontainers.image.authors=ADAMANT Developer Community + org.opencontainers.image.documentation=https://ipfs-node.docs.adamant.im org.opencontainers.image.version=${{ steps.release.outputs.version }} - name: Build and push From e6ee4082a08aafd5924cbf2426cd999d57955267 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sun, 13 Sep 2026 08:13:25 +0300 Subject: [PATCH 149/167] docs: add community and contact information to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 3f6d61b..ed1afff 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ each other. - API specification: [docs/openapi.yaml](docs/openapi.yaml) - Storage and replication reference: [docs/storage-lifecycle.md](docs/storage-lifecycle.md) +> Built and maintained by the ADAMANT developer community and cryptofoundry. +> Want custom crypto software, bots, payments or blockchain infrastructure built by engineers with production blockchain experience? [Tell us what to build](https://adamant.business#contact). + ## Features - REST upload of multipart files and content-addressed download by CID From f5cc5b9f11a731348b2431ed03a535b82fe82da4 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sun, 13 Sep 2026 09:02:48 +0300 Subject: [PATCH 150/167] ci: cut releases from master instead of dev Publication gated on the release tag being an ancestor of `dev`, which made `dev` the only branch a release could come from. Point the gate at `master` so that a release is prepared by merging `dev` into `master` and tagging there, and so that a tag left on `dev` or on a feature branch is refused before anything is built. Two consequences are handled in the same change: - the Container workflow now also builds and smoke-tests on `master`, so the image that publication will produce is exercised before the tag exists - the Docs workflow comment no longer claims that every release is cut from `dev`; the reason it has no `release` trigger is unchanged, because `master` only ever receives merges from `dev` The branch model is recorded in `AGENTS.md` and on the contributing page so the next release does not have to rediscover it. Refs #37 Co-Authored-By: Claude Opus 5 --- .github/workflows/docker.yml | 8 ++++++-- .github/workflows/docs.yml | 7 ++++--- .github/workflows/publish-docker.yml | 14 ++++++++------ AGENTS.md | 7 +++++++ docs/guide/contributing.md | 22 +++++++++++++++++++++- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d298636..2ec2e15 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,10 +3,14 @@ name: Container # Builds the production image and runs the documented runtime checks on it. # Nothing is pushed here: publication happens only from a GitHub Release, in # .github/workflows/publish-docker.yml. +# +# Both branches are covered: `dev` catches a regression when it is introduced, +# and `master` is the branch a release is tagged from, so the image that +# publication will build is exercised before the tag exists. on: push: - branches: [dev] + branches: [master, dev] paths: - 'Dockerfile' - '.dockerignore' @@ -18,7 +22,7 @@ on: - 'tsconfig*.json' - '.github/workflows/docker.yml' pull_request: - branches: [dev] + branches: [master, dev] paths: - 'Dockerfile' - '.dockerignore' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e1874d8..2283a9f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,9 +9,10 @@ name: Docs # # There is deliberately no `release` trigger. The `github-pages` environment # admits deployments from the `dev` branch only, and a release run's ref is the -# tag, so such a run is rejected before its first step. Every release is cut -# from `dev`, and the push above has already published that commit, so the -# trigger would add a guaranteed failure and no new content. +# tag, so such a run is rejected before its first step. A release is cut from +# `master`, which only ever receives merges from `dev`, so the push above has +# already published that content and the trigger would add a guaranteed failure +# and nothing new. on: push: diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index b16dbf5..8f5fa3d 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -39,13 +39,15 @@ jobs: ref: ${{ github.event.release.tag_name }} fetch-depth: 0 - # `dev` is this repository's default and stable release branch. A tag made - # on a feature branch must not become a published image. - - name: Verify the release tag is on dev + # `master` is this repository's release branch: `dev` integrates work and + # is merged into `master` for a release. A tag made on `dev`, on a feature + # branch, or on a commit that never reached `master` must not become a + # published image. + - name: Verify the release tag is on master run: | - git fetch --no-tags origin dev - git merge-base --is-ancestor HEAD origin/dev \ - || { echo "the release tag is not an ancestor of origin/dev"; exit 1; } + git fetch --no-tags origin master + git merge-base --is-ancestor HEAD origin/master \ + || { echo "the release tag is not an ancestor of origin/master"; exit 1; } - name: Resolve the release version id: release diff --git a/AGENTS.md b/AGENTS.md index 8f8f898..98bacf6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,6 +147,13 @@ Follow organization-wide governance, templates, title conventions, and label cas - Include validation evidence and note relevant risks or intentionally unchanged behavior - Add a link to the PR in the related issue +### Branches and releases + +- Target `dev` with every pull request; it is the default and integration branch +- Cut a release from `master`: merge `dev` into `master`, tag that commit `vX.Y.Z`, and publish a GitHub Release from the tag +- Keep the tag and the `package.json` version in agreement; container publication verifies both the version and that the tagged commit is an ancestor of `master` +- Do not tag a release on `dev` or on a feature branch; publication refuses it + ### Command-line content For multi-line CLI input, use a dated temporary Markdown file under `.ai-ignored/` and a file-based flag such as `--body-file`. Do not put multi-line issue, PR, or commit text directly in a shell argument. diff --git a/docs/guide/contributing.md b/docs/guide/contributing.md index 73fbe2e..541fb52 100644 --- a/docs/guide/contributing.md +++ b/docs/guide/contributing.md @@ -6,7 +6,8 @@ description: Development setup, repository layout, the checks that must pass, an # Contributing The project is GPL-3.0 and developed in the open at -[Adamant-im/ipfs-node](https://github.com/Adamant-im/ipfs-node). The default branch is `dev`. +[Adamant-im/ipfs-node](https://github.com/Adamant-im/ipfs-node). The default branch is `dev`, and +releases are cut from `master`. ## Development setup @@ -129,6 +130,25 @@ Keep the public surface synchronized. When behaviour changes, `README.md`, this `docs/openapi.yaml`, the container files, and `package.json` metadata are updated in the same change, or the omission is stated deliberately. +## Branches and releases + +`dev` is the default branch and the integration branch. Pull requests target it, and the build, the +security audit, the container smoke test, and the documentation deployment all run there. The build, +the security audit, and the container smoke test also run on `master`, so the commit a release is +tagged from is verified before the tag exists. + +`master` is the release branch. A release is prepared by merging `dev` into `master`, tagging that +commit `vX.Y.Z`, and publishing a GitHub Release from the tag. Publication refuses anything else: +before it builds, `publish-docker.yml` checks that the tagged commit is an ancestor of `master` and +that the version in the tag matches `package.json`, so a tag left on `dev` or on a feature branch +never becomes a published image. + +Every release publishes the immutable `ghcr.io/adamant-im/ipfs-node:X.Y.Z`. A stable release also +moves `latest`; a prerelease publishes its version tag only. + +The documentation site deploys from `dev` rather than from a release. `master` only ever receives +merges from `dev`, so the site already carries the released content by the time the tag exists. + ## Security Report suspected vulnerabilities privately through GitHub security advisories on From 5c17db0224d195f439aaca21f81d0fc8ae7e720b Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sun, 13 Sep 2026 09:02:57 +0300 Subject: [PATCH 151/167] fix: stop the documentation build warning about the ES2024 target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm run docs:dev` and `npm run docs:build` printed ▲ [WARNING] Unrecognized target environment "ES2024" [tsconfig.json] on every start. Vite bundles the VitePress configuration with esbuild before evaluating it, and that esbuild resolves the repository's root `tsconfig.json`. Vite 5 ships esbuild 0.21.5, which knows targets up to `es2023` only. Neither a docs-local `tsconfig.json` nor a Vite-level `esbuild.tsconfigRaw` override reaches that lookup: config bundling resolves the root file and accepts no redirection, which was verified against the bundled esbuild directly. The value in the root file is therefore the only place to change. `ESNext` is used instead. Nothing is loosened: `lib` stays at `ES2024` so no newer library API type-checks, `engines` and `.nvmrc` keep the runtime at Node 24, and `dist` was compared under both values and is byte-identical. The reason is recorded next to the setting so it is not reverted. Co-Authored-By: Claude Opus 5 --- tsconfig.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index c41f38b..1436578 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,14 @@ // misses is type-checked by the editor under default compiler options, which // report spurious errors about module resolution and missing Node typings. // It never emits: `tsconfig.build.json` and `tsconfig.test.json` do that. + // + // `target` is `ESNext` rather than `ES2024` on purpose. esbuild resolves this + // file whenever Vite bundles the VitePress configuration, and the esbuild + // that Vite 5 ships only knows targets up to `es2023`, so `ES2024` made every + // documentation build and dev start print `Unrecognized target environment`. + // Nothing is loosened by the change: `lib` stays at `ES2024`, so no newer + // library API type-checks, `engines` and `.nvmrc` pin the runtime to Node 24, + // and the emitted `dist` is byte-identical under either value. "compilerOptions": { "allowJs": false, "checkJs": false, @@ -11,7 +19,7 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "sourceMap": true, - "target": "ES2024", + "target": "ESNext", "strict": true, "noImplicitAny": true, "useUnknownInCatchVariables": false, From f4d17e0c9b33c5f6b71d709bcfbb7a807217fee3 Mon Sep 17 00:00:00 2001 From: al-onyxprotocol Date: Sun, 13 Sep 2026 09:10:33 +0300 Subject: [PATCH 152/167] fix: stop the container smoke test racing the shutdown log The graceful-shutdown check read `docker logs` once, immediately after `docker stop` returned. `docker stop` returns as soon as the container has exited, which can be before the daemon has flushed the last lines to the log driver, so on a loaded runner the grep missed a line that was already there a moment later. The failure was visible: the run failed with "no graceful shutdown was logged" while the diagnostic dump printed by the failure handler, a few milliseconds later, contained `Received SIGTERM, shutting down`. The same commit passed the same job on the push event and failed on the pull-request event. Poll for up to five seconds instead of reading once. The probe sits in a short-circuit position, so a failed attempt does not trip `set -e`, and the final call still decides the check. Co-Authored-By: Claude Opus 5 --- scripts/docker-smoke-test.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/docker-smoke-test.sh b/scripts/docker-smoke-test.sh index d8b023f..a66eeb4 100755 --- a/scripts/docker-smoke-test.sh +++ b/scripts/docker-smoke-test.sh @@ -246,8 +246,18 @@ step "6/9 SIGTERM shuts the node down gracefully" docker stop --timeout 25 "$CONTAINER" >/dev/null EXIT_CODE="$(docker container inspect --format '{{.State.ExitCode}}' "$CONTAINER")" [ "$EXIT_CODE" = "0" ] || fail "the container exited with code ${EXIT_CODE} instead of 0" -docker logs "$CONTAINER" 2>&1 | grep -q 'Received SIGTERM, shutting down' || - fail "no graceful shutdown was logged" +# `docker stop` returns as soon as the container has exited, which can be before +# the daemon has flushed its last lines to the log driver. Reading once made the +# check fail intermittently on a loaded runner while the line was already there a +# moment later, so poll for up to five seconds instead. +shutdown_logged() { + docker logs "$CONTAINER" 2>&1 | grep -q 'Received SIGTERM, shutting down' +} +for _ in $(seq 1 50); do + shutdown_logged && break + sleep 0.1 +done +shutdown_logged || fail "no graceful shutdown was logged" step "7/9 content, peer identity, and state survive container replacement" docker rm -f "$CONTAINER" >/dev/null From c0c6c31ad917e9696ef0e59181ad6cb72c41d067 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Sun, 20 Sep 2026 13:47:28 +0300 Subject: [PATCH 153/167] feat: implement peer liveness checks and recovery mechanisms Added functionality to ping connected peers and reset connections for those that fail to respond, ensuring more reliable peer interactions. Introduced a new recovery system to handle stale peer sessions, allowing for immediate redials after connection failures. Updated related services to report retrieval failures and trigger recovery processes. Added tests for identifying stale peer session errors. --- src/api/file.ts | 11 ++++-- src/peering.cron.ts | 38 +++++++++++++++++++- src/peering/liveness.ts | 34 ++++++++++++++++++ src/peering/recovery.ts | 56 ++++++++++++++++++++++++++++++ src/storage/replicationProtocol.ts | 8 +++-- src/storage/service.ts | 24 ++++++++++++- test/peeringRecovery.test.ts | 19 ++++++++++ 7 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 src/peering/liveness.ts create mode 100644 src/peering/recovery.ts create mode 100644 test/peeringRecovery.test.ts diff --git a/src/api/file.ts b/src/api/file.ts index e1ccacb..61f85e3 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -12,7 +12,8 @@ import { effectiveQuorum, prepareFileRetrieval, releaseFile, - replicateUploadedFile + replicateUploadedFile, + reportRetrievalFailure } from '../storage/service.js' import { fileRegistry } from '../storage/state.js' import { createUploadHandler } from './uploadRoute.js' @@ -23,6 +24,7 @@ import { setDownloadHeaders } from '../utils/downloadResponse.js' import { downloadFile, getFileStats } from '../utils/file.js' +import { FileNotFoundError } from '../utils/fileErrors.js' import { logger } from '../utils/logger.js' const router = Router() @@ -108,8 +110,10 @@ router.get('/:cid', readLimiter, admitDownload, async (req, res, next) => { } }) + let cid: ReturnType | undefined + try { - const cid = parseCid(req.params.cid) + cid = parseCid(req.params.cid) // Reach the file's holders first. Without this the read only succeeds if a // peer that has the file is already connected, which stops being true as @@ -140,6 +144,9 @@ router.get('/:cid', readLimiter, admitDownload, async (req, res, next) => { ) } catch (error) { if (requestController.signal.aborted || res.destroyed) return + if (error instanceof FileNotFoundError && cid !== undefined) { + reportRetrievalFailure(cid) + } next(error) } }) diff --git a/src/peering.cron.ts b/src/peering.cron.ts index df7f1c3..69bde35 100644 --- a/src/peering.cron.ts +++ b/src/peering.cron.ts @@ -1,6 +1,7 @@ import { CronJob } from 'cron' import { config } from './config.js' import { helia } from './helia.js' +import { pingPeer, resetPeerConnection } from './peering/liveness.js' import { logger } from './utils/logger.js' import { getNodesList } from './utils/utils.js' @@ -13,6 +14,36 @@ const DIAL_TIMEOUT_MS = 10000 let running = false let lastConnected = 0 +/** + * Ping configured peers that appear connected and reset any that do not answer. + * + * `@libp2p/bootstrap` only runs once; peering already redials missing peers. + * That is not enough when TCP stays up but bitswap or replication streams are + * broken — those peers never count as "missing". + */ +async function dropUnhealthyKnownPeers(): Promise { + const known = getNodesList([helia.libp2p.peerId.toString()]) + const connected = new Set(helia.libp2p.getPeers().map((peer) => peer.toString())) + + await Promise.all( + known + .filter((node) => connected.has(node.peerId.toString())) + .map(async (node) => { + const alive = await pingPeer(helia, node.peerId) + + if (alive) { + return + } + + logger.warn( + { event: 'peering_liveness_failed', peer: node.name }, + `Peering liveness check failed for ${node.name}; resetting connection` + ) + await resetPeerConnection(helia, node.peerId) + }) + ) +} + /** * Dial the configured ADAMANT nodes that are not currently connected. * @@ -27,6 +58,8 @@ let lastConnected = 0 * @returns How many nodes are connected after this pass */ export async function peerWithKnownNodes(): Promise { + await dropUnhealthyKnownPeers() + const known = getNodesList([helia.libp2p.peerId.toString()]) const connected = new Set(helia.libp2p.getPeers().map((peer) => peer.toString())) const missing = known.filter((node) => !connected.has(node.peerId.toString())) @@ -44,7 +77,10 @@ export async function peerWithKnownNodes(): Promise { } } - lastConnected = connected.size + results.filter((r) => r.status === 'fulfilled').length + lastConnected = helia.libp2p.getPeers().filter((peer) => + known.some((node) => node.peerId.equals(peer)) + ).length + return lastConnected } diff --git a/src/peering/liveness.ts b/src/peering/liveness.ts new file mode 100644 index 0000000..aea7e73 --- /dev/null +++ b/src/peering/liveness.ts @@ -0,0 +1,34 @@ +import type { PeerId } from '@libp2p/interface' +import type { IpfsNode } from '../ipfs-node.js' + +/** Time allowed for a libp2p ping that validates an existing mesh connection. */ +export const PEER_PING_TIMEOUT_MS = 5000 + +/** + * Return whether `peerId` answers a libp2p ping within the timeout. + * + * A TCP session can stay up while application protocols on it are broken; ping + * exercises the same stream stack bitswap and replication rely on. + */ +export async function pingPeer( + node: IpfsNode, + peerId: PeerId, + timeoutMs: number = PEER_PING_TIMEOUT_MS +): Promise { + try { + await node.libp2p.services.ping.ping(peerId, { + signal: AbortSignal.timeout(timeoutMs) + }) + return true + } catch { + return false + } +} + +/** + * Tear down every open libp2p connection to `peerId` so the next peering pass + * dials a fresh session. + */ +export async function resetPeerConnection(node: IpfsNode, peerId: PeerId): Promise { + await node.libp2p.hangUp(peerId).catch(() => {}) +} diff --git a/src/peering/recovery.ts b/src/peering/recovery.ts new file mode 100644 index 0000000..e607531 --- /dev/null +++ b/src/peering/recovery.ts @@ -0,0 +1,56 @@ +import { peerIdFromString } from '@libp2p/peer-id' +import { helia } from '../helia.js' +import { logger } from '../utils/logger.js' +import { resetPeerConnection } from './liveness.js' + +/** Do not reset the same peer more than once in this window. */ +const RECOVERY_COOLDOWN_MS = 30_000 + +const lastRecoveryAt = new Map() + +/** + * Patterns that indicate the libp2p session is stale while TCP may still look up. + */ +export function isStalePeerSessionError(message: string): boolean { + return ( + message.includes('stream that is closed') || + message.includes('stream ended before') || + message.includes('Replication request timed out') || + message.includes('The connection is closed') + ) +} + +/** + * Drop connections to a configured peer and schedule an immediate peering pass. + * + * @param peerId Configured peer identifier string + * @param reason Logged context for operators + * @param redial Peering function injected to avoid import cycles in tests + */ +export function recoverPeerSession( + peerId: string, + reason: string, + redial: () => Promise +): void { + const now = Date.now() + const last = lastRecoveryAt.get(peerId) ?? 0 + + if (now - last < RECOVERY_COOLDOWN_MS) { + return + } + + lastRecoveryAt.set(peerId, now) + + void (async () => { + try { + logger.warn( + { event: 'peer_session_recovery', peerId, reason }, + 'Resetting libp2p session to a configured peer after a transfer failure' + ) + await resetPeerConnection(helia, peerIdFromString(peerId)) + await redial() + } catch (err) { + logger.error({ err, peerId }, 'Peer session recovery failed') + } + })() +} diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index 6b64718..bb36a13 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -134,7 +134,8 @@ export interface ReplicationHandlers { * reserve. The copy sits in the same tier as read cache. */ cacheCopy(cid: string, peerId: string): Promise - onError?(message: string): void + /** Optional peer id when the failure happened on an inbound replication stream. */ + onError?(message: string, peerId?: string): void /** Called when a peer asks for something it is not allowed to ask for. */ onRefused?(peerId: string, op: string): void } @@ -379,7 +380,10 @@ export async function registerReplicationProtocol( respond(stream, connection, handlers, requestTimeoutMs) .then(async () => stream.close()) .catch((err: Error) => { - handlers.onError?.(`Replication request failed: ${err.message}`) + handlers.onError?.( + `Replication request failed: ${err.message}`, + connection.remotePeer.toString() + ) stream.abort(err) }) }) diff --git a/src/storage/service.ts b/src/storage/service.ts index adbb556..7fe8045 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -46,6 +46,8 @@ import { type ReplicationCallOptions } from './replicationProtocol.js' import { abortReplica, commitReplica, stageReplica } from './replicaStage.js' +import { peerWithKnownNodes } from '../peering.cron.js' +import { isStalePeerSessionError, recoverPeerSession } from '../peering/recovery.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { PER_PEER_INTAKE_BYTES, reserveIntake } from './intakeBudget.js' import { @@ -293,6 +295,21 @@ export async function prepareFileRetrieval(cid: CID, signal?: AbortSignal): Prom ) } +/** + * Reset libp2p sessions to the peers that should have held `cid` after a read + * timed out, so the next request does not reuse a broken connection. + */ +export function reportRetrievalFailure(cid: CID): void { + for (const peer of retrievalTargets( + cid.toString(), + config.replication, + selfPeerId(), + getReplicationPeers() + )) { + recoverPeerSession(peer.peerId, `retrieval timed out for ${cid}`, peerWithKnownNodes) + } +} + /** * Ask one peer to take a copy, and report what it agreed to. * @@ -635,7 +652,12 @@ export function createReplicationHandlers(): ReplicationHandlers { have: hasDurableReplica, willAccept: hasRoomForAnotherCopy, cacheCopy: cacheFileLocally, - onError: (message) => logger.warn(message), + onError: (message, peerId) => { + logger.warn(message) + if (peerId !== undefined && isStalePeerSessionError(message)) { + recoverPeerSession(peerId, message, peerWithKnownNodes) + } + }, onRefused: (peerId, op) => logger.info( { event: 'replication_refused_unknown_peer', op }, diff --git a/test/peeringRecovery.test.ts b/test/peeringRecovery.test.ts new file mode 100644 index 0000000..a2cfbf7 --- /dev/null +++ b/test/peeringRecovery.test.ts @@ -0,0 +1,19 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { isStalePeerSessionError } from '../src/peering/recovery.js' + +describe('isStalePeerSessionError', () => { + it('matches replication stream failures seen on testnet', () => { + assert.equal(isStalePeerSessionError('Replication request failed: Cannot write to a stream that is closed'), true) + assert.equal( + isStalePeerSessionError('Replication request failed: Replication stream ended before a complete message arrived'), + true + ) + assert.equal(isStalePeerSessionError('Replication request timed out'), true) + }) + + it('ignores unrelated errors', () => { + assert.equal(isStalePeerSessionError('Not authorized'), false) + assert.equal(isStalePeerSessionError('No room for another copy'), false) + }) +}) From 6b8db72129865be4f115b96d7c47cbd49b54345b Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Sun, 20 Sep 2026 14:55:21 +0300 Subject: [PATCH 154/167] fix: update AGENTS.md with Prettier and CI workflow guidelines; improve docker-smoke-test.sh shutdown polling; refactor peering cron logic; enhance tests for stale peer session errors --- AGENTS.md | 8 ++++++++ scripts/docker-smoke-test.sh | 12 ++++++------ src/peering.cron.ts | 6 +++--- test/peeringRecovery.test.ts | 11 +++++++++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 98bacf6..b23b224 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,6 +113,14 @@ Preserve backward compatibility unless the task explicitly approves a breaking c - Never claim a check passed unless it was actually executed - Report exact commands, results, and any checks that were not run +When you change JavaScript, TypeScript, JSON, Markdown, YAML, or other files covered by +Prettier, run `npm run format` before you report the work done. CI fails on `prettier --check` +the same way it fails on ESLint. Use `npm run format:fix` to apply formatting, then run +`npm run format` again to confirm a clean check. + +For code changes, run checks in the same order as the CI workflow when practical: +`npm run lint`, `npm run format`, `npm run typecheck`, then targeted or full tests. + Documentation site and container work carries further expectations: - Build the documentation site from the lockfile before claiming it works, and treat a broken diff --git a/scripts/docker-smoke-test.sh b/scripts/docker-smoke-test.sh index a66eeb4..99d114a 100755 --- a/scripts/docker-smoke-test.sh +++ b/scripts/docker-smoke-test.sh @@ -247,15 +247,15 @@ docker stop --timeout 25 "$CONTAINER" >/dev/null EXIT_CODE="$(docker container inspect --format '{{.State.ExitCode}}' "$CONTAINER")" [ "$EXIT_CODE" = "0" ] || fail "the container exited with code ${EXIT_CODE} instead of 0" # `docker stop` returns as soon as the container has exited, which can be before -# the daemon has flushed its last lines to the log driver. Reading once made the -# check fail intermittently on a loaded runner while the line was already there a -# moment later, so poll for up to five seconds instead. +# the daemon has flushed its last lines to the log driver. Five seconds was not +# always enough on a loaded linux/amd64 runner, so poll for up to thirty seconds. shutdown_logged() { - docker logs "$CONTAINER" 2>&1 | grep -q 'Received SIGTERM, shutting down' + docker logs "$CONTAINER" 2>&1 | + grep -qE 'Received SIGTERM, shutting down|Helia node stopped' } -for _ in $(seq 1 50); do +for _ in $(seq 1 150); do shutdown_logged && break - sleep 0.1 + sleep 0.2 done shutdown_logged || fail "no graceful shutdown was logged" diff --git a/src/peering.cron.ts b/src/peering.cron.ts index 69bde35..eff0e3f 100644 --- a/src/peering.cron.ts +++ b/src/peering.cron.ts @@ -77,9 +77,9 @@ export async function peerWithKnownNodes(): Promise { } } - lastConnected = helia.libp2p.getPeers().filter((peer) => - known.some((node) => node.peerId.equals(peer)) - ).length + lastConnected = helia.libp2p + .getPeers() + .filter((peer) => known.some((node) => node.peerId.equals(peer))).length return lastConnected } diff --git a/test/peeringRecovery.test.ts b/test/peeringRecovery.test.ts index a2cfbf7..dc0d6b1 100644 --- a/test/peeringRecovery.test.ts +++ b/test/peeringRecovery.test.ts @@ -4,9 +4,16 @@ import { isStalePeerSessionError } from '../src/peering/recovery.js' describe('isStalePeerSessionError', () => { it('matches replication stream failures seen on testnet', () => { - assert.equal(isStalePeerSessionError('Replication request failed: Cannot write to a stream that is closed'), true) assert.equal( - isStalePeerSessionError('Replication request failed: Replication stream ended before a complete message arrived'), + isStalePeerSessionError( + 'Replication request failed: Cannot write to a stream that is closed' + ), + true + ) + assert.equal( + isStalePeerSessionError( + 'Replication request failed: Replication stream ended before a complete message arrived' + ), true ) assert.equal(isStalePeerSessionError('Replication request timed out'), true) From 4fd7b3fdafc3185ae9f9e95fff7cf0538a20f8ec Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Sun, 20 Sep 2026 16:30:58 +0300 Subject: [PATCH 155/167] fix: enhance peer recovery and liveness mechanisms This update introduces a new system for managing unhealthy connected peers, replacing the previous ping and reset logic with a more efficient dropUnhealthyConnectedPeers function. It also implements an in-flight pass mechanism to prevent duplicate peering sweeps. Additionally, the peer session recovery logic has been modularized for better maintainability, and tests have been added to ensure the reliability of these new features. Unused imports and functions related to retrieval failure reporting have been removed for clarity. --- src/api/file.ts | 7 +- src/peering.cron.ts | 46 ++++--- src/peering/dropUnhealthyPeers.ts | 33 +++++ src/peering/inFlightPass.ts | 18 +++ src/peering/peerSessionRecovery.ts | 58 +++++++++ src/peering/peerSessionRecoveryRuntime.ts | 53 ++++++++ src/peering/recovery.ts | 58 +-------- src/peering/redialKnownPeer.ts | 27 ++++ src/peering/staleSessionErrors.ts | 11 ++ src/storage/service.ts | 22 ++-- test/peeringRecovery.test.ts | 146 +++++++++++++++++++++- 11 files changed, 383 insertions(+), 96 deletions(-) create mode 100644 src/peering/dropUnhealthyPeers.ts create mode 100644 src/peering/inFlightPass.ts create mode 100644 src/peering/peerSessionRecovery.ts create mode 100644 src/peering/peerSessionRecoveryRuntime.ts create mode 100644 src/peering/redialKnownPeer.ts create mode 100644 src/peering/staleSessionErrors.ts diff --git a/src/api/file.ts b/src/api/file.ts index 61f85e3..0f31e1d 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -12,8 +12,7 @@ import { effectiveQuorum, prepareFileRetrieval, releaseFile, - replicateUploadedFile, - reportRetrievalFailure + replicateUploadedFile } from '../storage/service.js' import { fileRegistry } from '../storage/state.js' import { createUploadHandler } from './uploadRoute.js' @@ -24,7 +23,6 @@ import { setDownloadHeaders } from '../utils/downloadResponse.js' import { downloadFile, getFileStats } from '../utils/file.js' -import { FileNotFoundError } from '../utils/fileErrors.js' import { logger } from '../utils/logger.js' const router = Router() @@ -144,9 +142,6 @@ router.get('/:cid', readLimiter, admitDownload, async (req, res, next) => { ) } catch (error) { if (requestController.signal.aborted || res.destroyed) return - if (error instanceof FileNotFoundError && cid !== undefined) { - reportRetrievalFailure(cid) - } next(error) } }) diff --git a/src/peering.cron.ts b/src/peering.cron.ts index eff0e3f..03a1aa2 100644 --- a/src/peering.cron.ts +++ b/src/peering.cron.ts @@ -1,7 +1,8 @@ import { CronJob } from 'cron' import { config } from './config.js' import { helia } from './helia.js' -import { pingPeer, resetPeerConnection } from './peering/liveness.js' +import { dropUnhealthyConnectedPeers } from './peering/dropUnhealthyPeers.js' +import { createInFlightPass } from './peering/inFlightPass.js' import { logger } from './utils/logger.js' import { getNodesList } from './utils/utils.js' @@ -24,24 +25,20 @@ let lastConnected = 0 async function dropUnhealthyKnownPeers(): Promise { const known = getNodesList([helia.libp2p.peerId.toString()]) const connected = new Set(helia.libp2p.getPeers().map((peer) => peer.toString())) - - await Promise.all( - known - .filter((node) => connected.has(node.peerId.toString())) - .map(async (node) => { - const alive = await pingPeer(helia, node.peerId) - - if (alive) { - return - } - - logger.warn( - { event: 'peering_liveness_failed', peer: node.name }, - `Peering liveness check failed for ${node.name}; resetting connection` - ) - await resetPeerConnection(helia, node.peerId) - }) + const resetPeerIds = await dropUnhealthyConnectedPeers( + helia, + known.map((node) => node.peerId), + connected ) + + for (const peerId of resetPeerIds) { + const node = known.find((entry) => entry.peerId.toString() === peerId) + + logger.warn( + { event: 'peering_liveness_failed', peer: node?.name ?? peerId }, + `Peering liveness check failed for ${node?.name ?? peerId}; resetting connection` + ) + } } /** @@ -57,7 +54,9 @@ async function dropUnhealthyKnownPeers(): Promise { * * @returns How many nodes are connected after this pass */ -export async function peerWithKnownNodes(): Promise { +const runPeeringPass = createInFlightPass() + +async function executePeeringPass(): Promise { await dropUnhealthyKnownPeers() const known = getNodesList([helia.libp2p.peerId.toString()]) @@ -84,6 +83,15 @@ export async function peerWithKnownNodes(): Promise { return lastConnected } +/** + * Dial configured peers that are not connected, after a liveness sweep. + * + * Concurrent callers share one in-flight pass instead of running duplicate sweeps. + */ +export async function peerWithKnownNodes(): Promise { + return runPeeringPass(executePeeringPass) +} + export const peeringCron = new CronJob(config.peeringSchedule, () => { if (running) { return diff --git a/src/peering/dropUnhealthyPeers.ts b/src/peering/dropUnhealthyPeers.ts new file mode 100644 index 0000000..691cbf5 --- /dev/null +++ b/src/peering/dropUnhealthyPeers.ts @@ -0,0 +1,33 @@ +import type { PeerId } from '@libp2p/interface' +import type { IpfsNode } from '../ipfs-node.js' +import { pingPeer, resetPeerConnection } from './liveness.js' + +/** + * Ping configured peers that appear connected and reset any that fail the ping. + * + * @returns Peer IDs whose connections were reset + */ +export async function dropUnhealthyConnectedPeers( + node: IpfsNode, + knownPeerIds: PeerId[], + connectedPeerIds: Set +): Promise { + const resetPeerIds: string[] = [] + + await Promise.all( + knownPeerIds + .filter((peerId) => connectedPeerIds.has(peerId.toString())) + .map(async (peerId) => { + const alive = await pingPeer(node, peerId) + + if (alive) { + return + } + + await resetPeerConnection(node, peerId) + resetPeerIds.push(peerId.toString()) + }) + ) + + return resetPeerIds +} diff --git a/src/peering/inFlightPass.ts b/src/peering/inFlightPass.ts new file mode 100644 index 0000000..c6300c4 --- /dev/null +++ b/src/peering/inFlightPass.ts @@ -0,0 +1,18 @@ +/** + * Run `task` at most once concurrently; later callers await the same promise. + */ +export function createInFlightPass(): (task: () => Promise) => Promise { + let inFlight: Promise | undefined + + return (task: () => Promise): Promise => { + if (inFlight !== undefined) { + return inFlight + } + + inFlight = task().finally(() => { + inFlight = undefined + }) + + return inFlight + } +} diff --git a/src/peering/peerSessionRecovery.ts b/src/peering/peerSessionRecovery.ts new file mode 100644 index 0000000..4c13de2 --- /dev/null +++ b/src/peering/peerSessionRecovery.ts @@ -0,0 +1,58 @@ +/** Do not reset the same peer more than once in this window. */ +export const RECOVERY_COOLDOWN_MS = 30_000 + +const lastRecoveryAt = new Map() + +export type PeerSessionRecoveryActions = { + isConfiguredPeer: (peerId: string) => boolean + ping: (peerId: string) => Promise + reset: (peerId: string) => Promise + redial: (peerId: string) => Promise + now: () => number +} + +/** + * Drop connections to a configured peer after a failed ping and redial that peer. + * + * @param peerId Configured peer identifier string + * @param reason Logged context for operators + * @param actions Recovery hooks (required in unit tests to avoid starting Helia) + */ +export function recoverPeerSession( + peerId: string, + reason: string, + actions: PeerSessionRecoveryActions +): void { + if (!actions.isConfiguredPeer(peerId)) { + return + } + + const now = actions.now() + const last = lastRecoveryAt.get(peerId) + + if (last !== undefined && now - last < RECOVERY_COOLDOWN_MS) { + return + } + + lastRecoveryAt.set(peerId, now) + + void (async () => { + try { + const alive = await actions.ping(peerId) + + if (alive) { + return + } + + await actions.reset(peerId) + await actions.redial(peerId) + } catch { + // Caller/runtime logs failures when a real node is available. + } + })() +} + +/** Clear cooldown state between unit tests. */ +export function resetPeerSessionRecoveryStateForTests(): void { + lastRecoveryAt.clear() +} diff --git a/src/peering/peerSessionRecoveryRuntime.ts b/src/peering/peerSessionRecoveryRuntime.ts new file mode 100644 index 0000000..d5a9f31 --- /dev/null +++ b/src/peering/peerSessionRecoveryRuntime.ts @@ -0,0 +1,53 @@ +import { peerIdFromString } from '@libp2p/peer-id' +import { helia } from '../helia.js' +import { getNodesList } from '../utils/utils.js' +import { logger } from '../utils/logger.js' +import { pingPeer, resetPeerConnection } from './liveness.js' +import { redialKnownPeer } from './redialKnownPeer.js' +import { + recoverPeerSession as recoverPeerSessionCore, + type PeerSessionRecoveryActions +} from './peerSessionRecovery.js' + +function defaultRecoveryActions(): PeerSessionRecoveryActions { + const selfPeerId = helia.libp2p.peerId.toString() + + return { + isConfiguredPeer: (peerId) => + getNodesList([selfPeerId]).some((node) => node.peerId.toString() === peerId), + ping: async (peerId) => pingPeer(helia, peerIdFromString(peerId)), + reset: async (peerId) => resetPeerConnection(helia, peerIdFromString(peerId)), + redial: redialKnownPeer, + now: Date.now + } +} + +/** + * Reset a configured peer session when reactive recovery is warranted. + */ +export function recoverPeerSession(peerId: string, reason: string): void { + const actions = defaultRecoveryActions() + + recoverPeerSessionCore(peerId, reason, { + ...actions, + ping: async (peerId) => { + const alive = await actions.ping(peerId) + + if (alive) { + logger.info( + { event: 'peer_session_recovery_skipped', peerId, reason }, + 'Peer answered a liveness ping; keeping the existing libp2p session' + ) + } + + return alive + }, + reset: async (peerId) => { + logger.warn( + { event: 'peer_session_recovery', peerId, reason }, + 'Resetting libp2p session to a configured peer after a transfer failure' + ) + await actions.reset(peerId) + } + }) +} diff --git a/src/peering/recovery.ts b/src/peering/recovery.ts index e607531..8a00c86 100644 --- a/src/peering/recovery.ts +++ b/src/peering/recovery.ts @@ -1,56 +1,2 @@ -import { peerIdFromString } from '@libp2p/peer-id' -import { helia } from '../helia.js' -import { logger } from '../utils/logger.js' -import { resetPeerConnection } from './liveness.js' - -/** Do not reset the same peer more than once in this window. */ -const RECOVERY_COOLDOWN_MS = 30_000 - -const lastRecoveryAt = new Map() - -/** - * Patterns that indicate the libp2p session is stale while TCP may still look up. - */ -export function isStalePeerSessionError(message: string): boolean { - return ( - message.includes('stream that is closed') || - message.includes('stream ended before') || - message.includes('Replication request timed out') || - message.includes('The connection is closed') - ) -} - -/** - * Drop connections to a configured peer and schedule an immediate peering pass. - * - * @param peerId Configured peer identifier string - * @param reason Logged context for operators - * @param redial Peering function injected to avoid import cycles in tests - */ -export function recoverPeerSession( - peerId: string, - reason: string, - redial: () => Promise -): void { - const now = Date.now() - const last = lastRecoveryAt.get(peerId) ?? 0 - - if (now - last < RECOVERY_COOLDOWN_MS) { - return - } - - lastRecoveryAt.set(peerId, now) - - void (async () => { - try { - logger.warn( - { event: 'peer_session_recovery', peerId, reason }, - 'Resetting libp2p session to a configured peer after a transfer failure' - ) - await resetPeerConnection(helia, peerIdFromString(peerId)) - await redial() - } catch (err) { - logger.error({ err, peerId }, 'Peer session recovery failed') - } - })() -} +export { isStalePeerSessionError } from './staleSessionErrors.js' +export { recoverPeerSession } from './peerSessionRecoveryRuntime.js' diff --git a/src/peering/redialKnownPeer.ts b/src/peering/redialKnownPeer.ts new file mode 100644 index 0000000..97eb0e8 --- /dev/null +++ b/src/peering/redialKnownPeer.ts @@ -0,0 +1,27 @@ +import { helia } from '../helia.js' +import { logger } from '../utils/logger.js' +import { getNodesList } from '../utils/utils.js' + +/** + * Time allowed for one peering dial. A node that is down must not hold up the + * others, and the next tick will try it again. + */ +const DIAL_TIMEOUT_MS = 10000 + +/** + * Redial one configured peer after reactive session recovery. + */ +export async function redialKnownPeer(peerId: string): Promise { + const known = getNodesList([helia.libp2p.peerId.toString()]) + const node = known.find((entry) => entry.peerId.toString() === peerId) + + if (node === undefined) { + return + } + + try { + await helia.libp2p.dial(node.multiAddr, { signal: AbortSignal.timeout(DIAL_TIMEOUT_MS) }) + } catch (err) { + logger.debug(`Reactive redial to ${node.name} failed: ${String(err)}`) + } +} diff --git a/src/peering/staleSessionErrors.ts b/src/peering/staleSessionErrors.ts new file mode 100644 index 0000000..26aa837 --- /dev/null +++ b/src/peering/staleSessionErrors.ts @@ -0,0 +1,11 @@ +/** + * Patterns that indicate the libp2p session is stale while TCP may still look up. + */ +export function isStalePeerSessionError(message: string): boolean { + return ( + message.includes('stream that is closed') || + message.includes('stream ended before') || + message.includes('Replication request timed out') || + message.includes('The connection is closed') + ) +} diff --git a/src/storage/service.ts b/src/storage/service.ts index 7fe8045..df9181b 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -46,7 +46,6 @@ import { type ReplicationCallOptions } from './replicationProtocol.js' import { abortReplica, commitReplica, stageReplica } from './replicaStage.js' -import { peerWithKnownNodes } from '../peering.cron.js' import { isStalePeerSessionError, recoverPeerSession } from '../peering/recovery.js' import { prepareRetrieval, retrievalTargets } from './retrieval.js' import { PER_PEER_INTAKE_BYTES, reserveIntake } from './intakeBudget.js' @@ -295,18 +294,11 @@ export async function prepareFileRetrieval(cid: CID, signal?: AbortSignal): Prom ) } -/** - * Reset libp2p sessions to the peers that should have held `cid` after a read - * timed out, so the next request does not reuse a broken connection. - */ -export function reportRetrievalFailure(cid: CID): void { - for (const peer of retrievalTargets( - cid.toString(), - config.replication, - selfPeerId(), - getReplicationPeers() - )) { - recoverPeerSession(peer.peerId, `retrieval timed out for ${cid}`, peerWithKnownNodes) +function recoverOutboundReplicationSession(peer: ReplicationPeer, err: unknown): void { + const message = err instanceof Error ? err.message : String(err) + + if (isStalePeerSessionError(message)) { + recoverPeerSession(peer.peerId, message) } } @@ -361,6 +353,7 @@ async function placeCopy( return { outcome: 'stored', staged: false } } catch (err) { if (!isNotAuthorizedError(err)) { + recoverOutboundReplicationSession(peer, err) throw err } @@ -437,6 +430,7 @@ async function repairCopy( return { outcome: 'stored', staged: false } } catch (err) { if (!isNotAuthorizedError(err)) { + recoverOutboundReplicationSession(peer, err) throw err } @@ -655,7 +649,7 @@ export function createReplicationHandlers(): ReplicationHandlers { onError: (message, peerId) => { logger.warn(message) if (peerId !== undefined && isStalePeerSessionError(message)) { - recoverPeerSession(peerId, message, peerWithKnownNodes) + recoverPeerSession(peerId, message) } }, onRefused: (peerId, op) => diff --git a/test/peeringRecovery.test.ts b/test/peeringRecovery.test.ts index dc0d6b1..3c8a417 100644 --- a/test/peeringRecovery.test.ts +++ b/test/peeringRecovery.test.ts @@ -1,6 +1,14 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' -import { isStalePeerSessionError } from '../src/peering/recovery.js' +import { dropUnhealthyConnectedPeers } from '../src/peering/dropUnhealthyPeers.js' +import { createInFlightPass } from '../src/peering/inFlightPass.js' +import { PEER_PING_TIMEOUT_MS } from '../src/peering/liveness.js' +import { + RECOVERY_COOLDOWN_MS, + recoverPeerSession, + resetPeerSessionRecoveryStateForTests +} from '../src/peering/peerSessionRecovery.js' +import { isStalePeerSessionError } from '../src/peering/staleSessionErrors.js' describe('isStalePeerSessionError', () => { it('matches replication stream failures seen on testnet', () => { @@ -24,3 +32,139 @@ describe('isStalePeerSessionError', () => { assert.equal(isStalePeerSessionError('No room for another copy'), false) }) }) + +describe('dropUnhealthyConnectedPeers', () => { + it('keeps healthy peers and resets peers that fail ping', async () => { + const peerA = { toString: () => 'peer-a' } as never + const peerB = { toString: () => 'peer-b' } as never + const connected = new Set(['peer-a', 'peer-b']) + const pinged: string[] = [] + const reset: string[] = [] + + const node = { + libp2p: { + services: { + ping: { + ping: async (peerId: { toString: () => string }) => { + pinged.push(peerId.toString()) + if (peerId.toString() === 'peer-a') { + return 1 + } + + throw new Error('ping failed') + } + } + }, + hangUp: async (peerId: { toString: () => string }) => { + reset.push(peerId.toString()) + } + } + } + + const resetPeerIds = await dropUnhealthyConnectedPeers(node as never, [peerA, peerB], connected) + + assert.deepEqual(pinged.sort(), ['peer-a', 'peer-b']) + assert.deepEqual(reset, ['peer-b']) + assert.deepEqual(resetPeerIds, ['peer-b']) + }) +}) + +async function waitForRecovery(done: () => boolean): Promise { + for (let attempt = 0; attempt < 100; attempt += 1) { + if (done()) { + return + } + + await new Promise((resolve) => setTimeout(resolve, 5)) + } + + assert.fail('peer session recovery did not finish') +} + +describe('recoverPeerSession', () => { + it('ignores unknown peers and enforces cooldown', async () => { + resetPeerSessionRecoveryStateForTests() + let now = 1_000 + const calls = { ping: 0, reset: 0, redial: 0 } + + const actions = { + isConfiguredPeer: (peerId: string) => peerId === 'known-peer', + ping: async () => { + calls.ping += 1 + return false + }, + reset: async () => { + calls.reset += 1 + }, + redial: async () => { + calls.redial += 1 + }, + now: () => now + } + + recoverPeerSession('unknown-peer', 'test', actions) + recoverPeerSession('known-peer', 'first', actions) + await waitForRecovery(() => calls.redial >= 1) + assert.equal(calls.ping, 1) + assert.equal(calls.reset, 1) + assert.equal(calls.redial, 1) + + now += RECOVERY_COOLDOWN_MS - 1 + recoverPeerSession('known-peer', 'cooldown', actions) + await waitForRecovery(() => calls.redial >= 1) + assert.equal(calls.ping, 1) + + now += 1 + recoverPeerSession('known-peer', 'after cooldown', actions) + await waitForRecovery(() => calls.redial >= 2) + assert.equal(calls.ping, 2) + }) + + it('skips reset when ping succeeds', async () => { + resetPeerSessionRecoveryStateForTests() + const calls = { reset: 0, redial: 0, ping: 0 } + + recoverPeerSession('known-peer', 'healthy', { + isConfiguredPeer: () => true, + ping: async () => { + calls.ping += 1 + return true + }, + reset: async () => { + calls.reset += 1 + }, + redial: async () => { + calls.redial += 1 + }, + now: Date.now + }) + + await waitForRecovery(() => calls.ping >= 1) + assert.equal(calls.reset, 0) + assert.equal(calls.redial, 0) + }) +}) + +describe('createInFlightPass', () => { + it('shares one in-flight promise between concurrent callers', async () => { + const runPass = createInFlightPass() + let runs = 0 + + const task = async () => { + runs += 1 + await new Promise((resolve) => setTimeout(resolve, 10)) + return runs + } + + const [first, second] = await Promise.all([runPass(task), runPass(task)]) + assert.equal(first, 1) + assert.equal(second, 1) + assert.equal(runs, 1) + }) +}) + +describe('pingPeer timeout constant', () => { + it('uses a bounded ping timeout', () => { + assert.equal(PEER_PING_TIMEOUT_MS, 5000) + }) +}) From b0e7d2f54faa5cd8c23c628a02fdebd63955813c Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Sun, 20 Sep 2026 18:19:12 +0300 Subject: [PATCH 156/167] fix: enhance peer connection management and liveness checks This update improves the peer connection management by implementing a ping mechanism for connected peers, allowing for the detection and reset of stale sessions. The peering logic has been refined to ensure that both connected and unconnected peers are appropriately managed, enhancing overall network reliability. Additionally, the documentation has been updated to reflect these changes, and tests have been added to verify the new functionality. Unused code and comments have been removed for clarity. --- docs/guide/architecture.md | 11 +++-- docs/guide/comparison.md | 2 +- docs/guide/configuration.md | 6 ++- docs/guide/use-cases.md | 2 +- docs/operations/troubleshooting.md | 3 ++ docs/storage-lifecycle.md | 10 +++-- src/api/file.ts | 4 +- src/peering.cron.ts | 46 +++++++++---------- src/peering/liveness.ts | 14 ++++-- src/peering/peerSessionRecovery.ts | 3 +- src/peering/redialKnownPeer.ts | 9 +--- src/peering/staleSessionErrors.ts | 10 ++++- src/storage/service.ts | 12 ++++- test/peeringRecovery.test.ts | 72 +++++++++++++++++++++++++++++- 14 files changed, 149 insertions(+), 55 deletions(-) diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index 815ef5f..d416488 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -34,7 +34,7 @@ Scheduled work runs in the same process: | Job | Schedule | Purpose | | ------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------- | | Disk usage | `diskUsageScanPeriod` | Refreshes the storage report behind `GET /api/storage/metrics`; one scan also runs at startup | -| Peering | `peeringSchedule` | Redials the entries in `nodes` that are not connected | +| Peering | `peeringSchedule` | Pings connected entries in `nodes`, then redials any that are missing | | Garbage collection | `storage.gc.schedule` | Frees blocks when space is short; started only when `storage.gc.enabled` is true | | Admission recovery | `storage.gc.schedule` | Replaces the collector when it is disabled, clearing upload tokens whose handler died | | Replication repair | `replication.repairSchedule` | Restores missing copies; started only when `replication.enabled` and `replication.repairEnabled` are both true | @@ -114,9 +114,12 @@ address. never again. Nothing in libp2p reconnects a peer that restarted or dropped, and a mesh that quietly comes apart still accepts uploads, so the failure surfaces later as slow retrieval and as replication that cannot place copies. The peering job closes that gap: on every `peeringSchedule` -tick it dials the entries in `nodes` that are not currently connected, gives each dial ten seconds, -and logs the failures at debug level. The work is bounded by the size of the operator's own peer -list. +tick it pings configured peers that already appear connected, hangs up any that miss the ping, then +dials the entries in `nodes` that are not currently connected. Each ping has five seconds, each +dial ten seconds, and failures log at debug or warn. Ping proves that the ping protocol answered, +not that bitswap or replication on that session still work; a session that answers ping and still +fails transfers is not reset by this sweep. The work is bounded by the size of the operator's own +peer list. The `allow` list holds the same addresses. It keeps configured peers connectable once `maxConnections` is reached. It is not an access control list: no deny list is configured, so any diff --git a/docs/guide/comparison.md b/docs/guide/comparison.md index 15f38ff..786adaf 100644 --- a/docs/guide/comparison.md +++ b/docs/guide/comparison.md @@ -18,7 +18,7 @@ deliberately general; read their own documentation before choosing one. | Property | ADAMANT IPFS Node | Kubo | Kubo with IPFS Cluster | Custom Helia application | Managed provider | Object storage | | ----------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------- | ------------------------------------------- | ---------------------------------------------- | | Runtime | One Node.js 24 process embedding Helia and Express | A Go daemon | A Go daemon plus a cluster daemon per node | A Node.js process the author maintains | Hosted by the provider | Hosted service, or a self-hosted server | -| Peer discovery | Static `nodes` list, dialled at startup and redialled on `peeringSchedule` | Public IPFS bootstrap peers and local network discovery | As Kubo, plus a cluster peer set | Whatever the author configures | Provider-operated | None; clients address a service endpoint | +| Peer discovery | Static `nodes` list, dialled at startup and kept up on `peeringSchedule` | Public IPFS bootstrap peers and local network discovery | As Kubo, plus a cluster peer set | Whatever the author configures | Provider-operated | None; clients address a service endpoint | | Content routing | None; holders are derived from the CID | Participates in the public DHT | Public DHT through Kubo | Whatever the author configures | Provider-operated | None; keys are location-addressed | | Public gateway | None | Ships an HTTP gateway | Kubo's HTTP gateway | Only if the author builds one | Provider-operated | The service HTTP endpoint | | Pin orchestration | libp2p protocol `/adamant/replication/1.0.0` between configured nodes | None; pinning is per node | Consensus inside the cluster | None; Helia has no native pin-orchestration protocol | Provider-operated | Not applicable | diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 826beab..8d85165 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -81,7 +81,7 @@ expression throws when the job is constructed, which is still before the API ser | `peerDiscovery.listen` | array of strings | required | libp2p listen multiaddrs, at least one | | `serverPort` | integer >= 1 | required | HTTP API port | | `diskUsageScanPeriod` | cron string | required | How often the storage scan that feeds the metrics runs | -| `peeringSchedule` | cron string | `*/30 * * * * *` | How often unconnected entries of `nodes` are redialled | +| `peeringSchedule` | cron string | `*/30 * * * * *` | How often connected entries of `nodes` are pinged and missing entries are redialled | | `uploadLimitSizeBytes` | integer >= 1 | required | Maximum size of one uploaded file | | `maxFileCount` | integer 1-100 | required | Maximum number of files in one upload request | | `findFileTimeout` | integer >= 1 | required | Milliseconds allowed for locating a file before a download fails | @@ -103,7 +103,9 @@ Notes on the less obvious entries: trustless, or censorship-proof; content served over `GET /api/file/:cid` is public to anyone who has the CID. - `peerDiscovery.bootstrap` dials once at startup and never again, which is why `peeringSchedule` - exists. Without a peering schedule a mesh never recovers from a restart. + exists. Without a peering schedule a mesh never recovers from a restart. The same job pings + connected configured peers and hangs up any that miss the ping so the tick can redial them. Ping + is a cheap liveness signal, not a bitswap or replication health check. - The cron examples use the six-field form with a leading seconds field - `downloadMaxDurationMs` caps the deadline of a single response. The effective deadline is the smaller of this ceiling and the size-aware deadline diff --git a/docs/guide/use-cases.md b/docs/guide/use-cases.md index 2de8223..13e1283 100644 --- a/docs/guide/use-cases.md +++ b/docs/guide/use-cases.md @@ -87,7 +87,7 @@ its own contract, documented in [API reference](/reference/api). Use [Kubo with IPFS Cluster](https://ipfscluster.io). Peers come from a static `nodes` list that must be applied consistently on every member, bootstrap dials that list once and `peeringSchedule` -redials it, and changing the list changes the peer-set epoch, which resets persisted health +keeps it connected, and changing the list changes the peer-set epoch, which resets persisted health checkpoint heights. Durable placement is accepted only from configured peers, so every member is a party the others have chosen to trust. Peer discovery instead of static lists is open work ([issue #28](https://github.com/Adamant-im/ipfs-node/issues/28)) and is not available. diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index 9ba771e..96cafde 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -91,6 +91,9 @@ Likely causes: - No connected peer holds the content. Check `GET /api/libp2p/peers` and the peering log. Bootstrap dials once, so `peeringSchedule` is what keeps the mesh together after a restart +- A libp2p TCP session is up while bitswap or replication is not. Look for `peering_liveness_failed` + or `peer_session_recovery` in the log. The peering job pings connected configured peers and + resets those that miss the ping; a session that still answers ping is left in place. - The CID exists only on the public IPFS network. This node registers no content routing and will never find it - The content was released and its blocks were reclaimed on every holder diff --git a/docs/storage-lifecycle.md b/docs/storage-lifecycle.md index 59f7841..8423686 100644 --- a/docs/storage-lifecycle.md +++ b/docs/storage-lifecycle.md @@ -463,9 +463,13 @@ damage shows up later as slow retrieval and as replication that cannot place copies. The node therefore redials the peers in `nodes` that are not connected, once at -startup before the API starts serving and then on `peeringSchedule`. The work is -bounded by the size of `nodes`, which is the operator's own peer list, so it -never becomes network-wide dialling. +startup before the API starts serving and then on `peeringSchedule`. The same +pass pings configured peers that already appear connected and hangs up any that +miss the ping, so a TCP session that stayed up after the application protocols +died can be replaced. Ping is a cheap liveness signal, not proof that bitswap +or replication on that session still work. The work is bounded by the size of +`nodes`, which is the operator's own peer list, so it never becomes +network-wide dialling. ### Repair diff --git a/src/api/file.ts b/src/api/file.ts index 0f31e1d..e1ccacb 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -108,10 +108,8 @@ router.get('/:cid', readLimiter, admitDownload, async (req, res, next) => { } }) - let cid: ReturnType | undefined - try { - cid = parseCid(req.params.cid) + const cid = parseCid(req.params.cid) // Reach the file's holders first. Without this the read only succeeds if a // peer that has the file is already connected, which stops being true as diff --git a/src/peering.cron.ts b/src/peering.cron.ts index 03a1aa2..c709b24 100644 --- a/src/peering.cron.ts +++ b/src/peering.cron.ts @@ -3,15 +3,10 @@ import { config } from './config.js' import { helia } from './helia.js' import { dropUnhealthyConnectedPeers } from './peering/dropUnhealthyPeers.js' import { createInFlightPass } from './peering/inFlightPass.js' +import { PEER_DIAL_TIMEOUT_MS } from './peering/liveness.js' import { logger } from './utils/logger.js' import { getNodesList } from './utils/utils.js' -/** - * Time allowed for one peering dial. A node that is down must not hold up the - * others, and the next tick will try it again. - */ -const DIAL_TIMEOUT_MS = 10000 - let running = false let lastConnected = 0 @@ -19,8 +14,9 @@ let lastConnected = 0 * Ping configured peers that appear connected and reset any that do not answer. * * `@libp2p/bootstrap` only runs once; peering already redials missing peers. - * That is not enough when TCP stays up but bitswap or replication streams are - * broken — those peers never count as "missing". + * That is not enough when TCP stays up but the session is otherwise dead — + * those peers never count as "missing". Ping is a cheap liveness signal, not + * proof that bitswap or replication on that session still work. */ async function dropUnhealthyKnownPeers(): Promise { const known = getNodesList([helia.libp2p.peerId.toString()]) @@ -41,19 +37,6 @@ async function dropUnhealthyKnownPeers(): Promise { } } -/** - * Dial the configured ADAMANT nodes that are not currently connected. - * - * `@libp2p/bootstrap` emits its peers once shortly after start and never again, - * so nothing reconnects a peer that restarted or dropped. A mesh that quietly - * comes apart still serves uploads, which makes the failure easy to miss: it - * shows up later as slow retrieval and as replication that cannot place copies. - * - * The work is bounded by the size of `nodes`, which is the operator's own peer - * list, so this never turns into network-wide dialling. - * - * @returns How many nodes are connected after this pass - */ const runPeeringPass = createInFlightPass() async function executePeeringPass(): Promise { @@ -65,7 +48,7 @@ async function executePeeringPass(): Promise { const results = await Promise.allSettled( missing.map(async (node) => { - await helia.libp2p.dial(node.multiAddr, { signal: AbortSignal.timeout(DIAL_TIMEOUT_MS) }) + await helia.libp2p.dial(node.multiAddr, { signal: AbortSignal.timeout(PEER_DIAL_TIMEOUT_MS) }) return node.name }) ) @@ -84,9 +67,24 @@ async function executePeeringPass(): Promise { } /** - * Dial configured peers that are not connected, after a liveness sweep. + * Keep configured peers connected: reset sessions that miss a ping, then dial + * any that are not connected. + * + * `@libp2p/bootstrap` emits its peers once shortly after start and never again, + * so nothing reconnects a peer that restarted or dropped. A mesh that quietly + * comes apart still serves uploads, which makes the failure easy to miss: it + * shows up later as slow retrieval and as replication that cannot place copies. + * Connected peers that no longer answer ping have the same symptom, and they + * never count as missing, so this pass pings them first. + * + * Ping is a cheap liveness signal, not proof that bitswap or replication on + * that session still work. + * + * Concurrent callers share one in-flight pass instead of running duplicate + * sweeps. The work is bounded by the size of `nodes`, which is the operator's + * own peer list, so this never turns into network-wide dialling. * - * Concurrent callers share one in-flight pass instead of running duplicate sweeps. + * @returns How many configured nodes are connected after this pass */ export async function peerWithKnownNodes(): Promise { return runPeeringPass(executePeeringPass) diff --git a/src/peering/liveness.ts b/src/peering/liveness.ts index aea7e73..5e537a2 100644 --- a/src/peering/liveness.ts +++ b/src/peering/liveness.ts @@ -1,14 +1,22 @@ import type { PeerId } from '@libp2p/interface' import type { IpfsNode } from '../ipfs-node.js' -/** Time allowed for a libp2p ping that validates an existing mesh connection. */ +/** Time allowed for a libp2p ping that checks an existing mesh connection. */ export const PEER_PING_TIMEOUT_MS = 5000 +/** + * Time allowed for one peering dial. A node that is down must not hold up the + * others, and the next tick will try it again. + */ +export const PEER_DIAL_TIMEOUT_MS = 10_000 + /** * Return whether `peerId` answers a libp2p ping within the timeout. * - * A TCP session can stay up while application protocols on it are broken; ping - * exercises the same stream stack bitswap and replication rely on. + * Ping is a cheap liveness signal, not proof that bitswap or replication on + * the same session still work. A TCP session can stay up while those + * application protocols are broken; a session that still answers ping is left + * in place. */ export async function pingPeer( node: IpfsNode, diff --git a/src/peering/peerSessionRecovery.ts b/src/peering/peerSessionRecovery.ts index 4c13de2..7e4cefd 100644 --- a/src/peering/peerSessionRecovery.ts +++ b/src/peering/peerSessionRecovery.ts @@ -15,7 +15,8 @@ export type PeerSessionRecoveryActions = { * Drop connections to a configured peer after a failed ping and redial that peer. * * @param peerId Configured peer identifier string - * @param reason Logged context for operators + * @param reason Operator-facing explanation. Unused here; kept on the core + * signature so the runtime wrapper can log the same argument * @param actions Recovery hooks (required in unit tests to avoid starting Helia) */ export function recoverPeerSession( diff --git a/src/peering/redialKnownPeer.ts b/src/peering/redialKnownPeer.ts index 97eb0e8..0937584 100644 --- a/src/peering/redialKnownPeer.ts +++ b/src/peering/redialKnownPeer.ts @@ -1,12 +1,7 @@ import { helia } from '../helia.js' import { logger } from '../utils/logger.js' import { getNodesList } from '../utils/utils.js' - -/** - * Time allowed for one peering dial. A node that is down must not hold up the - * others, and the next tick will try it again. - */ -const DIAL_TIMEOUT_MS = 10000 +import { PEER_DIAL_TIMEOUT_MS } from './liveness.js' /** * Redial one configured peer after reactive session recovery. @@ -20,7 +15,7 @@ export async function redialKnownPeer(peerId: string): Promise { } try { - await helia.libp2p.dial(node.multiAddr, { signal: AbortSignal.timeout(DIAL_TIMEOUT_MS) }) + await helia.libp2p.dial(node.multiAddr, { signal: AbortSignal.timeout(PEER_DIAL_TIMEOUT_MS) }) } catch (err) { logger.debug(`Reactive redial to ${node.name} failed: ${String(err)}`) } diff --git a/src/peering/staleSessionErrors.ts b/src/peering/staleSessionErrors.ts index 26aa837..7e371b2 100644 --- a/src/peering/staleSessionErrors.ts +++ b/src/peering/staleSessionErrors.ts @@ -1,11 +1,17 @@ /** - * Patterns that indicate the libp2p session is stale while TCP may still look up. + * Detect libp2p session-stale failures on replication streams. + * + * Call this only from replication error paths. Substring matching is coarse: + * `'stream ended before'` also appears in the health protocol, and + * `'The connection is closed'` can match a benign shutdown race. A match only + * schedules a ping, never an immediate hang-up. */ export function isStalePeerSessionError(message: string): boolean { return ( message.includes('stream that is closed') || - message.includes('stream ended before') || + message.includes('Replication stream ended before') || message.includes('Replication request timed out') || + message.includes('Replication message timed out') || message.includes('The connection is closed') ) } diff --git a/src/storage/service.ts b/src/storage/service.ts index df9181b..8ea3524 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -125,7 +125,12 @@ async function placeReplicas( config: config.replication, store, cacheOnly: async (peer) => { - await requestCache(helia, peer.multiAddr, cid, callOptions()) + try { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + } catch (err) { + recoverOutboundReplicationSession(peer, err) + throw err + } } }) } @@ -339,6 +344,8 @@ async function placeCopy( throw err } + recoverOutboundReplicationSession(peer, err) + // A lost ack after a pin must still be aborted. A structured refusal // (busy, not staged, already aborted) never took ownership. return { @@ -417,7 +424,8 @@ async function repairCopy( if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { return { outcome: 'stored', staged: false } } - } catch { + } catch (err) { + recoverOutboundReplicationSession(peer, err) // No matching stage, or the peer is still temporary; `store` is next. } } diff --git a/test/peeringRecovery.test.ts b/test/peeringRecovery.test.ts index 3c8a417..ed88edc 100644 --- a/test/peeringRecovery.test.ts +++ b/test/peeringRecovery.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { dropUnhealthyConnectedPeers } from '../src/peering/dropUnhealthyPeers.js' import { createInFlightPass } from '../src/peering/inFlightPass.js' -import { PEER_PING_TIMEOUT_MS } from '../src/peering/liveness.js' +import { PEER_PING_TIMEOUT_MS, pingPeer } from '../src/peering/liveness.js' import { RECOVERY_COOLDOWN_MS, recoverPeerSession, @@ -25,11 +25,21 @@ describe('isStalePeerSessionError', () => { true ) assert.equal(isStalePeerSessionError('Replication request timed out'), true) + assert.equal(isStalePeerSessionError('Replication message timed out'), true) + assert.equal( + isStalePeerSessionError('Replication request failed: Replication message timed out'), + true + ) + assert.equal(isStalePeerSessionError('The connection is closed'), true) }) it('ignores unrelated errors', () => { assert.equal(isStalePeerSessionError('Not authorized'), false) assert.equal(isStalePeerSessionError('No room for another copy'), false) + assert.equal( + isStalePeerSessionError('Health stream ended before a complete message arrived'), + false + ) }) }) @@ -67,6 +77,29 @@ describe('dropUnhealthyConnectedPeers', () => { assert.deepEqual(reset, ['peer-b']) assert.deepEqual(resetPeerIds, ['peer-b']) }) + + it('does not ping configured peers that are not connected', async () => { + const peerA = { toString: () => 'peer-a' } as never + const pinged: string[] = [] + const node = { + libp2p: { + services: { + ping: { + ping: async (peerId: { toString: () => string }) => { + pinged.push(peerId.toString()) + return 1 + } + } + }, + hangUp: async () => {} + } + } + + const resetPeerIds = await dropUnhealthyConnectedPeers(node as never, [peerA], new Set()) + + assert.deepEqual(pinged, []) + assert.deepEqual(resetPeerIds, []) + }) }) async function waitForRecovery(done: () => boolean): Promise { @@ -161,10 +194,45 @@ describe('createInFlightPass', () => { assert.equal(second, 1) assert.equal(runs, 1) }) + + it('runs a new pass after the previous one finishes', async () => { + const runPass = createInFlightPass() + let runs = 0 + const task = async () => { + runs += 1 + return runs + } + + assert.equal(await runPass(task), 1) + assert.equal(await runPass(task), 2) + }) }) -describe('pingPeer timeout constant', () => { +describe('pingPeer', () => { it('uses a bounded ping timeout', () => { assert.equal(PEER_PING_TIMEOUT_MS, 5000) }) + + it('returns false when ping does not settle before the timeout', async () => { + const node = { + libp2p: { + services: { + ping: { + ping: async (_peerId: unknown, options?: { signal?: AbortSignal }) => + new Promise((_resolve, reject) => { + options?.signal?.addEventListener('abort', () => { + reject(new Error('aborted')) + }) + }) + } + } + } + } + + const startedAt = Date.now() + const alive = await pingPeer(node as never, { toString: () => 'peer-a' } as never, 20) + + assert.equal(alive, false) + assert.equal(Date.now() - startedAt < 500, true) + }) }) From 6b7e8b3895afa28a59b673dd413bd0d51f8db911 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Mon, 21 Sep 2026 08:25:49 +0300 Subject: [PATCH 157/167] docs: add .npmrc for strict script handling and update documentation Introduced a new .npmrc file to enforce strict handling of install scripts, ensuring that npm ci fails on unapproved scripts. Updated AGENTS.md, README.md, and various documentation files to reflect the new script approval process and the importance of allowing specific packages in package.json. Enhanced troubleshooting guidance for issues related to the node-datachannel native module and clarified the installation process in contributing and installation guides. --- .github/workflows/ci.yml | 2 ++ .github/workflows/docs.yml | 5 +++-- .npmrc | 3 +++ AGENTS.md | 28 ++++++++++++++++++++++++++++ Dockerfile | 6 ++++-- README.md | 15 ++++++++++----- docs/guide/contributing.md | 20 +++++++++++++++++--- docs/guide/docker.md | 4 +++- docs/guide/installation.md | 17 ++++++++++------- docs/guide/quick-start.md | 10 ++++++---- docs/guide/security.md | 4 +++- docs/operations/troubleshooting.md | 19 +++++++++++++++++++ docs/operations/upgrades.md | 12 +++++++++--- package.json | 5 +++++ 14 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 .npmrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3b28a7..106d243 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,8 @@ jobs: # Install scripts must run here: Helia pulls @libp2p/webrtc, whose # node-datachannel binary is required to import the module at all. + # package.json allowScripts permits that download; .npmrc + # strict-allow-scripts fails the job if a new unreviewed script appears. - name: Install dependencies run: npm ci diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2283a9f..7dc3a72 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -58,8 +58,9 @@ jobs: cache: npm # The site build touches no runtime dependency, so the node-datachannel - # prebuild download that `npm ci` normally performs is skipped here. Every - # other workflow installs with scripts enabled. + # prebuild download that `npm ci` normally performs is skipped here. + # `--ignore-scripts` overrides allowScripts; every other workflow + # installs with scripts enabled. - name: Install dependencies run: npm ci --ignore-scripts diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..74289c5 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +# npm 12 skips unapproved dependency install scripts. Fail the install when a +# new one appears, instead of producing a tree that cannot start Helia. +strict-allow-scripts=true diff --git a/AGENTS.md b/AGENTS.md index b23b224..dd508e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,6 +105,34 @@ Preserve backward compatibility unless the task explicitly approves a breaking c - Preserve existing privacy, decentralization, and self-hosting properties - Stop and request maintainer guidance when a change has unclear security, privacy, compatibility, or data-integrity consequences +## Install scripts + +npm 12 blocks dependency `preinstall`, `install`, and `postinstall` scripts unless the package is +listed in `package.json` `allowScripts`. `.npmrc` sets `strict-allow-scripts=true`, so `npm ci` +fails when a new unreviewed script appears instead of silently skipping it and producing a tree +that cannot start Helia. + +This repository allows only: + +- `node-datachannel` — Helia depends on `@libp2p/webrtc`; the native binary is required to import the runtime +- `esbuild` — VitePress documentation tooling +- `fsevents` — optional macOS file watching for `npm run dev` + +The entries are package names, not pinned versions, so a routine lockfile bump of the same package +does not need a second approval. + +When a lockfile change introduces a package with an install script: + +1. Run `npm install-scripts ls` and read the names +2. Approve only what this project needs: `npm install-scripts approve --no-allow-scripts-pin ` +3. Do not use `approve --all` without reviewing each name +4. Never install a runnable or testable tree with `--ignore-scripts`; that flag is only for the + documentation-site and security-audit CI jobs, and for auditing + +A process that crashes with `Cannot find module '.../node_datachannel.node'` was installed without +the native binary. Delete `node_modules`, run a normal `npm ci` that can reach GitHub releases, and +confirm `node_modules/node-datachannel/build/Release/node_datachannel.node` exists before starting. + ## Validation Policy - Use repository-defined scripts and configuration as the source of truth for checks diff --git a/Dockerfile b/Dockerfile index 77a4577..d1b707d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,10 @@ FROM ${NODE_IMAGE} AS build WORKDIR /app # Install scripts must run: @libp2p/webrtc pulls node-datachannel, whose binary -# is downloaded here. A tree installed with --ignore-scripts fails at startup. -COPY package.json package-lock.json ./ +# is downloaded here. package.json allowScripts permits that download; .npmrc +# fails the build if a new unreviewed install script appears. A tree installed +# with --ignore-scripts fails at startup. +COPY package.json package-lock.json .npmrc ./ RUN npm ci COPY tsconfig.json tsconfig.build.json ./ diff --git a/README.md b/README.md index ed1afff..ba22bc9 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,10 @@ node dist/index.js ``` `npm ci` must run install scripts. Helia depends on `@libp2p/webrtc`, whose `node-datachannel` -native module downloads a prebuilt binary from GitHub releases; installing with `--ignore-scripts` -produces a tree that fails at startup. See [Dependency notes](#dependency-notes). +native module downloads a prebuilt binary from GitHub releases. npm 12 blocks that download unless +the package is listed in `package.json` `allowScripts`; this repository already lists it. +Installing with `--ignore-scripts`, or on a host that cannot reach GitHub releases, produces a tree +that fails at startup. See [Dependency notes](#dependency-notes). Copy `config.default.json5` to `config.json5` before starting. **Its peer list points at the ADAMANT production mesh**, so a deployment that is not joining ADAMANT must replace `nodes`, @@ -269,9 +271,12 @@ Use `npm run security:audit:raw` to inspect the unfiltered npm result. configured here. That pulls two things into the tree: - `node-datachannel`, a native module whose prebuilt binary is downloaded from GitHub releases - during `npm install`. An installer that reaches the npm registry but not GitHub releases produces - a tree that fails at startup, so `--ignore-scripts` is suitable only for auditing and for building - the documentation site, not for running or testing + during `npm install`. npm 12 runs that script only because `allowScripts` names the package. + `.npmrc` sets `strict-allow-scripts=true`, so a new unreviewed install script fails `npm ci` + instead of silently skipping. An installer that reaches the npm registry but not GitHub + releases, or one run with `--ignore-scripts`, produces a tree that fails at startup, so + `--ignore-scripts` is suitable only for auditing and for building the documentation site, not + for running or testing - `react-native-webrtc`, and through it `react-native` and its Metro bundler, in the development tree only diff --git a/docs/guide/contributing.md b/docs/guide/contributing.md index 541fb52..20c8bbc 100644 --- a/docs/guide/contributing.md +++ b/docs/guide/contributing.md @@ -20,9 +20,22 @@ cp config.default.json5 config.json5 npm run dev ``` -`npm run dev` runs the service under nodemon. `npm ci` must be allowed to run install scripts: a -native module of the WebRTC transport package downloads its prebuilt binary during installation, and -a tree installed with `--ignore-scripts` fails at startup. +`npm run dev` runs the service under nodemon. `npm ci` must run install scripts: Helia pulls +`@libp2p/webrtc`, whose `node-datachannel` native module downloads a prebuilt binary during +installation. npm 12 blocks that download unless the package is in `package.json` `allowScripts`; +this repository already lists `node-datachannel`, `esbuild`, and `fsevents`. `.npmrc` sets +`strict-allow-scripts=true`, so a new unreviewed install script fails the install instead of +silently skipping. A tree installed with `--ignore-scripts` fails at startup. + +When a lockfile change introduces a package with an install script, run `npm install-scripts ls`, +review the name, and approve only what this project needs: + +```bash +npm install-scripts approve --no-allow-scripts-pin +``` + +Do not use `approve --all` without reading each name. The documentation-site and security-audit CI +jobs are the only installs that use `--ignore-scripts`. Replace the peer list in `config.json5` before starting. The shipped template points at the ADAMANT production mesh. @@ -35,6 +48,7 @@ production mesh. | `src/middleware/` | Rate limiting, admission control, upload and download guards, error handling | | `src/security/` | CORS, trusted proxy, API key, rate-limit policy, access-policy mounting | | `src/storage/` | Lifecycle registry, admission, placement, replication, repair, collection | +| `src/peering/` | libp2p ping liveness, session recovery, and in-flight peering-pass sharing | | `src/health/` | Checkpoint state, health service, libp2p health protocol, membership | | `src/utils/` | Logger, CID helpers, download responses, filename sanitization | | `test/` | Unit suites | diff --git a/docs/guide/docker.md b/docs/guide/docker.md index 5c96463..293d10a 100644 --- a/docs/guide/docker.md +++ b/docs/guide/docker.md @@ -277,7 +277,9 @@ docker build -t ipfs-node:local . ``` The build needs network access, and install scripts must run: `node-datachannel` downloads its -prebuilt binary during `npm ci`, and a tree installed with `--ignore-scripts` fails at startup. +prebuilt binary during `npm ci`. The image copies `package.json` `allowScripts` and `.npmrc` +`strict-allow-scripts=true`, so that download is permitted and a new unreviewed install script +fails the build. A tree installed with `--ignore-scripts` fails at startup. | Build argument | Default | Purpose | | ---------------- | ---------------------------- | ----------------------------------------- | diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 888c761..c537314 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -85,8 +85,11 @@ a warning that TLS is not handled at the application level, and a warning while `npm ci` must run install scripts. `helia` depends on `@helia/libp2p`, which depends on `@libp2p/webrtc`, whose `node-datachannel` native module downloads a prebuilt binary from GitHub -releases during installation. Installing with `--ignore-scripts` produces a tree that fails at -startup, and an installer that reaches the npm registry but not GitHub releases fails the same way. +releases during installation. npm 12 blocks that download unless the package is listed in +`package.json` `allowScripts`; this repository already lists it, and `.npmrc` sets +`strict-allow-scripts=true` so a new unreviewed install script fails the install instead of +silently skipping. Installing with `--ignore-scripts` produces a tree that fails at startup, and an +installer that reaches the npm registry but not GitHub releases fails the same way. `--ignore-scripts` is useful for two things and nothing else: @@ -101,8 +104,8 @@ It is not suitable for running the service, for `npm test`, or for producing a d configured here. That pulls two things into the tree: - `node-datachannel`, a native module whose prebuilt binary is downloaded from GitHub releases - during `npm install`. Upstream publishes both glibc and musl builds, but glibc is the one this - project is tested on. + during `npm install`. npm 12 runs that script only because `allowScripts` names the package. + Upstream publishes both glibc and musl builds, but glibc is the one this project is tested on. - `react-native-webrtc`, and through it `react-native` and its Metro bundler, in the development tree only @@ -282,9 +285,9 @@ full boundary, including the administrative key rules, is described in ## Upgrading -An upgrade is a `git` update, a fresh `npm ci`, a rebuild, and a restart. The store directory is not -touched, so the peer identity, the blockstore, the pin set, the lifecycle registry, and the health -checkpoint survive; no store migration is required. +An upgrade is a `git` update, a deleted `node_modules` tree, a fresh `npm ci`, a rebuild, and a +restart. The store directory is not touched, so the peer identity, the blockstore, the pin set, the +lifecycle registry, and the health checkpoint survive; no store migration is required. Read [Upgrades and rollback](/operations/upgrades) for the procedure, the order to follow across a mesh, and how to roll back. diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index 89ea466..7a63eac 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -46,10 +46,12 @@ npm run build node dist/index.js ``` -`npm ci` must be allowed to run install scripts. Helia depends on `@libp2p/webrtc`, whose -`node-datachannel` native module downloads a prebuilt binary during installation. Installing with -`--ignore-scripts` produces a dependency tree that fails at startup; that flag is suitable only for -auditing dependencies and for building the documentation site. +`npm ci` must run install scripts. Helia depends on `@libp2p/webrtc`, whose +`node-datachannel` native module downloads a prebuilt binary during installation. npm 12 blocks +that download unless the package is listed in `package.json` `allowScripts`; this repository +already lists it. Installing with `--ignore-scripts` produces a dependency tree that fails at +startup; that flag is suitable only for auditing dependencies and for building the documentation +site. The build compiles TypeScript into `dist/`. Rerun `npm run build` after every code change; the running process does not pick up source edits on its own. diff --git a/docs/guide/security.md b/docs/guide/security.md index dd0b6e2..41e2f38 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -345,7 +345,9 @@ Two practical notes: to be installed separately. - an install for auditing only may use `--ignore-scripts`, but the resulting tree cannot start the node, because a native dependency of the WebRTC transport package needs its install script. Use a - normal `npm ci` for anything that has to run. + normal `npm ci` for anything that has to run. npm 12 also needs that package named in + `package.json` `allowScripts`; this repository already lists it, and `.npmrc` + `strict-allow-scripts=true` fails the install if a new unreviewed script appears ## Reporting a vulnerability diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index 96cafde..35a05d1 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -101,6 +101,25 @@ Likely causes: The node never answers `404` for a CID it cannot retrieve, because it cannot know whether the content exists elsewhere. A `404` on this API means an unknown registry record or an unrouted path. +## The process exits with `Cannot find module node_datachannel.node` + +Helia depends on `@libp2p/webrtc`, which loads the `node-datachannel` native binary at import time. +The binary is downloaded during `npm ci`, not during `npm run build`. npm 12 skips that download +unless `package.json` `allowScripts` names the package; this repository already lists it. + +Typical causes: + +- `npm ci --ignore-scripts` was used on a tree that has to start +- `node_modules` was copied from a host that never ran the install script +- the installer reached the npm registry but not GitHub releases, where the prebuild lives +- a lockfile change introduced a new install script and `strict-allow-scripts` was not yet in + `.npmrc`, so npm 12 skipped `node-datachannel` silently + +Delete `node_modules`, run a normal `npm ci` that can reach GitHub releases, and confirm +`node_modules/node-datachannel/build/Release/node_datachannel.node` exists before starting. If +`npm ci` lists skipped install scripts, run `npm install-scripts ls` and approve only what this +project needs, as described in [Contributing](/guide/contributing). + ## Replication never places copies - `replication.enabled` is `false`. The node then stores content best effort, one local copy, and diff --git a/docs/operations/upgrades.md b/docs/operations/upgrades.md index 8a39267..c7eef33 100644 --- a/docs/operations/upgrades.md +++ b/docs/operations/upgrades.md @@ -44,14 +44,20 @@ git -C /opt/ipfs-node fetch --tags git -C /opt/ipfs-node checkout cd /opt/ipfs-node nvm use +rm -rf node_modules npm ci npm run build sudo systemctl start ipfs-node ``` -`npm ci` must run install scripts: the WebRTC transport package pulls a native module whose prebuilt -binary is downloaded during installation, and a tree installed with `--ignore-scripts` fails at -startup. +`npm ci` must run install scripts: the WebRTC transport package pulls a native module whose +prebuilt binary is downloaded during installation. npm 12 blocks that download unless the package +is listed in `package.json` `allowScripts`; this repository already lists `node-datachannel`. +`.npmrc` sets `strict-allow-scripts=true`, so a lockfile bump that introduces a new install script +fails the install instead of starting a process that cannot load Helia. A tree installed with +`--ignore-scripts`, or without deleting `node_modules` after a major dependency change, can fail +the same way. Confirm `node_modules/node-datachannel/build/Release/node_datachannel.node` exists +before starting. Review the configuration file against [Configuration](/guide/configuration) before starting. Unknown keys are ignored, so a removed option does not fail the start — it simply stops doing diff --git a/package.json b/package.json index 6b6e104..3850095 100644 --- a/package.json +++ b/package.json @@ -119,5 +119,10 @@ }, "engines": { "node": ">=24.0.0" + }, + "allowScripts": { + "node-datachannel": true, + "esbuild": true, + "fsevents": true } } From 30f9d150a7878198bfbb0a3343de81592188f1a1 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Mon, 21 Sep 2026 09:31:58 +0300 Subject: [PATCH 158/167] feat: enhance replication health monitoring and configuration This update introduces new fields in the replication configuration and health monitoring, including `repairBacklogGraceCycles`, `consecutiveUnsuccessfulCycles`, and `lastCycle` details. The OpenAPI documentation has been updated to reflect these changes, ensuring better tracking of repair cycles and their outcomes. Additionally, the health evaluation logic has been adjusted to incorporate these new parameters, allowing for more nuanced health assessments based on repair cycle performance. Tests have been added to validate the new functionality and ensure robustness. --- config.default.json5 | 5 +- docs/guide/configuration.md | 19 +-- docs/openapi.yaml | 24 +++- docs/operations/monitoring.md | 42 +++++-- src/config.ts | 14 ++- src/health/service.ts | 6 + src/health/state.ts | 36 +++++- src/ipfs-node.ts | 2 +- src/peering/liveness.ts | 61 +++++++--- src/peering/peerSessionRecovery.ts | 44 ++++--- src/peering/peerSessionRecoveryRuntime.ts | 44 +++++-- src/peering/staleSessionErrors.ts | 8 +- src/replication.cron.ts | 54 ++++++++- src/storage/repairEvidence.ts | 24 ++++ src/storage/replicationProtocol.ts | 17 ++- src/storage/service.ts | 93 ++++++++++----- test/healthState.test.ts | 91 ++++++++++++++ test/nodeRoutes.test.ts | 10 +- test/peeringRecovery.test.ts | 72 ++++++++++- test/repairEvidence.test.ts | 20 +++- test/replicationFraming.test.ts | 139 ++++++++++++++++++++++ 21 files changed, 710 insertions(+), 115 deletions(-) create mode 100644 test/replicationFraming.test.ts diff --git a/config.default.json5 b/config.default.json5 index 59f4753..b4ce7bc 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -115,6 +115,9 @@ repairMaxAgeMs: 3600000, clockSkewToleranceMs: 10000, peerAttestationTimeoutMs: 5000, - requiredPeerCount: 1 + requiredPeerCount: 1, + // Consecutive complete unsuccessful repair cycles during which a non-zero + // backlog does not fail repairFresh. 0 preserves current strict behaviour. + repairBacklogGraceCycles: 0 } } diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 8d85165..5be38b3 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -295,15 +295,16 @@ nodes, independent operators, and what each of them keeps running. The `health` section bounds the checkpoint that `GET /api/node/health` reports. The whole section is optional. -| Option | Type | Default | Description | -| --------------------------------- | --------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------- | -| `health.checkpointIntervalMs` | integer >= 1000 | `60000` | Length of one checkpoint round | -| `health.maxCheckpointAgeMs` | integer >= `checkpointIntervalMs` | `checkpointIntervalMs * 3` | Age at which the last completed checkpoint becomes stale | -| `health.storageMaxAgeMs` | integer >= `checkpointIntervalMs` | `checkpointIntervalMs * 2` | Maximum age of the cached storage scan a checkpoint accepts | -| `health.repairMaxAgeMs` | integer >= `checkpointIntervalMs` | `3600000` | Maximum age of a completed full repair sweep | -| `health.clockSkewToleranceMs` | integer >= 0 | `10000` | Allowed clock difference between two attesting peers | -| `health.peerAttestationTimeoutMs` | integer >= 1 | `5000` | Bound for one peer attestation call | -| `health.requiredPeerCount` | integer >= 0 | `1` when more than one node is configured, otherwise `0` | Configured remote peers that must attest a round | +| Option | Type | Default | Description | +| --------------------------------- | --------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `health.checkpointIntervalMs` | integer >= 1000 | `60000` | Length of one checkpoint round | +| `health.maxCheckpointAgeMs` | integer >= `checkpointIntervalMs` | `checkpointIntervalMs * 3` | Age at which the last completed checkpoint becomes stale | +| `health.storageMaxAgeMs` | integer >= `checkpointIntervalMs` | `checkpointIntervalMs * 2` | Maximum age of the cached storage scan a checkpoint accepts | +| `health.repairMaxAgeMs` | integer >= `checkpointIntervalMs` | `3600000` | Maximum age of a completed full repair sweep | +| `health.clockSkewToleranceMs` | integer >= 0 | `10000` | Allowed clock difference between two attesting peers | +| `health.peerAttestationTimeoutMs` | integer >= 1 | `5000` | Bound for one peer attestation call | +| `health.requiredPeerCount` | integer >= 0 | `1` when more than one node is configured, otherwise `0` | Configured remote peers that must attest a round | +| `health.repairBacklogGraceCycles` | integer >= 0 | `0` | Consecutive complete unsuccessful repair cycles with backlog during which `repairFresh` remains true | Three consequences worth planning for: diff --git a/docs/openapi.yaml b/docs/openapi.yaml index e682cff..fa40d4a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -248,12 +248,34 @@ components: reserveHealthy: { type: boolean } replication: type: object - required: [repairRequired, lastCompleteAt, ageMs, backlog] + required: + - repairRequired + - schedule + - lastCompleteAt + - ageMs + - backlog + - consecutiveUnsuccessfulCycles + - lastCycle properties: repairRequired: { type: boolean } + schedule: { type: string, description: Configured replication repair cron expression } lastCompleteAt: { type: [integer, 'null'] } ageMs: { type: [integer, 'null'], minimum: 0 } backlog: { type: integer, minimum: 0 } + consecutiveUnsuccessfulCycles: + type: integer + minimum: 0 + description: Complete repair cycles in a row that ended with backlog or unrecoverable records + lastCycle: + type: [object, 'null'] + description: Summary of the last completed repair cycle + required: [examined, stillMissing, unrecoverable, completedAt, successful] + properties: + examined: { type: integer, minimum: 0 } + stillMissing: { type: integer, minimum: 0 } + unrecoverable: { type: integer, minimum: 0 } + completedAt: { type: [integer, 'null'] } + successful: { type: boolean } checks: type: object description: Checkpoint, storage, and repair freshness are recomputed at read time; every other check describes evaluatedAt diff --git a/docs/operations/monitoring.md b/docs/operations/monitoring.md index b7ab019..83ae356 100644 --- a/docs/operations/monitoring.md +++ b/docs/operations/monitoring.md @@ -64,9 +64,18 @@ so expect up to `health.checkpointIntervalMs` of lag once the underlying fault c }, "replication": { "repairRequired": true, + "schedule": "0 */30 * * * *", "lastCompleteAt": 1720614900000, "ageMs": 98797, - "backlog": 0 + "backlog": 0, + "consecutiveUnsuccessfulCycles": 0, + "lastCycle": { + "examined": 1400, + "stillMissing": 0, + "unrecoverable": 0, + "completedAt": 1720614900000, + "successful": true + } }, "checks": { "checkpointFresh": true, @@ -90,6 +99,10 @@ Field by field: - `checkpoint.observedAt` dates the last attempt that succeeded, so it differs from `evaluatedAt` whenever the most recent attempt failed - `membership.version` is a 64-character digest of the configured peer set +- `replication.consecutiveUnsuccessfulCycles` counts complete repair sweeps in a row that ended with + backlog or unrecoverable records; it resets to `0` on any complete clean cycle +- `replication.schedule` exposes the configured repair cron expression so monitors know the cycle interval +- `replication.lastCycle` summarizes candidate counts and outcome of the last completed repair sweep The `checks` object splits into two kinds. `checkpointFresh`, `storageFresh`, and `repairFresh` are recomputed at read time, because elapsed time alone can invalidate them. Every other check, along @@ -97,7 +110,11 @@ with `membership` and `startup`, describes `evaluatedAt`. `repairFresh` is automatically true when repair is not required — that is, when `replication.enabled` or `replication.repairEnabled` is false. When repair is required, the check -demands a complete, healthy cycle with no backlog, completed within `health.repairMaxAgeMs`. +demands a completed cycle within `health.repairMaxAgeMs` and zero backlog, unless +`health.repairBacklogGraceCycles` allows consecutive complete unsuccessful cycles with backlog. A cycle +that never completes still fails `repairFresh` through `health.repairMaxAgeMs`. Immediate prerequisites +(Helia, disk reserve, clock consistency, startup, and peer attestations) remain immediate and are +never delayed by grace cycles. A clock that moves behind the checkpoint this node already recorded clears `clockConsistent` and stops advancement until it catches up, rather than persisting a round that starts after it finished. @@ -106,15 +123,18 @@ stops advancement until it catches up, rather than persisting a round that start Express thresholds in terms of the configured values, not in absolute seconds. -- `state` is not `ready` for longer than a small multiple of `health.checkpointIntervalMs` -- `checkpoint.ageMs` exceeds `checkpoint.maxAgeMs`, which is the definition of `stale` -- `checks.storageReserve` is false: free space has fallen into `storage.diskReserveBytes`, and - uploads answer `507` -- `checks.clockConsistent` is false: the host clock moved backwards -- `replication.backlog` is non-zero across more than one repair cycle -- `replication.ageMs` approaches `health.repairMaxAgeMs`, which means a cycle is not completing - inside its window -- `membership.attestedPeers` below `membership.requiredPeers` +Split monitoring into immediate prerequisites and repair durability: + +- **Immediate alerts**: + - `checks.helia` is false: the libp2p/Helia subsystem is down + - `checks.storageReserve` is false: free space has fallen into `storage.diskReserveBytes`, and uploads answer `507` + - `checks.clockConsistent` is false: the host clock moved backwards + - `checks.startupReconciliation` is false: startup validation failed + - `checks.peerAttestations` is false: `membership.attestedPeers` is below `membership.requiredPeers` + - `checkpoint.ageMs` exceeds `checkpoint.maxAgeMs`, which is the definition of `stale` +- **Repair durability alerts**: + - `replication.consecutiveUnsuccessfulCycles >= health.repairBacklogGraceCycles` (or `checks.repairFresh` is false once grace is configured) + - `replication.ageMs` approaches `health.repairMaxAgeMs`, which means a cycle is not completing inside its window - `membership.version` changed without a configuration change being deployed - `height` frozen while `state` is `ready` on other nodes of the same membership version diff --git a/src/config.ts b/src/config.ts index 4919355..411112a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -72,6 +72,11 @@ export interface HealthConfig { peerAttestationTimeoutMs: number /** Minimum number of configured remote peers that must attest a round. */ requiredPeerCount: number + /** + * Number of consecutive complete unsuccessful repair cycles during which a + * non-zero backlog does not fail repairFresh. + */ + repairBacklogGraceCycles: number } export interface Config { @@ -256,6 +261,12 @@ function resolveHealthConfig(value: unknown, nodeCount: number): HealthConfig { nodeCount > 1 ? 1 : 0, 0 ) + const repairBacklogGraceCycles = optionalInteger( + raw.repairBacklogGraceCycles, + 'health.repairBacklogGraceCycles', + 0, + 0 + ) if (requiredPeerCount > Math.max(0, nodeCount - 1)) { fail('health.requiredPeerCount', 'cannot exceed the number of configured remote peers') @@ -268,7 +279,8 @@ function resolveHealthConfig(value: unknown, nodeCount: number): HealthConfig { repairMaxAgeMs, clockSkewToleranceMs, peerAttestationTimeoutMs, - requiredPeerCount + requiredPeerCount, + repairBacklogGraceCycles } } diff --git a/src/health/service.ts b/src/health/service.ts index 2617c19..605a405 100644 --- a/src/health/service.ts +++ b/src/health/service.ts @@ -34,9 +34,12 @@ let snapshot: HealthSnapshot = evaluateHealth(config.health, { storageAvailableBytes: 0, storageReservedBytes: config.storage.diskReserveBytes, repairRequired: config.replication.enabled && config.replication.repairEnabled, + repairSchedule: config.replication.repairSchedule, repairCompletedAt: null, repairHealthy: !config.replication.enabled || !config.replication.repairEnabled, repairBacklog: 0, + repairConsecutiveUnsuccessfulCycles: 0, + repairLastCycle: null, attestedPeers: 0, membershipVersion: networkMembershipVersion, previous: null @@ -90,9 +93,12 @@ export function runHealthCheckpoint(): Promise { storageAvailableBytes: storage.availableBytes, storageReservedBytes: storage.reservedBytes, repairRequired: repair.required, + repairSchedule: repair.schedule, repairCompletedAt: repair.completedAt, repairHealthy: repair.healthy, repairBacklog: repair.backlog, + repairConsecutiveUnsuccessfulCycles: repair.consecutiveUnsuccessfulCycles, + repairLastCycle: repair.lastCycle, attestedPeers, membershipVersion: networkMembershipVersion, previous diff --git a/src/health/state.ts b/src/health/state.ts index eaa9574..0770846 100644 --- a/src/health/state.ts +++ b/src/health/state.ts @@ -10,6 +10,14 @@ export interface HealthCheckpoint { attestedPeers: number } +export interface HealthRepairLastCycle { + examined: number + stillMissing: number + unrecoverable: number + completedAt: number | null + successful: boolean +} + export interface HealthInputs { now: number heliaReady: boolean @@ -19,9 +27,12 @@ export interface HealthInputs { storageAvailableBytes: number storageReservedBytes: number repairRequired: boolean + repairSchedule: string repairCompletedAt: number | null repairHealthy: boolean repairBacklog: number + repairConsecutiveUnsuccessfulCycles: number + repairLastCycle: HealthRepairLastCycle | null attestedPeers: number membershipVersion: string previous: HealthCheckpoint | null @@ -64,9 +75,12 @@ export interface HealthSnapshot { } replication: { repairRequired: boolean + schedule: string lastCompleteAt: number | null ageMs: number | null backlog: number + consecutiveUnsuccessfulCycles: number + lastCycle: HealthRepairLastCycle | null } checks: { checkpointFresh: boolean @@ -136,10 +150,12 @@ export function evaluateHealth( repairFresh: !input.repairRequired || (clockConsistent && - input.repairHealthy && - input.repairBacklog === 0 && input.repairCompletedAt !== null && - input.now - input.repairCompletedAt <= policy.repairMaxAgeMs), + input.now - input.repairCompletedAt <= policy.repairMaxAgeMs && + (input.repairBacklog === 0 + ? input.repairHealthy + : (policy.repairBacklogGraceCycles ?? 0) > 0 && + input.repairConsecutiveUnsuccessfulCycles <= (policy.repairBacklogGraceCycles ?? 0))), peerAttestations: input.attestedPeers >= policy.requiredPeerCount } const complete = Object.values(prerequisiteChecks).every(Boolean) @@ -198,9 +214,12 @@ export function evaluateHealth( }, replication: { repairRequired: input.repairRequired, + schedule: input.repairSchedule, lastCompleteAt: input.repairCompletedAt, ageMs: age(input.now, input.repairCompletedAt), - backlog: input.repairBacklog + backlog: input.repairBacklog, + consecutiveUnsuccessfulCycles: input.repairConsecutiveUnsuccessfulCycles, + lastCycle: input.repairLastCycle }, checks }, @@ -212,7 +231,9 @@ export function evaluateHealth( export function refreshHealthSnapshot( current: HealthSnapshot, timestamp: number, - policy: Pick + policy: Pick & { + repairBacklogGraceCycles?: number + } ): HealthSnapshot { const checkpointAge = age(timestamp, current.checkpoint.observedAt) const storageAge = age(timestamp, current.storage.measuredAt) @@ -241,7 +262,10 @@ export function refreshHealthSnapshot( !current.replication.repairRequired || (current.checks.repairFresh && clockConsistent && - current.replication.backlog === 0 && + (current.replication.backlog === 0 || + ((policy.repairBacklogGraceCycles ?? 0) > 0 && + current.replication.consecutiveUnsuccessfulCycles <= + (policy.repairBacklogGraceCycles ?? 0))) && repairAge !== null && repairAge <= policy.repairMaxAgeMs) } diff --git a/src/ipfs-node.ts b/src/ipfs-node.ts index 6b99194..5702f78 100644 --- a/src/ipfs-node.ts +++ b/src/ipfs-node.ts @@ -84,7 +84,7 @@ export async function createIpfsNode(options: IpfsNodeOptions): Promise>() + +/** Clear in-flight ping cache between unit tests. */ +export function resetInFlightPingsForTests(): void { + inFlightPings.clear() +} + /** * Return whether `peerId` answers a libp2p ping within the timeout. * - * Ping is a cheap liveness signal, not proof that bitswap or replication on - * the same session still work. A TCP session can stay up while those - * application protocols are broken; a session that still answers ping is left - * in place. + * Concurrent ping calls to the same peer share one in-flight probe to prevent + * stream collisions. Ping remains a cheap scheduled liveness probe, not proof + * that bitswap or replication on the same session still work. */ export async function pingPeer( node: IpfsNode, peerId: PeerId, timeoutMs: number = PEER_PING_TIMEOUT_MS ): Promise { - try { - await node.libp2p.services.ping.ping(peerId, { - signal: AbortSignal.timeout(timeoutMs) - }) - return true - } catch { - return false + const key = peerId.toString() + const existing = inFlightPings.get(key) + if (existing !== undefined) { + return existing } + + const promise = (async () => { + try { + await node.libp2p.services.ping.ping(peerId, { + signal: AbortSignal.timeout(timeoutMs) + }) + return true + } catch { + return false + } finally { + inFlightPings.delete(key) + } + })() + + inFlightPings.set(key, promise) + return promise } /** - * Tear down every open libp2p connection to `peerId` so the next peering pass - * dials a fresh session. + * Tear down every open libp2p connection to `peerId` and wait until libp2p no + * longer reports the peer as connected, so the subsequent dial establishes a + * genuinely fresh session. */ -export async function resetPeerConnection(node: IpfsNode, peerId: PeerId): Promise { +export async function resetPeerConnection( + node: IpfsNode, + peerId: PeerId, + timeoutMs: number = 5000 +): Promise { await node.libp2p.hangUp(peerId).catch(() => {}) + + const start = Date.now() + while (Date.now() - start < timeoutMs) { + const peers = node.libp2p.getPeers?.() + if (!peers || !peers.some((p) => p.equals?.(peerId) || p.toString() === peerId.toString())) { + break + } + await new Promise((resolve) => setTimeout(resolve, 50)) + } } diff --git a/src/peering/peerSessionRecovery.ts b/src/peering/peerSessionRecovery.ts index 7e4cefd..ead414b 100644 --- a/src/peering/peerSessionRecovery.ts +++ b/src/peering/peerSessionRecovery.ts @@ -2,58 +2,72 @@ export const RECOVERY_COOLDOWN_MS = 30_000 const lastRecoveryAt = new Map() +const inFlightRecoveries = new Map>() export type PeerSessionRecoveryActions = { isConfiguredPeer: (peerId: string) => boolean - ping: (peerId: string) => Promise + ping?: (peerId: string) => Promise reset: (peerId: string) => Promise redial: (peerId: string) => Promise now: () => number } /** - * Drop connections to a configured peer after a failed ping and redial that peer. + * Drop connections to a configured peer after an application-level transfer failure + * and redial that peer. + * + * Ping remains a diagnostic probe and does NOT veto session reset after an application failure. * * @param peerId Configured peer identifier string - * @param reason Operator-facing explanation. Unused here; kept on the core - * signature so the runtime wrapper can log the same argument + * @param reason Operator-facing explanation * @param actions Recovery hooks (required in unit tests to avoid starting Helia) + * @returns Whether the session was reset and redialled */ -export function recoverPeerSession( +export async function recoverPeerSession( peerId: string, reason: string, actions: PeerSessionRecoveryActions -): void { +): Promise { if (!actions.isConfiguredPeer(peerId)) { - return + return false + } + + const inFlight = inFlightRecoveries.get(peerId) + if (inFlight !== undefined) { + return inFlight } const now = actions.now() const last = lastRecoveryAt.get(peerId) if (last !== undefined && now - last < RECOVERY_COOLDOWN_MS) { - return + return false } lastRecoveryAt.set(peerId, now) - void (async () => { + const recoveryPromise = (async () => { try { - const alive = await actions.ping(peerId) - - if (alive) { - return + if (actions.ping) { + await actions.ping(peerId).catch(() => false) } await actions.reset(peerId) await actions.redial(peerId) + return true } catch { - // Caller/runtime logs failures when a real node is available. + return false + } finally { + inFlightRecoveries.delete(peerId) } })() + + inFlightRecoveries.set(peerId, recoveryPromise) + return recoveryPromise } -/** Clear cooldown state between unit tests. */ +/** Clear cooldown and in-flight state between unit tests. */ export function resetPeerSessionRecoveryStateForTests(): void { lastRecoveryAt.clear() + inFlightRecoveries.clear() } diff --git a/src/peering/peerSessionRecoveryRuntime.ts b/src/peering/peerSessionRecoveryRuntime.ts index d5a9f31..b790aa2 100644 --- a/src/peering/peerSessionRecoveryRuntime.ts +++ b/src/peering/peerSessionRecoveryRuntime.ts @@ -8,6 +8,7 @@ import { recoverPeerSession as recoverPeerSessionCore, type PeerSessionRecoveryActions } from './peerSessionRecovery.js' +import { REPLICATION_PROTOCOL } from '../storage/replicationProtocol.js' function defaultRecoveryActions(): PeerSessionRecoveryActions { const selfPeerId = helia.libp2p.peerId.toString() @@ -24,30 +25,47 @@ function defaultRecoveryActions(): PeerSessionRecoveryActions { /** * Reset a configured peer session when reactive recovery is warranted. + * + * Ping is diagnostic only and never vetoes resetting an application session + * whose replication or transfer stream failed. */ -export function recoverPeerSession(peerId: string, reason: string): void { +export async function recoverPeerSession( + peerId: string, + reason: string, + protocol: string = REPLICATION_PROTOCOL +): Promise { const actions = defaultRecoveryActions() - recoverPeerSessionCore(peerId, reason, { + let pingAnswered = false + const recovered = await recoverPeerSessionCore(peerId, reason, { ...actions, ping: async (peerId) => { - const alive = await actions.ping(peerId) - - if (alive) { - logger.info( - { event: 'peer_session_recovery_skipped', peerId, reason }, - 'Peer answered a liveness ping; keeping the existing libp2p session' - ) - } - - return alive + const alive = await actions.ping?.(peerId) + pingAnswered = Boolean(alive) + return pingAnswered }, reset: async (peerId) => { logger.warn( - { event: 'peer_session_recovery', peerId, reason }, + { + event: 'peer_session_recovery', + peerId, + protocol, + reason, + pingAnswered, + vetoUsed: false + }, 'Resetting libp2p session to a configured peer after a transfer failure' ) await actions.reset(peerId) } }) + + if (recovered) { + logger.info( + { event: 'peer_session_recovered', peerId, protocol }, + 'Successfully recovered libp2p session to a configured peer' + ) + } + + return recovered } diff --git a/src/peering/staleSessionErrors.ts b/src/peering/staleSessionErrors.ts index 7e371b2..6a2be02 100644 --- a/src/peering/staleSessionErrors.ts +++ b/src/peering/staleSessionErrors.ts @@ -12,6 +12,12 @@ export function isStalePeerSessionError(message: string): boolean { message.includes('Replication stream ended before') || message.includes('Replication request timed out') || message.includes('Replication message timed out') || - message.includes('The connection is closed') + message.includes('The connection is closed') || + message.includes('The stream has been reset') || + message.includes('stream has been reset') || + message.includes('stream reset') || + message.includes('connection reset') || + message.includes('ECONNRESET') || + message.includes('EPIPE') ) } diff --git a/src/replication.cron.ts b/src/replication.cron.ts index e00ef6d..4793e7d 100644 --- a/src/replication.cron.ts +++ b/src/replication.cron.ts @@ -36,7 +36,11 @@ function emptyEvidence(now = Date.now()): RepairCycleEvidence { unrecoverable: 0, lastCompletedAt: config.replication.enabled && config.replication.repairEnabled ? null : now, lastCompletedSuccessfully: !config.replication.enabled || !config.replication.repairEnabled, - lastCompletedBacklog: 0 + lastCompletedBacklog: 0, + consecutiveUnsuccessfulCycles: 0, + lastCompletedExamined: 0, + lastCompletedStillMissing: 0, + lastCompletedUnrecoverable: 0 } } @@ -110,6 +114,12 @@ export async function repairUnderReplicatedFiles(): Promise { // schedule rather than advancing on a coverage gap. const superseded = current.superseded || lastReport.uncovered > 0 || !lastReport.coverageProven const publishes = lastReport.cycleCompleted && !superseded + const completedSuccessfully = aggregate.stillMissing === 0 && aggregate.unrecoverable === 0 + const nextConsecutiveUnsuccessfulCycles = publishes + ? completedSuccessfully + ? 0 + : current.consecutiveUnsuccessfulCycles + 1 + : current.consecutiveUnsuccessfulCycles await saveEvidence( lastReport.cycleCompleted @@ -117,11 +127,19 @@ export async function repairUnderReplicatedFiles(): Promise { ...emptyEvidence(), lastCompletedAt: publishes ? Date.now() : current.lastCompletedAt, lastCompletedSuccessfully: publishes - ? aggregate.stillMissing === 0 && aggregate.unrecoverable === 0 + ? completedSuccessfully : current.lastCompletedSuccessfully, lastCompletedBacklog: publishes ? aggregate.stillMissing + aggregate.unrecoverable - : current.lastCompletedBacklog + : current.lastCompletedBacklog, + consecutiveUnsuccessfulCycles: nextConsecutiveUnsuccessfulCycles, + lastCompletedExamined: publishes ? aggregate.examined : current.lastCompletedExamined, + lastCompletedStillMissing: publishes + ? aggregate.stillMissing + : current.lastCompletedStillMissing, + lastCompletedUnrecoverable: publishes + ? aggregate.unrecoverable + : current.lastCompletedUnrecoverable } : { ...current, @@ -241,7 +259,8 @@ export function getReplicationState() { unrecoverable: evidence.unrecoverable, lastCompletedAt: evidence.lastCompletedAt, lastCompletedSuccessfully: evidence.lastCompletedSuccessfully, - lastCompletedBacklog: evidence.lastCompletedBacklog + lastCompletedBacklog: evidence.lastCompletedBacklog, + consecutiveUnsuccessfulCycles: evidence.consecutiveUnsuccessfulCycles } : null, lastRun: lastReport @@ -264,19 +283,44 @@ export async function initializeReplicationRepairState(): Promise { await loadEvidence() } +export interface RepairLastCycleEvidence { + examined: number + stillMissing: number + unrecoverable: number + completedAt: number | null + successful: boolean +} + /** Last successful complete repair cycle used by the health checkpoint. */ export function getRepairHealthEvidence(): { required: boolean + schedule: string completedAt: number | null healthy: boolean backlog: number + consecutiveUnsuccessfulCycles: number + lastCycle: RepairLastCycleEvidence | null } { const required = config.replication.enabled && config.replication.repairEnabled const activeBacklog = (evidence?.stillMissing ?? 0) + (evidence?.unrecoverable ?? 0) + const lastCycle: RepairLastCycleEvidence | null = + evidence?.lastCompletedAt !== null && evidence?.lastCompletedAt !== undefined + ? { + examined: evidence.lastCompletedExamined, + stillMissing: evidence.lastCompletedStillMissing, + unrecoverable: evidence.lastCompletedUnrecoverable, + completedAt: evidence.lastCompletedAt, + successful: evidence.lastCompletedSuccessfully + } + : null + return { required, + schedule: config.replication.repairSchedule, completedAt: evidence?.lastCompletedAt ?? (required ? null : Date.now()), healthy: !required || evidence?.lastCompletedSuccessfully === true, - backlog: required ? Math.max(evidence?.lastCompletedBacklog ?? 0, activeBacklog) : 0 + backlog: required ? Math.max(evidence?.lastCompletedBacklog ?? 0, activeBacklog) : 0, + consecutiveUnsuccessfulCycles: required ? (evidence?.consecutiveUnsuccessfulCycles ?? 0) : 0, + lastCycle } } diff --git a/src/storage/repairEvidence.ts b/src/storage/repairEvidence.ts index e53943d..d42bad4 100644 --- a/src/storage/repairEvidence.ts +++ b/src/storage/repairEvidence.ts @@ -21,6 +21,11 @@ export interface RepairCycleEvidence { lastCompletedAt: number | null lastCompletedSuccessfully: boolean lastCompletedBacklog: number + /** Consecutive complete unsuccessful repair cycles. */ + consecutiveUnsuccessfulCycles: number + lastCompletedExamined: number + lastCompletedStillMissing: number + lastCompletedUnrecoverable: number } function isCount(value: unknown): value is number { @@ -66,6 +71,12 @@ export function parseEvidence(value: unknown, now: number): RepairCycleEvidence (isCount(parsed.lastCompletedAt) && parsed.lastCompletedAt <= now)) && typeof parsed.lastCompletedSuccessfully === 'boolean' && isCount(parsed.lastCompletedBacklog) && + (parsed.consecutiveUnsuccessfulCycles === undefined || + isCount(parsed.consecutiveUnsuccessfulCycles)) && + (parsed.lastCompletedExamined === undefined || isCount(parsed.lastCompletedExamined)) && + (parsed.lastCompletedStillMissing === undefined || isCount(parsed.lastCompletedStillMissing)) && + (parsed.lastCompletedUnrecoverable === undefined || + isCount(parsed.lastCompletedUnrecoverable)) && // A cycle cannot have checked more records than it visited, and cannot // report a clean completion while carrying a backlog. parsed.checked <= (parsed.examined ?? parsed.checked) && @@ -75,8 +86,21 @@ export function parseEvidence(value: unknown, now: number): RepairCycleEvidence return null } + const consecutiveUnsuccessfulCycles = + parsed.consecutiveUnsuccessfulCycles !== undefined + ? parsed.consecutiveUnsuccessfulCycles + : parsed.lastCompletedSuccessfully + ? 0 + : (parsed.lastCompletedBacklog ?? 0) > 0 + ? 1 + : 0 + return { ...parsed, + consecutiveUnsuccessfulCycles, + lastCompletedExamined: parsed.lastCompletedExamined ?? 0, + lastCompletedStillMissing: parsed.lastCompletedStillMissing ?? parsed.lastCompletedBacklog ?? 0, + lastCompletedUnrecoverable: parsed.lastCompletedUnrecoverable ?? 0, examined: parsed.examined ?? parsed.checked, superseded: parsed.superseded ?? false } as RepairCycleEvidence diff --git a/src/storage/replicationProtocol.ts b/src/storage/replicationProtocol.ts index bb36a13..6451386 100644 --- a/src/storage/replicationProtocol.ts +++ b/src/storage/replicationProtocol.ts @@ -172,9 +172,13 @@ function encodeMessage(message: unknown): Uint8Array { return frame } +function isSignalAborted(signal?: AbortSignal): boolean { + return signal?.aborted === true +} + /** Read exactly one framed message, without waiting for the stream to end. */ -async function readMessage(stream: Stream, signal?: AbortSignal): Promise { - if (signal?.aborted === true) { +export async function readMessage(stream: Stream, signal?: AbortSignal): Promise { + if (isSignalAborted(signal)) { throw new Error('Replication message timed out') } @@ -212,7 +216,16 @@ async function readMessage(stream: Stream, signal?: AbortSignal): Promise { const message = err instanceof Error ? err.message : String(err) if (isStalePeerSessionError(message)) { - recoverPeerSession(peer.peerId, message) + return recoverPeerSession(peer.peerId, message) } + + return false } /** @@ -344,7 +349,7 @@ async function placeCopy( throw err } - recoverOutboundReplicationSession(peer, err) + void recoverOutboundReplicationSession(peer, err) // A lost ack after a pin must still be aborted. A structured refusal // (busy, not staged, already aborted) never took ownership. @@ -360,7 +365,7 @@ async function placeCopy( return { outcome: 'stored', staged: false } } catch (err) { if (!isNotAuthorizedError(err)) { - recoverOutboundReplicationSession(peer, err) + void recoverOutboundReplicationSession(peer, err) throw err } @@ -400,6 +405,35 @@ function isMaybeStagedError(err: unknown): boolean { ) } +async function performRepairCopy( + peer: ReplicationPeer, + cid: string, + commitTransactionId?: string +): Promise { + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return { outcome: 'stored', staged: false } + } + + if (commitTransactionId !== undefined) { + try { + await requestCommit(helia, peer.multiAddr, cid, commitTransactionId, callOptions()) + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return { outcome: 'stored', staged: false } + } + } catch (err) { + await recoverOutboundReplicationSession(peer, err) + // No matching stage, or the peer is still temporary; `store` is next. + } + } + + if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { + throw new Error('peer has no room for another copy') + } + + await requestStore(helia, peer.multiAddr, cid, callOptions()) + return { outcome: 'stored', staged: false } +} + /** * Restore a missing durable copy without creating a new upload transaction. * @@ -407,6 +441,9 @@ function isMaybeStagedError(err: unknown): boolean { * a peer that kept the prepared pin can promote it instead of waiting for TTL * and a later `store`. `store` remains the fallback for an empty peer, and it * still refuses a live stage it does not own. + * + * If replication to a configured peer fails due to a stale stream session, + * reactive recovery resets and redials the peer before attempting replication once more. */ async function repairCopy( peer: ReplicationPeer, @@ -414,36 +451,35 @@ async function repairCopy( commitTransactionId?: string ): Promise { try { - if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { - return { outcome: 'stored', staged: false } + return await performRepairCopy(peer, cid, commitTransactionId) + } catch (err) { + if (isNotAuthorizedError(err)) { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return 'cached' } - if (commitTransactionId !== undefined) { + const recovered = await recoverOutboundReplicationSession(peer, err) + if (recovered) { try { - await requestCommit(helia, peer.multiAddr, cid, commitTransactionId, callOptions()) - if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { - return { outcome: 'stored', staged: false } + logger.info( + { + event: 'replication_retry_after_session_recovery', + peer: peer.name, + peerId: peer.peerId + }, + `Retrying repair replication to ${peer.name} after session recovery` + ) + return await performRepairCopy(peer, cid, commitTransactionId) + } catch (retryErr) { + if (isNotAuthorizedError(retryErr)) { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return 'cached' } - } catch (err) { - recoverOutboundReplicationSession(peer, err) - // No matching stage, or the peer is still temporary; `store` is next. + throw retryErr } } - if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { - throw new Error('peer has no room for another copy') - } - - await requestStore(helia, peer.multiAddr, cid, callOptions()) - return { outcome: 'stored', staged: false } - } catch (err) { - if (!isNotAuthorizedError(err)) { - recoverOutboundReplicationSession(peer, err) - throw err - } - - await requestCache(helia, peer.multiAddr, cid, callOptions()) - return 'cached' + throw err } } @@ -849,7 +885,8 @@ async function liveHolderNames(holders: ReplicationPeer[], cid: string): Promise holders.map(async (peer) => { try { return (await probeHave(helia, peer.multiAddr, cid, callOptions())) ? peer.name : undefined - } catch { + } catch (err) { + void recoverOutboundReplicationSession(peer, err) return undefined } }) diff --git a/test/healthState.test.ts b/test/healthState.test.ts index 10b08bb..40d5195 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -9,6 +9,7 @@ const policy: HealthConfig = { maxCheckpointAgeMs: 3_000, storageMaxAgeMs: 2_000, repairMaxAgeMs: 5_000, + repairBacklogGraceCycles: 0, clockSkewToleranceMs: 100, peerAttestationTimeoutMs: 100, requiredPeerCount: 1 @@ -23,9 +24,12 @@ const healthy = (now: number) => ({ storageAvailableBytes: 2_000, storageReservedBytes: 1_000, repairRequired: true, + repairSchedule: '0 */30 * * * *', repairCompletedAt: now, repairHealthy: true, repairBacklog: 0, + repairConsecutiveUnsuccessfulCycles: 0, + repairLastCycle: null, attestedPeers: 1, membershipVersion: 'a'.repeat(64), previous: null @@ -101,6 +105,93 @@ describe('health checkpoint state', () => { assert.equal(result.completed, undefined) }) + it('tolerates repair backlog within repairBacklogGraceCycles', () => { + const gracePolicy: HealthConfig = { + ...policy, + repairBacklogGraceCycles: 2 + } + const result = evaluateHealth(gracePolicy, { + ...healthy(12_345), + repairBacklog: 3, + repairConsecutiveUnsuccessfulCycles: 2 + }) + + assert.equal(result.snapshot.state, 'ready') + assert.equal(result.snapshot.checks.repairFresh, true) + assert.equal(result.snapshot.replication.consecutiveUnsuccessfulCycles, 2) + assert.equal(result.snapshot.replication.backlog, 3) + assert.notEqual(result.completed, undefined) + }) + + it('marks state degraded when consecutive unsuccessful cycles exceed repairBacklogGraceCycles', () => { + const gracePolicy: HealthConfig = { + ...policy, + repairBacklogGraceCycles: 2 + } + const result = evaluateHealth(gracePolicy, { + ...healthy(12_345), + repairBacklog: 3, + repairConsecutiveUnsuccessfulCycles: 3 + }) + + assert.equal(result.snapshot.state, 'degraded') + assert.equal(result.snapshot.checks.repairFresh, false) + assert.equal(result.snapshot.replication.consecutiveUnsuccessfulCycles, 3) + assert.equal(result.completed, undefined) + }) + + it('immediately fails state when non-repair checks fail regardless of repair grace', () => { + const gracePolicy: HealthConfig = { + ...policy, + repairBacklogGraceCycles: 5 + } + const result = evaluateHealth(gracePolicy, { + ...healthy(12_345), + repairBacklog: 3, + repairConsecutiveUnsuccessfulCycles: 1, + storageAvailableBytes: 500 + }) + + assert.equal(result.snapshot.state, 'degraded') + assert.equal(result.snapshot.checks.storageReserve, false) + assert.equal(result.snapshot.checks.repairFresh, true) + assert.equal(result.completed, undefined) + }) + + it('marks repairFresh false and state degraded when repair cycle is overdue even within grace', () => { + const gracePolicy: HealthConfig = { + ...policy, + repairBacklogGraceCycles: 5 + } + const result = evaluateHealth(gracePolicy, { + ...healthy(12_345), + repairCompletedAt: 12_345 - (policy.repairMaxAgeMs + 100), + repairBacklog: 1, + repairConsecutiveUnsuccessfulCycles: 1 + }) + + assert.equal(result.snapshot.state, 'degraded') + assert.equal(result.snapshot.checks.repairFresh, false) + assert.equal(result.completed, undefined) + }) + + it('marks cached snapshot stale when repair cycle expires even within grace', () => { + const gracePolicy: HealthConfig = { + ...policy, + repairBacklogGraceCycles: 5 + } + const ready = evaluateHealth(gracePolicy, { + ...healthy(12_345), + repairBacklog: 1, + repairConsecutiveUnsuccessfulCycles: 1 + }).snapshot + assert.equal(ready.state, 'ready') + + const expired = refreshHealthSnapshot(ready, 12_345 + policy.repairMaxAgeMs + 100, gracePolicy) + assert.equal(expired.state, 'stale') + assert.equal(expired.checks.repairFresh, false) + }) + it('reports a cached ready checkpoint as stale when its age expires', () => { const ready = evaluateHealth(policy, healthy(12_345)).snapshot const refreshed = refreshHealthSnapshot(ready, 16_000, policy) diff --git a/test/nodeRoutes.test.ts b/test/nodeRoutes.test.ts index d7e1b41..3c97601 100644 --- a/test/nodeRoutes.test.ts +++ b/test/nodeRoutes.test.ts @@ -17,7 +17,15 @@ const health: HealthSnapshot = { membership: { version: 'a'.repeat(64), requiredPeers: 1, attestedPeers: 1 }, startup: { complete: true, healthy: true }, storage: { measuredAt: 12_345, measurementAgeMs: 0, reserveHealthy: true }, - replication: { repairRequired: true, lastCompleteAt: 12_345, ageMs: 0, backlog: 0 }, + replication: { + repairRequired: true, + schedule: '0 */30 * * * *', + lastCompleteAt: 12_345, + ageMs: 0, + backlog: 0, + consecutiveUnsuccessfulCycles: 0, + lastCycle: null + }, checks: { checkpointFresh: true, clockConsistent: true, diff --git a/test/peeringRecovery.test.ts b/test/peeringRecovery.test.ts index ed88edc..9886b10 100644 --- a/test/peeringRecovery.test.ts +++ b/test/peeringRecovery.test.ts @@ -31,6 +31,12 @@ describe('isStalePeerSessionError', () => { true ) assert.equal(isStalePeerSessionError('The connection is closed'), true) + assert.equal(isStalePeerSessionError('The stream has been reset'), true) + assert.equal(isStalePeerSessionError('stream has been reset'), true) + assert.equal(isStalePeerSessionError('stream reset'), true) + assert.equal(isStalePeerSessionError('connection reset'), true) + assert.equal(isStalePeerSessionError('read ECONNRESET'), true) + assert.equal(isStalePeerSessionError('write EPIPE'), true) }) it('ignores unrelated errors', () => { @@ -153,7 +159,7 @@ describe('recoverPeerSession', () => { assert.equal(calls.ping, 2) }) - it('skips reset when ping succeeds', async () => { + it('does not skip reset when ping succeeds', async () => { resetPeerSessionRecoveryStateForTests() const calls = { reset: 0, redial: 0, ping: 0 } @@ -172,9 +178,40 @@ describe('recoverPeerSession', () => { now: Date.now }) - await waitForRecovery(() => calls.ping >= 1) - assert.equal(calls.reset, 0) - assert.equal(calls.redial, 0) + await waitForRecovery(() => calls.redial >= 1) + assert.equal(calls.ping, 1) + assert.equal(calls.reset, 1) + assert.equal(calls.redial, 1) + }) + + it('coalesces concurrent recovery attempts for the same peer', async () => { + resetPeerSessionRecoveryStateForTests() + let pingStarted = 0 + let resetDone = 0 + + const actions = { + isConfiguredPeer: () => true, + ping: async () => { + pingStarted += 1 + await new Promise((resolve) => setTimeout(resolve, 20)) + return false + }, + reset: async () => { + resetDone += 1 + }, + redial: async () => {}, + now: Date.now + } + + const [first, second] = await Promise.all([ + recoverPeerSession('peer-1', 'reason 1', actions), + recoverPeerSession('peer-1', 'reason 2', actions) + ]) + + assert.equal(first, true) + assert.equal(second, true) + assert.equal(pingStarted, 1) + assert.equal(resetDone, 1) }) }) @@ -235,4 +272,31 @@ describe('pingPeer', () => { assert.equal(alive, false) assert.equal(Date.now() - startedAt < 500, true) }) + + it('coalesces concurrent ping requests for the same peer', async () => { + let rawPings = 0 + const node = { + libp2p: { + services: { + ping: { + ping: async () => { + rawPings += 1 + await new Promise((resolve) => setTimeout(resolve, 20)) + return 42 + } + } + } + } + } + + const peerId = { toString: () => 'coalesced-peer' } as never + const [alive1, alive2] = await Promise.all([ + pingPeer(node as never, peerId), + pingPeer(node as never, peerId) + ]) + + assert.equal(alive1, true) + assert.equal(alive2, true) + assert.equal(rawPings, 1) + }) }) diff --git a/test/repairEvidence.test.ts b/test/repairEvidence.test.ts index 8642f2b..0f2d6bd 100644 --- a/test/repairEvidence.test.ts +++ b/test/repairEvidence.test.ts @@ -17,7 +17,11 @@ const valid: RepairCycleEvidence = { unrecoverable: 0, lastCompletedAt: NOW - 30_000, lastCompletedSuccessfully: true, - lastCompletedBacklog: 0 + lastCompletedBacklog: 0, + consecutiveUnsuccessfulCycles: 0, + lastCompletedExamined: 120, + lastCompletedStillMissing: 0, + lastCompletedUnrecoverable: 0 } describe('persisted repair evidence', () => { @@ -48,7 +52,11 @@ describe('persisted repair evidence', () => { 'repaired', 'stillMissing', 'unrecoverable', - 'lastCompletedBacklog' + 'lastCompletedBacklog', + 'consecutiveUnsuccessfulCycles', + 'lastCompletedExamined', + 'lastCompletedStillMissing', + 'lastCompletedUnrecoverable' ] as const) { assert.equal(parseEvidence({ ...valid, [field]: -1 }, NOW), null, field) } @@ -75,10 +83,18 @@ describe('persisted repair evidence', () => { const legacy = { ...valid } delete (legacy as Partial).examined delete (legacy as Partial).superseded + delete (legacy as Partial).consecutiveUnsuccessfulCycles + delete (legacy as Partial).lastCompletedExamined + delete (legacy as Partial).lastCompletedStillMissing + delete (legacy as Partial).lastCompletedUnrecoverable const parsed = parseEvidence(legacy, NOW) assert.equal(parsed?.examined, valid.checked) assert.equal(parsed?.superseded, false) + assert.equal(parsed?.consecutiveUnsuccessfulCycles, 0) + assert.equal(parsed?.lastCompletedExamined, 0) + assert.equal(parsed?.lastCompletedStillMissing, 0) + assert.equal(parsed?.lastCompletedUnrecoverable, 0) }) }) diff --git a/test/replicationFraming.test.ts b/test/replicationFraming.test.ts new file mode 100644 index 0000000..170a106 --- /dev/null +++ b/test/replicationFraming.test.ts @@ -0,0 +1,139 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import type { Stream } from '@libp2p/interface' +import { readMessage } from '../src/storage/replicationProtocol.js' +import { isStalePeerSessionError } from '../src/peering/staleSessionErrors.js' + +function encodeMessage(payload: unknown): Uint8Array { + const body = new TextEncoder().encode(JSON.stringify(payload)) + const frame = new Uint8Array(4 + body.byteLength) + new DataView(frame.buffer).setUint32(0, body.byteLength) + frame.set(body, 4) + return frame +} + +function mockStream(chunks: Uint8Array[], onAbort?: (err: Error) => void): Stream { + return { + async *[Symbol.asyncIterator]() { + for (const chunk of chunks) { + yield chunk + } + }, + abort: (err: Error) => { + onAbort?.(err) + } + } as unknown as Stream +} + +describe('readMessage framing', () => { + it('reads a valid message sent in a single chunk', async () => { + const payload = { op: 'have', cid: 'bafytest' } + const frame = encodeMessage(payload) + const stream = mockStream([frame]) + + const message = await readMessage(stream) + assert.deepEqual(message, payload) + }) + + it('reads a valid message fragmented across multiple chunks', async () => { + const payload = { op: 'commit', cid: 'bafytest', transactionId: 'tx-123' } + const frame = encodeMessage(payload) + + // Fragment into 3 parts: 2 bytes of length, next 2 bytes of length + part of body, rest of body + const chunk1 = frame.subarray(0, 2) + const chunk2 = frame.subarray(2, 10) + const chunk3 = frame.subarray(10) + const stream = mockStream([chunk1, chunk2, chunk3]) + + const message = await readMessage(stream) + assert.deepEqual(message, payload) + }) + + it('throws when stream ends before length prefix is complete', async () => { + const stream = mockStream([new Uint8Array([0, 0, 1])]) // only 3 bytes + + await assert.rejects( + async () => readMessage(stream), + /Replication stream ended before a complete message arrived/ + ) + }) + + it('throws when stream ends before declared payload is complete', async () => { + const frame = new Uint8Array(10) + new DataView(frame.buffer).setUint32(0, 20) // expects 20 bytes payload + frame.set(new TextEncoder().encode('{"op":'), 4) + const stream = mockStream([frame]) + + await assert.rejects( + async () => readMessage(stream), + /Replication stream ended before a complete message arrived/ + ) + }) + + it('throws when length prefix declares a message larger than MAX_MESSAGE_BYTES', async () => { + const frame = new Uint8Array(4) + new DataView(frame.buffer).setUint32(0, 10_000) // 10,000 > 4096 + const stream = mockStream([frame]) + + await assert.rejects(async () => readMessage(stream), /Replication message is too large/) + }) + + it('throws when stream sends excessive bytes without completing a valid message', async () => { + const excessiveChunk = new Uint8Array(5000) // > 4096 + 4 + const stream = mockStream([excessiveChunk]) + + await assert.rejects(async () => readMessage(stream), /Replication message is too large/) + }) + + it('throws timeout error and aborts stream when abort signal fires', async () => { + const controller = new AbortController() + let abortedWithError: Error | null = null + + const stream: Stream = { + async *[Symbol.asyncIterator]() { + // Yield nothing and wait for abort + await new Promise((resolve) => { + controller.signal.addEventListener('abort', resolve) + }) + yield new Uint8Array(0) + }, + abort: (err: Error) => { + abortedWithError = err + } + } as unknown as Stream + + const promise = readMessage(stream, controller.signal) + controller.abort() + + await assert.rejects(async () => promise, /Replication message timed out/) + assert.notEqual(abortedWithError, null) + assert.equal((abortedWithError as unknown as Error).message, 'Replication message timed out') + }) + + it('throws immediately when signal is already aborted', async () => { + const stream = mockStream([encodeMessage({ test: true })]) + const signal = AbortSignal.abort() + + await assert.rejects(async () => readMessage(stream, signal), /Replication message timed out/) + }) + + it('propagates stream reset error which is recognized as stale session error', async () => { + const resetError = new Error('The stream has been reset') + const stream: Stream = { + async *[Symbol.asyncIterator]() { + yield new Uint8Array(0) + throw resetError + }, + abort: () => {} + } as unknown as Stream + + await assert.rejects( + async () => readMessage(stream), + (err: unknown) => { + assert.equal(err, resetError) + assert.equal(isStalePeerSessionError((err as Error).message), true) + return true + } + ) + }) +}) From 44108113a0e6642e4d081e6269ff6bd56ab5d135 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Mon, 21 Sep 2026 10:38:14 +0300 Subject: [PATCH 159/167] fix: update monitoring and troubleshooting documentation for health checks This commit refines the monitoring and troubleshooting documentation, clarifying the conditions for repair durability alerts and enhancing the descriptions of failing health checks. The changes improve the readability and accuracy of the documentation, ensuring users have a better understanding of the health evaluation process and the implications of various health states. Additionally, adjustments were made to the API documentation to reflect the updated logic for height advancement based on repair cycles and peer attestations. --- docs/operations/monitoring.md | 2 +- docs/operations/troubleshooting.md | 18 +++++++++--------- docs/reference/api.md | 4 ++-- src/ipfs-node.ts | 15 ++++++++++++++- src/peering/liveness.ts | 8 +++++++- src/peering/staleSessionErrors.ts | 4 ++-- src/storage/service.ts | 2 ++ test/healthState.test.ts | 5 +++++ 8 files changed, 42 insertions(+), 16 deletions(-) diff --git a/docs/operations/monitoring.md b/docs/operations/monitoring.md index 83ae356..feda6a4 100644 --- a/docs/operations/monitoring.md +++ b/docs/operations/monitoring.md @@ -133,7 +133,7 @@ Split monitoring into immediate prerequisites and repair durability: - `checks.peerAttestations` is false: `membership.attestedPeers` is below `membership.requiredPeers` - `checkpoint.ageMs` exceeds `checkpoint.maxAgeMs`, which is the definition of `stale` - **Repair durability alerts**: - - `replication.consecutiveUnsuccessfulCycles >= health.repairBacklogGraceCycles` (or `checks.repairFresh` is false once grace is configured) + - `checks.repairFresh` is false (with default `repairBacklogGraceCycles: 0`, any non-zero backlog or unhealthy cycle fails freshness immediately; with grace configured, `replication.consecutiveUnsuccessfulCycles > health.repairBacklogGraceCycles`) - `replication.ageMs` approaches `health.repairMaxAgeMs`, which means a cycle is not completing inside its window - `membership.version` changed without a configuration change being deployed - `height` frozen while `state` is `ready` on other nodes of the same membership version diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index 35a05d1..f12a620 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -43,15 +43,15 @@ until it is rewritten; see the migration section of [Configuration](/guide/confi Read `checks` in `GET /api/node/health`; the failing entry names the cause. -| Failing check | Cause | Check | -| ----------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `startupReconciliation` | Registry backfill or admission recovery reported errors | Look for `Registry backfill:` or `Admission recovery:` warnings in the log | -| `storageFresh` | No storage measurement inside `health.storageMaxAgeMs` | Confirm `diskUsageScanPeriod` is more frequent than that window and that the scan is not erroring | -| `storageReserve` | Free space has fallen into `storage.diskReserveBytes` | `GET /api/storage/metrics`; free space or lower the reserve deliberately | -| `repairFresh` | No complete, healthy repair cycle inside `health.repairMaxAgeMs`, or a non-zero backlog | `GET /api/node/health` `replication.backlog` and `replication.ageMs`; raise `health.repairMaxAgeMs` above the largest expected cycle duration plus the schedule interval | -| `peerAttestations` | Fewer peers attested than `health.requiredPeerCount` | Confirm the peers are connected, upgraded, and share the same `membership.version` | -| `clockConsistent` | The host clock moved behind a recorded observation | Check NTP; advancement resumes once the clock catches up | -| `helia` | The Helia node is not started | Read the startup log | +| Failing check | Cause | Check | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `startupReconciliation` | Registry backfill or admission recovery reported errors | Look for `Registry backfill:` or `Admission recovery:` warnings in the log | +| `storageFresh` | No storage measurement inside `health.storageMaxAgeMs` | Confirm `diskUsageScanPeriod` is more frequent than that window and that the scan is not erroring | +| `storageReserve` | Free space has fallen into `storage.diskReserveBytes` | `GET /api/storage/metrics`; free space or lower the reserve deliberately | +| `repairFresh` | No complete, healthy repair cycle inside `health.repairMaxAgeMs`, or a backlog exceeding `health.repairBacklogGraceCycles` | `GET /api/node/health` `replication.backlog` and `replication.ageMs`; raise `health.repairMaxAgeMs` above the largest expected cycle duration plus the schedule interval | +| `peerAttestations` | Fewer peers attested than `health.requiredPeerCount` | Confirm the peers are connected, upgraded, and share the same `membership.version` | +| `clockConsistent` | The host clock moved behind a recorded observation | Check NTP; advancement resumes once the clock catches up | +| `helia` | The Helia node is not started | Read the startup log | **The state is `ready` but `height` stays `0`.** The membership epoch changed. Changing `nodes` resets the persisted checkpoint, so height restarts diff --git a/docs/reference/api.md b/docs/reference/api.md index 97b37ba..e6c5e57 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -197,8 +197,8 @@ This route always returns HTTP `200`. Consumers must read `state`, which is `sta `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It advances only when startup reconciliation, storage freshness and reserve, a complete successful -repair cycle with no known backlog, and the configured peer attestations all pass, and it freezes on -failure. +repair cycle (or a cycle within configured `health.repairBacklogGraceCycles`), and the configured peer +attestations all pass, and it freezes on failure. State changes are asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` diff --git a/src/ipfs-node.ts b/src/ipfs-node.ts index 5702f78..39ec87d 100644 --- a/src/ipfs-node.ts +++ b/src/ipfs-node.ts @@ -45,6 +45,16 @@ export interface IpfsNodeOptions { /** Default connection ceiling, kept from the pre-migration configuration. */ export const DEFAULT_MAX_CONNECTIONS = 100 +/** + * Maximum concurrent inbound and outbound ping protocol streams per peer connection. + * + * Libp2p defaults to 1/1, which exhausts streams when concurrent probes occur. + * Outbound requests are already serialized per peer via coalescing (`pingPeer`), + * so 4 streams per connection provides ample headroom for diagnostics and API calls + * while protecting the node against stream resource exhaustion / DoS from individual peers. + */ +export const MAX_PING_STREAMS_PER_CONNECTION = 4 + /** * Create a Helia node configured for the ADAMANT topology. * @@ -84,7 +94,10 @@ export async function createIpfsNode(options: IpfsNodeOptions): Promise>() /** Clear in-flight ping cache between unit tests. */ @@ -60,7 +66,7 @@ export async function pingPeer( export async function resetPeerConnection( node: IpfsNode, peerId: PeerId, - timeoutMs: number = 5000 + timeoutMs: number = PEER_HANG_UP_TIMEOUT_MS ): Promise { await node.libp2p.hangUp(peerId).catch(() => {}) diff --git a/src/peering/staleSessionErrors.ts b/src/peering/staleSessionErrors.ts index 6a2be02..d05b3e8 100644 --- a/src/peering/staleSessionErrors.ts +++ b/src/peering/staleSessionErrors.ts @@ -3,8 +3,8 @@ * * Call this only from replication error paths. Substring matching is coarse: * `'stream ended before'` also appears in the health protocol, and - * `'The connection is closed'` can match a benign shutdown race. A match only - * schedules a ping, never an immediate hang-up. + * `'The connection is closed'` can match a benign shutdown race. A match + * triggers reactive recovery (immediate hang-up and redial). */ export function isStalePeerSessionError(message: string): boolean { return ( diff --git a/src/storage/service.ts b/src/storage/service.ts index 27a3410..3dca616 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -886,6 +886,8 @@ async function liveHolderNames(holders: ReplicationPeer[], cid: string): Promise try { return (await probeHave(helia, peer.multiAddr, cid, callOptions())) ? peer.name : undefined } catch (err) { + // Recovery runs concurrently in background; 30s cooldown prevents recovery storms, + // and any peer still unrecovered will be evaluated in the next repair sweep. void recoverOutboundReplicationSession(peer, err) return undefined } diff --git a/test/healthState.test.ts b/test/healthState.test.ts index 40d5195..9ba1526 100644 --- a/test/healthState.test.ts +++ b/test/healthState.test.ts @@ -112,6 +112,7 @@ describe('health checkpoint state', () => { } const result = evaluateHealth(gracePolicy, { ...healthy(12_345), + repairHealthy: false, repairBacklog: 3, repairConsecutiveUnsuccessfulCycles: 2 }) @@ -130,6 +131,7 @@ describe('health checkpoint state', () => { } const result = evaluateHealth(gracePolicy, { ...healthy(12_345), + repairHealthy: false, repairBacklog: 3, repairConsecutiveUnsuccessfulCycles: 3 }) @@ -147,6 +149,7 @@ describe('health checkpoint state', () => { } const result = evaluateHealth(gracePolicy, { ...healthy(12_345), + repairHealthy: false, repairBacklog: 3, repairConsecutiveUnsuccessfulCycles: 1, storageAvailableBytes: 500 @@ -165,6 +168,7 @@ describe('health checkpoint state', () => { } const result = evaluateHealth(gracePolicy, { ...healthy(12_345), + repairHealthy: false, repairCompletedAt: 12_345 - (policy.repairMaxAgeMs + 100), repairBacklog: 1, repairConsecutiveUnsuccessfulCycles: 1 @@ -182,6 +186,7 @@ describe('health checkpoint state', () => { } const ready = evaluateHealth(gracePolicy, { ...healthy(12_345), + repairHealthy: false, repairBacklog: 1, repairConsecutiveUnsuccessfulCycles: 1 }).snapshot From 9141255400c9635950fbe7616334dcea2e6a5375 Mon Sep 17 00:00:00 2001 From: massivedev0 Date: Mon, 21 Sep 2026 10:55:03 +0300 Subject: [PATCH 160/167] fix: hang up stale sessions without ping and retry upload placement Reactive recovery waited on ping, which delayed hang-up on sessions that still answered liveness. Upload placement also returned failed without retrying on the new session. Align both with the repair path and correct the health-grace docs. Co-authored-by: Cursor --- docker/config.example.json5 | 5 +- docs/operations/troubleshooting.md | 18 ++--- docs/reference/api.md | 7 +- src/peering/peerSessionRecovery.ts | 13 ++- src/peering/peerSessionRecoveryRuntime.ts | 14 +--- src/storage/service.ts | 99 ++++++++++++++++------- test/peeringRecovery.test.ts | 58 ++++--------- 7 files changed, 108 insertions(+), 106 deletions(-) diff --git a/docker/config.example.json5 b/docker/config.example.json5 index b0b491c..aaf6995 100644 --- a/docker/config.example.json5 +++ b/docker/config.example.json5 @@ -109,6 +109,9 @@ clockSkewToleranceMs: 10000, // Must not exceed the number of configured remote peers, so a single node // has to keep this at 0. Raise it once every required peer is upgraded. - requiredPeerCount: 0 + requiredPeerCount: 0, + // Consecutive complete unsuccessful repair cycles with backlog during which + // repairFresh stays true. 0 is strict: any backlog fails the checkpoint. + repairBacklogGraceCycles: 0 } } diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index f12a620..b9e1c6c 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -43,15 +43,15 @@ until it is rewritten; see the migration section of [Configuration](/guide/confi Read `checks` in `GET /api/node/health`; the failing entry names the cause. -| Failing check | Cause | Check | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `startupReconciliation` | Registry backfill or admission recovery reported errors | Look for `Registry backfill:` or `Admission recovery:` warnings in the log | -| `storageFresh` | No storage measurement inside `health.storageMaxAgeMs` | Confirm `diskUsageScanPeriod` is more frequent than that window and that the scan is not erroring | -| `storageReserve` | Free space has fallen into `storage.diskReserveBytes` | `GET /api/storage/metrics`; free space or lower the reserve deliberately | -| `repairFresh` | No complete, healthy repair cycle inside `health.repairMaxAgeMs`, or a backlog exceeding `health.repairBacklogGraceCycles` | `GET /api/node/health` `replication.backlog` and `replication.ageMs`; raise `health.repairMaxAgeMs` above the largest expected cycle duration plus the schedule interval | -| `peerAttestations` | Fewer peers attested than `health.requiredPeerCount` | Confirm the peers are connected, upgraded, and share the same `membership.version` | -| `clockConsistent` | The host clock moved behind a recorded observation | Check NTP; advancement resumes once the clock catches up | -| `helia` | The Helia node is not started | Read the startup log | +| Failing check | Cause | Check | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `startupReconciliation` | Registry backfill or admission recovery reported errors | Look for `Registry backfill:` or `Admission recovery:` warnings in the log | +| `storageFresh` | No storage measurement inside `health.storageMaxAgeMs` | Confirm `diskUsageScanPeriod` is more frequent than that window and that the scan is not erroring | +| `storageReserve` | Free space has fallen into `storage.diskReserveBytes` | `GET /api/storage/metrics`; free space or lower the reserve deliberately | +| `repairFresh` | No complete cycle inside `health.repairMaxAgeMs`, or too many consecutive unsuccessful cycles (`replication.consecutiveUnsuccessfulCycles` above `health.repairBacklogGraceCycles`; a non-zero file backlog alone is not enough when grace is set) | `GET /api/node/health` `replication.consecutiveUnsuccessfulCycles`, `replication.backlog`, and `replication.ageMs` | +| `peerAttestations` | Fewer peers attested than `health.requiredPeerCount` | Confirm the peers are connected, upgraded, and share the same `membership.version` | +| `clockConsistent` | The host clock moved behind a recorded observation | Check NTP; advancement resumes once the clock catches up | +| `helia` | The Helia node is not started | Read the startup log | **The state is `ready` but `height` stays `0`.** The membership epoch changed. Changing `nodes` resets the persisted checkpoint, so height restarts diff --git a/docs/reference/api.md b/docs/reference/api.md index e6c5e57..8fdde44 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -196,9 +196,10 @@ This route always returns HTTP `200`. Consumers must read `state`, which is `sta `stale`, or `degraded`. `height` is a persisted, monotonic Unix-millisecond checkpoint at the start of a fixed round. It -advances only when startup reconciliation, storage freshness and reserve, a complete successful -repair cycle (or a cycle within configured `health.repairBacklogGraceCycles`), and the configured peer -attestations all pass, and it freezes on failure. +advances only when startup reconciliation, storage freshness and reserve, repair freshness, and the +configured peer attestations all pass, and it freezes on failure. Repair freshness requires a +complete cycle inside `health.repairMaxAgeMs` with no backlog, unless +`health.repairBacklogGraceCycles` still covers that many consecutive unsuccessful complete cycles. State changes are asymmetric. The response is served from the last checkpoint, and reading it recomputes only what elapsed time can decide, so a node may be downgraded to `degraded` or `stale` diff --git a/src/peering/peerSessionRecovery.ts b/src/peering/peerSessionRecovery.ts index ead414b..4c509bf 100644 --- a/src/peering/peerSessionRecovery.ts +++ b/src/peering/peerSessionRecovery.ts @@ -6,17 +6,18 @@ const inFlightRecoveries = new Map>() export type PeerSessionRecoveryActions = { isConfiguredPeer: (peerId: string) => boolean - ping?: (peerId: string) => Promise reset: (peerId: string) => Promise redial: (peerId: string) => Promise now: () => number } /** - * Drop connections to a configured peer after an application-level transfer failure - * and redial that peer. + * Drop connections to a configured peer after an application-level transfer + * failure and redial that peer. * - * Ping remains a diagnostic probe and does NOT veto session reset after an application failure. + * Ping is a scheduled liveness probe on connected peers, not a step on this + * path: a session can still answer ping while replication streams are dead, so + * waiting on ping would delay hang-up and can exhaust `/ipfs/ping/1.0.0`. * * @param peerId Configured peer identifier string * @param reason Operator-facing explanation @@ -48,10 +49,6 @@ export async function recoverPeerSession( const recoveryPromise = (async () => { try { - if (actions.ping) { - await actions.ping(peerId).catch(() => false) - } - await actions.reset(peerId) await actions.redial(peerId) return true diff --git a/src/peering/peerSessionRecoveryRuntime.ts b/src/peering/peerSessionRecoveryRuntime.ts index b790aa2..08f4bd8 100644 --- a/src/peering/peerSessionRecoveryRuntime.ts +++ b/src/peering/peerSessionRecoveryRuntime.ts @@ -2,7 +2,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import { helia } from '../helia.js' import { getNodesList } from '../utils/utils.js' import { logger } from '../utils/logger.js' -import { pingPeer, resetPeerConnection } from './liveness.js' +import { resetPeerConnection } from './liveness.js' import { redialKnownPeer } from './redialKnownPeer.js' import { recoverPeerSession as recoverPeerSessionCore, @@ -16,7 +16,6 @@ function defaultRecoveryActions(): PeerSessionRecoveryActions { return { isConfiguredPeer: (peerId) => getNodesList([selfPeerId]).some((node) => node.peerId.toString() === peerId), - ping: async (peerId) => pingPeer(helia, peerIdFromString(peerId)), reset: async (peerId) => resetPeerConnection(helia, peerIdFromString(peerId)), redial: redialKnownPeer, now: Date.now @@ -26,8 +25,8 @@ function defaultRecoveryActions(): PeerSessionRecoveryActions { /** * Reset a configured peer session when reactive recovery is warranted. * - * Ping is diagnostic only and never vetoes resetting an application session - * whose replication or transfer stream failed. + * Hang-up is immediate: ping stays on the scheduled peering pass and does not + * run here, because a live ping does not mean replication still works. */ export async function recoverPeerSession( peerId: string, @@ -36,14 +35,8 @@ export async function recoverPeerSession( ): Promise { const actions = defaultRecoveryActions() - let pingAnswered = false const recovered = await recoverPeerSessionCore(peerId, reason, { ...actions, - ping: async (peerId) => { - const alive = await actions.ping?.(peerId) - pingAnswered = Boolean(alive) - return pingAnswered - }, reset: async (peerId) => { logger.warn( { @@ -51,7 +44,6 @@ export async function recoverPeerSession( peerId, protocol, reason, - pingAnswered, vetoUsed: false }, 'Resetting libp2p session to a configured peer after a transfer failure' diff --git a/src/storage/service.ts b/src/storage/service.ts index 3dca616..cfa22aa 100644 --- a/src/storage/service.ts +++ b/src/storage/service.ts @@ -128,7 +128,11 @@ async function placeReplicas( try { await requestCache(helia, peer.multiAddr, cid, callOptions()) } catch (err) { - recoverOutboundReplicationSession(peer, err) + const recovered = await recoverOutboundReplicationSession(peer, err) + if (recovered) { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return + } throw err } } @@ -326,51 +330,84 @@ async function recoverOutboundReplicationSession( * offered as an unpinned copy instead: the peer can serve it from then on, and * nothing is counted as durable that is not. */ +async function attemptPlaceCopy( + peer: ReplicationPeer, + cid: string, + transactionId?: string +): Promise { + if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { + return { outcome: 'stored', staged: false } + } + + if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { + throw new Error('peer has no room for another copy') + } + + if (transactionId !== undefined) { + const result = await requestStage(helia, peer.multiAddr, cid, transactionId, callOptions()) + return { outcome: 'stored', staged: result.staged } + } + + await requestStore(helia, peer.multiAddr, cid, callOptions()) + return { outcome: 'stored', staged: false } +} + +function failedStageResult(err: unknown): ReplicationStoreResult { + return { + outcome: 'failed', + staged: isMaybeStagedError(err), + error: (err as Error).message + } +} + +/** + * Place one copy during upload. + * + * A stale session is reset and the placement is tried once more on the fresh + * connection, the same as repair. A stage that still fails after that is + * reported so abort can withdraw a pin the peer may have taken. + */ async function placeCopy( peer: ReplicationPeer, cid: string, transactionId?: string ): Promise { try { - if (await probeHave(helia, peer.multiAddr, cid, callOptions())) { - return { outcome: 'stored', staged: false } - } - - if (!(await probeAccept(helia, peer.multiAddr, cid, callOptions()))) { - throw new Error('peer has no room for another copy') + return await attemptPlaceCopy(peer, cid, transactionId) + } catch (err) { + if (isNotAuthorizedError(err)) { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return 'cached' } - if (transactionId !== undefined) { + const recovered = await recoverOutboundReplicationSession(peer, err) + if (recovered) { try { - const result = await requestStage(helia, peer.multiAddr, cid, transactionId, callOptions()) - return { outcome: 'stored', staged: result.staged } - } catch (err) { - if (isNotAuthorizedError(err)) { - throw err + logger.info( + { + event: 'replication_retry_after_session_recovery', + peer: peer.name, + peerId: peer.peerId + }, + `Retrying upload replication to ${peer.name} after session recovery` + ) + return await attemptPlaceCopy(peer, cid, transactionId) + } catch (retryErr) { + if (isNotAuthorizedError(retryErr)) { + await requestCache(helia, peer.multiAddr, cid, callOptions()) + return 'cached' } - - void recoverOutboundReplicationSession(peer, err) - - // A lost ack after a pin must still be aborted. A structured refusal - // (busy, not staged, already aborted) never took ownership. - return { - outcome: 'failed', - staged: isMaybeStagedError(err), - error: (err as Error).message + if (transactionId !== undefined) { + return failedStageResult(retryErr) } + throw retryErr } } - await requestStore(helia, peer.multiAddr, cid, callOptions()) - return { outcome: 'stored', staged: false } - } catch (err) { - if (!isNotAuthorizedError(err)) { - void recoverOutboundReplicationSession(peer, err) - throw err + if (transactionId !== undefined) { + return failedStageResult(err) } - - await requestCache(helia, peer.multiAddr, cid, callOptions()) - return 'cached' + throw err } } diff --git a/test/peeringRecovery.test.ts b/test/peeringRecovery.test.ts index 9886b10..e88daba 100644 --- a/test/peeringRecovery.test.ts +++ b/test/peeringRecovery.test.ts @@ -108,30 +108,14 @@ describe('dropUnhealthyConnectedPeers', () => { }) }) -async function waitForRecovery(done: () => boolean): Promise { - for (let attempt = 0; attempt < 100; attempt += 1) { - if (done()) { - return - } - - await new Promise((resolve) => setTimeout(resolve, 5)) - } - - assert.fail('peer session recovery did not finish') -} - describe('recoverPeerSession', () => { it('ignores unknown peers and enforces cooldown', async () => { resetPeerSessionRecoveryStateForTests() let now = 1_000 - const calls = { ping: 0, reset: 0, redial: 0 } + const calls = { reset: 0, redial: 0 } const actions = { isConfiguredPeer: (peerId: string) => peerId === 'known-peer', - ping: async () => { - calls.ping += 1 - return false - }, reset: async () => { calls.reset += 1 }, @@ -141,34 +125,27 @@ describe('recoverPeerSession', () => { now: () => now } - recoverPeerSession('unknown-peer', 'test', actions) - recoverPeerSession('known-peer', 'first', actions) - await waitForRecovery(() => calls.redial >= 1) - assert.equal(calls.ping, 1) + await recoverPeerSession('unknown-peer', 'test', actions) + await recoverPeerSession('known-peer', 'first', actions) assert.equal(calls.reset, 1) assert.equal(calls.redial, 1) now += RECOVERY_COOLDOWN_MS - 1 - recoverPeerSession('known-peer', 'cooldown', actions) - await waitForRecovery(() => calls.redial >= 1) - assert.equal(calls.ping, 1) + await recoverPeerSession('known-peer', 'cooldown', actions) + assert.equal(calls.reset, 1) now += 1 - recoverPeerSession('known-peer', 'after cooldown', actions) - await waitForRecovery(() => calls.redial >= 2) - assert.equal(calls.ping, 2) + await recoverPeerSession('known-peer', 'after cooldown', actions) + assert.equal(calls.reset, 2) + assert.equal(calls.redial, 2) }) - it('does not skip reset when ping succeeds', async () => { + it('hangs up without waiting on ping', async () => { resetPeerSessionRecoveryStateForTests() - const calls = { reset: 0, redial: 0, ping: 0 } + const calls = { reset: 0, redial: 0 } - recoverPeerSession('known-peer', 'healthy', { + await recoverPeerSession('known-peer', 'replication stream ended', { isConfiguredPeer: () => true, - ping: async () => { - calls.ping += 1 - return true - }, reset: async () => { calls.reset += 1 }, @@ -178,25 +155,20 @@ describe('recoverPeerSession', () => { now: Date.now }) - await waitForRecovery(() => calls.redial >= 1) - assert.equal(calls.ping, 1) assert.equal(calls.reset, 1) assert.equal(calls.redial, 1) }) it('coalesces concurrent recovery attempts for the same peer', async () => { resetPeerSessionRecoveryStateForTests() - let pingStarted = 0 + let resetStarted = 0 let resetDone = 0 const actions = { isConfiguredPeer: () => true, - ping: async () => { - pingStarted += 1 - await new Promise((resolve) => setTimeout(resolve, 20)) - return false - }, reset: async () => { + resetStarted += 1 + await new Promise((resolve) => setTimeout(resolve, 20)) resetDone += 1 }, redial: async () => {}, @@ -210,7 +182,7 @@ describe('recoverPeerSession', () => { assert.equal(first, true) assert.equal(second, true) - assert.equal(pingStarted, 1) + assert.equal(resetStarted, 1) assert.equal(resetDone, 1) }) }) From a879d4bc2bc94cb09b788e4a182d3f7c4253cf0b Mon Sep 17 00:00:00 2001 From: metalisk Date: Mon, 28 Sep 2026 10:56:24 +0300 Subject: [PATCH 161/167] chore: update CORS configuration and error handling documentation --- README.md | 3 +- config.default.json5 | 18 ++++- docker/config.example.json5 | 30 ++++++-- docs/guide/adamant-messenger.md | 11 ++- docs/guide/configuration.md | 63 ++++++++++++---- docs/guide/docker.md | 20 ++++- docs/guide/installation.md | 24 +++++- docs/guide/quick-start.md | 6 +- docs/guide/security.md | 88 ++++++++++++++++------ docs/openapi.yaml | 33 ++++++-- docs/operations/monitoring.md | 13 +++- docs/operations/troubleshooting.md | 82 +++++++++++++++++--- docs/reference/api.md | 71 ++++++++++++----- scripts/docker-smoke-test.sh | 3 + src/api/uploadRoute.ts | 5 +- src/middleware/downloadAdmission.ts | 7 +- src/middleware/uploadAdmission.ts | 7 +- src/security/config.ts | 2 + src/security/cors.ts | 82 +++++++++++++++++--- src/security/errors.ts | 89 +++++++++++++++++++--- src/security/rateLimit.ts | 3 +- test/downloadAdmission.test.ts | 12 ++- test/downloadResponse.test.ts | 5 +- test/security.test.ts | 98 ++++++++++++++++++++++-- test/uploadAdmission.test.ts | 113 ++++++++++++++++++++++++++++ 25 files changed, 758 insertions(+), 130 deletions(-) create mode 100644 test/uploadAdmission.test.ts diff --git a/README.md b/README.md index ba22bc9..9d7a083 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,8 @@ Every option, default, and cross-field rule, plus the migration notes for `autoP | Disabled by default | all `/api/debug/*` | Not mounted unless `enableDebugApi` is `true`; still requires `x-api-key` | | Authenticated user | None | The service has no end-user identity or session layer | -CORS is a browser control and is never treated as authentication. +CORS is a browser control and is never treated as authentication. Exact `app://` +origins can be listed for a desktop client; they are not in the default set. `GET /api/helia/routing/findProviders/:cid` no longer exists: provider lookup requires content routing, and this deployment intentionally runs none. diff --git a/config.default.json5 b/config.default.json5 index b4ce7bc..7800092 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -38,17 +38,29 @@ downloadMinBytesPerSecond: 32768, downloadMaxDurationMs: 14400000, // 4 h absolute ceiling for one response cors: { - // Exact origins and left-most subdomain wildcards are supported. + // Exact HTTP(S) origins and left-most subdomain wildcards are supported. + // A desktop client that loads the UI from Electron's `app` scheme sends + // `Origin: app://.` (some builds send `app://localhost` or `app://`). + // Uncomment the matching entry only for a node that serves that client. + // A web page cannot forge `app://`; any local app that registered the + // scheme can. `*` and `file:` are rejected. allowedOrigins: [ 'https://*.adamant.im', 'https://adm.im', 'https://*.vercel.app', 'https://*.surge.sh', 'http://localhost:8080' + // 'app://.' ] }, - // Keep false when clients connect directly. Prefer exact proxy IP/CIDR values - // when a reverse proxy forwards requests to this process. + // Keep false when clients connect to this process directly. + // Behind one reverse proxy that overwrites X-Forwarded-For, use hop count 1 + // or the proxy's own addresses, for example: + // trustProxy: 1 + // trustProxy: ['127.0.0.1/8', '::1/128'] + // The same choice applies on every environment that uses that topology. + // `true` is rejected. Leaving this false behind a proxy makes every client + // share the proxy address for rate limits. trustProxy: false, rateLimits: { upload: { windowMs: 900000, limit: 10 }, diff --git a/docker/config.example.json5 b/docker/config.example.json5 index aaf6995..0c21dfa 100644 --- a/docker/config.example.json5 +++ b/docker/config.example.json5 @@ -42,13 +42,33 @@ downloadMaxDurationMs: 14400000, // 4 h absolute ceiling for one response cors: { - // Exact origins and any-depth subdomain wildcards. Replace these with the - // origins of your own application; `*` is rejected. - allowedOrigins: ['http://localhost:8080'] + // Exact HTTP(S) origins and any-depth subdomain wildcards. `*` is rejected. + // Replace localhost with the origins of the application that calls this + // node. A browser page on another host is refused until its origin is listed. + // + // Electron and other custom-scheme desktops send a non-HTTP Origin. The + // official ADAMANT desktop build sends `app://.`. Other builds may send + // `app://localhost` or `app://`. Uncomment the exact value only when + // this node should answer those clients. A web page cannot forge `app://`; + // any local app that registered the scheme can. Wildcards are not accepted + // for that scheme. + allowedOrigins: [ + 'http://localhost:8080' + // 'app://.' + ] }, - // Keep false when clients connect directly. Behind a reverse proxy, list the - // exact proxy addresses or CIDR ranges instead. + // false is correct when clients connect to this process directly. The + // container smoke test does that, and it does not cover a proxied deployment. + // + // Behind one reverse proxy that overwrites X-Forwarded-For — host nginx in + // front of a loopback publish (`127.0.0.1:4000:4000`), or nginx in front of + // a pm2 or systemd process — set the hop count, or trust only the proxy: + // trustProxy: 1 + // trustProxy: ['127.0.0.1/8', '::1/128'] + // Use the same setting on every environment with that topology. `true` is + // rejected. Leaving this false behind a proxy puts every client in one + // rate-limit bucket, the proxy's own address. trustProxy: false, rateLimits: { diff --git a/docs/guide/adamant-messenger.md b/docs/guide/adamant-messenger.md index daf3ac4..52e52ae 100644 --- a/docs/guide/adamant-messenger.md +++ b/docs/guide/adamant-messenger.md @@ -67,8 +67,15 @@ authorization it needs. `config.default.json5` is the ADAMANT production template. Its `nodes` and `peerDiscovery.bootstrap` lists point at the ADAMANT production mesh, and its `cors.allowedOrigins` -entries are ADAMANT origins. **A deployment that is not joining ADAMANT must replace all three.** -Leaving `nodes` and `peerDiscovery.bootstrap` empty runs a standalone node. +entries are ADAMANT web origins (`https://*.adamant.im` and the compatibility hosts listed there). +**A deployment that is not joining ADAMANT must replace all three.** Leaving `nodes` and +`peerDiscovery.bootstrap` empty runs a standalone node. + +Those web origins are one adopter's set, not a required list. The Electron build of the same client +loads from the `app` scheme and sends `Origin: app://.`. That entry is commented in the template +until an operator enables it. A PWA on `https://msg.adamant.im` is already covered by the wildcard +and does not need the desktop origin. Another application lists its own origins and, if it has a +desktop shell, the exact custom-scheme origin that shell sends. Start at [Quick start](/guide/quick-start), or [Docker](/guide/docker) for a container. The messenger client itself is developed in diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 5be38b3..5da5163 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -113,8 +113,8 @@ Notes on the less obvious entries: ## CORS -`cors.allowedOrigins` is required and must be a non-empty array. Each entry is either a canonical -HTTP or HTTPS origin, or an any-depth subdomain wildcard. +`cors.allowedOrigins` is required and must be a non-empty array. Each entry is a canonical HTTP or +HTTPS origin, an any-depth subdomain wildcard, or an exact `app://` desktop origin. | Option | Type | Default | Description | | --------------------- | ---------------- | -------- | ----------------------------------- | @@ -123,18 +123,34 @@ HTTP or HTTPS origin, or an any-depth subdomain wildcard. Entry rules, enforced when the matcher is compiled: - Each entry is a non-empty string of at most 255 characters -- An exact origin must be canonical: scheme `http` or `https`, no credentials, no `*` in the +- An exact HTTP(S) origin must be canonical: scheme `http` or `https`, no credentials, no `*` in the hostname, path exactly `/`, no query string, no fragment, and no trailing slash. The string must equal the parsed origin exactly. - A wildcard has the form `https://*.example.org`, optionally with a port. The suffix hostname must contain a dot, must not start or end with a dot, must not contain `..`, and must be at most 253 characters. A port above 65535 is rejected. -- A bare `*` is rejected in every position +- An exact desktop origin is `app://.` or `app://`. The host is either the single dot Chromium + uses for `app://./…`, or a lowercase hostname (`localhost`, `bundle`, `files.example.org`). No + wildcard, port, userinfo, path, query, or fragment. `APP://.` and `app://./` are rejected; write + the canonical string the client sends. +- A bare `*` is rejected in every position. `file:`, `data:`, and `blob:` are rejected. Matching is exact on scheme and port. A wildcard matches any subdomain at any depth but never the suffix origin itself, so `https://*.example.org` accepts `https://app.example.org` and `https://a.b.example.org` but not `https://example.org`. Add the bare origin as its own entry when -it is needed. +it is needed. `app://.` matches that origin only, not `app://localhost`. + +For a deployment that browsers and a desktop client both call: + +- list each web origin, or one `https://*.example.org` wildcard, for the pages that call the API +- add `app://.` only when a desktop build sends that origin. The official ADAMANT Electron build + does. Another build may send `app://localhost` or `app://` instead; add that exact string. +- leave the desktop entry out until those clients are in scope. [ADAMANT Messenger](/guide/adamant-messenger) + shows one adopter's set, not a list to copy. + +`app://` is not a web origin a page on `https://` can set. Allowing it lets any local application +that registered the `app` scheme read responses in a browser engine. The threat model is in +[Security](/guide/security). A request without an `Origin` header is treated as a non-browser request and is allowed. CORS is a browser-side control: it does not authenticate anything, and it does not restrict `curl` or any @@ -172,9 +188,24 @@ Rejected forms: - comma-separated values inside one string - strings with surrounding whitespace, and empty strings -Behind a reverse proxy, list the exact proxy addresses. While `trustProxy` is `false` the node logs -a warning at startup, because every client behind a proxy would otherwise share the proxy address -for rate-limiting purposes. +Behind a reverse proxy, set a hop count or list the proxy addresses. While `trustProxy` is `false` +the node logs a warning at startup, because every client behind a proxy would otherwise share the +proxy address for rate-limiting purposes. + +The choice is the same whether operators call the deployment production or a test network. What +matters is the path in front of the process: + +- Clients connect straight to `serverPort`, and nothing adds `X-Forwarded-For`. Keep `false`. +- One reverse proxy overwrites `X-Forwarded-For` and is the only hop. Use `1`. That covers host + nginx in front of Docker published as `127.0.0.1:4000:4000`, and nginx on the same machine as a + PM2 or systemd process. The nginx config is in [Installation](/guide/installation). +- Proxy connections come from loopback and a hop count would be wider than the socket you want to + trust. Use `['127.0.0.1/8', '::1/128']`, or the single address of that proxy. +- More than one trusted proxy appends a sanitized forwarding header. Set the hop count to that + number, and only if every path crosses exactly those hops. + +`true` is rejected in all of these cases. Do not treat a Docker deployment as exempt, and do not +leave a bare PM2 process at `false` once nginx is in front of it. ## Rate limits @@ -195,9 +226,10 @@ The `read` policy covers `GET /api/file/:cid`, `GET /api/file/:cid/status`, Behaviour of the limiters: -- Exceeding a window returns `429` with the body `{"error":"Too many requests. Please try again later."}` -- Standard `RateLimit` headers are sent in the IETF draft-8 form; the legacy `X-RateLimit-*` headers - are disabled +- Exceeding a window returns `429` with + `{"error":"Too many requests. Please try again later.","code":"rate_limited"}` +- `Retry-After` carries the seconds left in the window. Standard `RateLimit` and `RateLimit-Policy` + headers are sent in the IETF draft-8 form; the legacy `X-RateLimit-*` headers are disabled - The window is fixed, not sliding, and keyed by the client address that `trustProxy` resolves These limits are separate from the storage concurrency limits, which also answer `429`; see @@ -451,10 +483,15 @@ its node reports at `GET /api/node/details`, and use the reachable address of th cors: { // Replace with the origins of your own application. - allowedOrigins: ['https://app.example.org', 'https://*.example.org'] + // Uncomment 'app://.' only for a desktop client that sends that origin. + allowedOrigins: [ + 'https://app.example.org', + 'https://*.example.org' + // 'app://.' + ] }, - // Behind a reverse proxy, list the exact proxy addresses instead. + // Direct clients. One overwriting reverse proxy: trustProxy: 1 trustProxy: false, rateLimits: { diff --git a/docs/guide/docker.md b/docs/guide/docker.md index 293d10a..3b72d7f 100644 --- a/docs/guide/docker.md +++ b/docs/guide/docker.md @@ -183,7 +183,10 @@ restore procedures are in [Persistence](/operations/persistence). Both ports are declared with `EXPOSE`; publishing them is the operator's decision. - Bind port 4000 to loopback and put a TLS-terminating reverse proxy in front of it. The image - contains no TLS, the process serves plain HTTP, and it logs a warning about that at startup + contains no TLS, the process serves plain HTTP, and it logs a warning about that at startup. + With one such proxy, set `trustProxy: 1` or `trustProxy: ['127.0.0.1/8', '::1/128']` in the + mounted `config.json5`. The image does not bake origins or proxy trust in; `config.default.json5` + inside the image is a template and is never selected automatically - Port 4001 has to be reachable by every peer listed in `nodes`, otherwise replication and health attestations cannot be exchanged - Upload and download routes are public by design, and rate limits and size limits are the only @@ -193,8 +196,14 @@ Both ports are declared with `EXPOSE`; publishing them is the operator's decisio of content-routing metadata. It does not by itself make a deployment private, anonymous, trustless, or censorship-proof +The mounted file is also where CORS lives. `docker/config.example.json5` starts from +`http://localhost:8080` and leaves `app://.` commented. A browser on another host, and a desktop +client that sends `Origin: app://.`, stay blocked until those entries are listed. Do not add an +adopter's origins to the image to save that edit. + Reverse proxy settings, `trustProxy`, CORS, and administrative key handling are covered in -[Security](/guide/security). +[Security](/guide/security). The same proxy and origin rules apply on a test network and on a public +deployment. ## Health checks @@ -347,3 +356,10 @@ The nine steps verify that the image: The same script runs in CI on both architectures for changes to the Dockerfile, the `docker/` directory, `src/`, or the dependency lockfile, and again against the published image after a release. + +The script keeps `trustProxy: false` and a single `http://localhost:8080` origin, then calls the +published port with `curl` and no `Origin` header. That matches a direct probe. It does not +terminate TLS, does not forward `X-Forwarded-For`, and does not check `Access-Control-Allow-Origin`, +so a green run does not prove reverse-proxy client identity or desktop CORS. Those are settings in +the mounted file, not in the image. The comment above `trustProxy` in +`scripts/docker-smoke-test.sh` records the same limit. diff --git a/docs/guide/installation.md b/docs/guide/installation.md index c537314..4aa3fe0 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -260,19 +260,35 @@ The proxy must overwrite untrusted forwarding headers. `$proxy_add_x_forwarded_f whatever the client sent, so at an internet-facing edge use `$remote_addr`; the appending form is correct only when the hop in front of nginx is itself trusted and sanitizing. -Overwriting is only half of it: `trustProxy` has to list the proxy addresses, otherwise Express -keeps using the socket address and every client shares the proxy's identity and rate-limit bucket. -The process logs a startup warning while the value is `false`. Configure exact addresses or CIDR -ranges: +Overwriting is only half of it: `trustProxy` has to name that proxy, otherwise Express keeps using +the socket address and every client shares the proxy's identity and rate-limit bucket. The process +logs a startup warning while the value is `false`. This block is one hop and it overwrites +`X-Forwarded-For`, so either of these is correct: + +```json5 +trustProxy: 1 +``` ```json5 trustProxy: ['127.0.0.1/8', '::1/128'] ``` +Use `1` when every request crosses exactly this one proxy, including a PM2 or systemd process on +the same host with nginx in front. Use the address list when you would rather trust the loopback +socket than a hop count. The same pair of choices applies to a test network and to a public +deployment: the label does not change the header the process sees. Docker published only on +loopback uses this same nginx. The container side is in [Docker](/guide/docker). + A numeric hop count is accepted only for a fixed topology where every path to the application crosses exactly that many trusted hops. The blanket value `true` is rejected, because a client could then spoof `X-Forwarded-For` whenever the last proxy does not overwrite it. +Browsers and desktop clients also need their `Origin` listed in `cors.allowedOrigins`. A page on +`https://app.example.org` needs that origin or a matching wildcard. The official ADAMANT Electron +build sends `app://.`; add that exact entry only on nodes that serve it. Probes that omit `Origin`, +including `curl` and Zabbix, are not subject to CORS, so a green health check does not prove the +app can read the response. The checklist is in [Security](/guide/security). + `client_max_body_size` bounds one request at the edge; the node independently rejects a single file over `uploadLimitSizeBytes` and a combined request over `storage.maxRequestSizeBytes`. Keep the proxy limit at or below `uploadLimitSizeBytes` so oversized bodies are refused before they reach the diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index 7a63eac..30a12f9 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -114,9 +114,9 @@ Notes on the keys above: blockstore and datastore in `$HOME/.adm-ipfs`. An absolute data directory is not implemented yet; it is tracked as [issue #30](https://github.com/Adamant-im/ipfs-node/issues/30) - `peerDiscovery.listen` must contain at least one multiaddr even for a standalone node -- `cors.allowedOrigins` must be non-empty and accepts exact origins and any-depth subdomain - wildcards such as `https://*.example.org`. A bare `*` is rejected. Requests without an `Origin` - header, including `curl`, are not affected by CORS +- `cors.allowedOrigins` must be non-empty and accepts exact HTTP(S) origins, any-depth subdomain + wildcards such as `https://*.example.org`, and an exact desktop origin such as `app://.`. A bare + `*` is rejected. Requests without an `Origin` header, including `curl`, are not affected by CORS - `health.requiredPeerCount` is `0` because there is no remote peer to attest a checkpoint round. It is also the default when fewer than two nodes are configured, and it can never exceed the number of configured remote peers diff --git a/docs/guide/security.md b/docs/guide/security.md index 41e2f38..9b54396 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -118,6 +118,19 @@ The node logs a warning at startup whenever `trustProxy` is `false`, stating tha share the proxy address for rate limiting until exact trusted proxy addresses are configured. The warning is expected when clients connect directly, and is a defect to fix when they do not. +The rule is the same on every environment. A public production node and a test network that both +terminate TLS at nginx and forward to this process need the same `trustProxy` value. Nothing about +the name of the network changes how Express reads `X-Forwarded-For`. Direct access to the process +port, with no forwarding headers, keeps `false`. + +One proxy hop that overwrites `X-Forwarded-For` is hop count `1`. That is the usual shape for host +nginx in front of a container published on `127.0.0.1:4000:4000`, and for nginx on the same machine +as a [PM2 or systemd](/guide/installation) process. An address list such as +`['127.0.0.1/8', '::1/128']` trusts the socket instead of counting hops, which is the tighter choice +when every proxy connection comes from loopback. Use a hop count only when every path to the process +crosses exactly that many trusted proxies. The nginx snippet is in +[Installation](/guide/installation), and the container publish is in [Docker](/guide/docker). + ## Administrative API key Generate a key with: @@ -156,13 +169,16 @@ Operational notes: ## CORS -`cors.allowedOrigins` is required and must be a non-empty array. Each entry is either a canonical -HTTP or HTTPS origin, such as `https://adm.im` or `http://localhost:8080`, or an any-depth subdomain -wildcard of the form `https://*.adamant.im`. +`cors.allowedOrigins` is required and must be a non-empty array. Each entry is one of: + +- a canonical HTTP or HTTPS origin, such as `https://adm.im` or `http://localhost:8080` +- an any-depth subdomain wildcard of the form `https://*.adamant.im` +- an exact desktop origin, `app://.` or `app://` Startup rejects a bare `*`, an entry carrying a path, credentials, query string, or fragment, a -scheme other than `http` or `https`, an entry longer than 255 characters, a wildcard hostname -without a dot or longer than 253 characters, and a port above 65535. +scheme other than `http`, `https`, or exact `app`, an entry longer than 255 characters, a wildcard +hostname without a dot or longer than 253 characters, a port above 65535, and any wildcard, port, or +userinfo on an `app` entry. `file:`, `data:`, and `blob:` are rejected. Matching for the rule `https://*.adamant.im`: @@ -176,8 +192,29 @@ Matching for the rule `https://*.adamant.im`: | `https://notadamant.im` | Not allowed; the match requires the dot boundary | An exact entry matches that origin and nothing else. A request that carries no `Origin` header is -allowed through: those are non-browser clients such as `curl`, server-to-server calls, and mobile -applications, which the browser origin model never governed. +allowed through: those are non-browser clients such as `curl`, server-to-server calls, monitoring +probes, and mobile applications, which the browser origin model never governed. A probe can therefore +be green while a browser or desktop renderer shows the node offline; see +[Troubleshooting](/operations/troubleshooting). + +### Desktop `app://` origins + +Electron, and similar custom-scheme desktops, load the UI outside `http` and `https`. Chromium then +sends that scheme in `Origin`. For a privileged standard scheme named `app` whose pages are loaded +as `app://./index.html`, the serialized origin is `app://.`. A build that loads +`app://localhost/…` or `app:///…` sends that host instead. Configure the exact value the +client sends. There is no `app://*` wildcard. + +The official ADAMANT desktop build sends `app://.`. That entry is commented out in +`config.default.json5` and in `docker/config.example.json5`. Add it only on a node that should +answer that client. [ADAMANT Messenger](/guide/adamant-messenger) is one adopter's origin set, not +the list every deployment should copy. A PWA on `https://msg.adamant.im` is covered by +`https://*.adamant.im` and does not need the desktop entry. + +`app://` is not a web origin a page on `https://` can choose. Browsers set `Origin` from the +document that made the request, so allowing `app://.` does not let an arbitrary website read the +API. It does let any local application that registered the `app` scheme. That is why the entry is +an explicit opt-in rather than a default. The rest of the CORS configuration is fixed: methods `GET` and `POST`, allowed request headers `content-type` and `x-api-key`, `credentials: false`, and a preflight `maxAge` of 600 seconds. @@ -192,7 +229,9 @@ one more reason to keep the key out of browsers. Two independent mechanisms bound HTTP load, and both answer `429`. Rate limiters count requests per client address in a fixed window, using `express-rate-limit` with -`draft-8` standard headers and legacy headers disabled. +`draft-8` standard headers and legacy headers disabled. A window refusal is `429` with +`{"error":"Too many requests. Please try again later.","code":"rate_limited"}`, a `Retry-After` +header set to the seconds remaining in the window, and `RateLimit` / `RateLimit-Policy` headers. | Limiter | Option | Default | Routes | | ------- | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -211,12 +250,12 @@ rates at the proxy as well. Admission limiters are a different mechanism: they bound how many operations may be in flight at once, not how often they may be requested. -| Limiter | Option | Default | Refusal | -| --------------------------- | ------------------------------------------------------------ | ------- | ------------------------------------ | -| Uploads | `storage.maxConcurrentUploads` | 32 | `429` with `Retry-After: 5` | -| Downloads, global | `storage.maxConcurrentDownloads` | 64 | `429` with `Retry-After: 5` | -| Downloads, per client | `storage.maxConcurrentDownloadsPerClient` | 8 | `429` with `Retry-After: 5` | -| Incoming copies over libp2p | Derived as `max(4, floor(storage.maxConcurrentUploads / 4))` | 8 | Protocol refusal with code `no_room` | +| Limiter | Option | Default | Refusal | +| --------------------------- | ------------------------------------------------------------ | ------- | ------------------------------------------------------------- | +| Uploads | `storage.maxConcurrentUploads` | 32 | `429`, `Retry-After: 5`, `code` `upload_concurrency` | +| Downloads, global | `storage.maxConcurrentDownloads` | 64 | `429`, `Retry-After: 5`, `code` `download_concurrency` | +| Downloads, per client | `storage.maxConcurrentDownloadsPerClient` | 8 | `429`, `Retry-After: 5`, `code` `download_client_concurrency` | +| Incoming copies over libp2p | Derived as `max(4, floor(storage.maxConcurrentUploads / 4))` | 8 | Protocol refusal with code `no_room` | The per-client download share exists because a download slot is held for the whole transfer: without it, one address can hold every global slot and leave the rest of the network with `429` until those @@ -225,9 +264,10 @@ HTTP rate limiters do not apply, and because a copy claims the aggregate request duration. Since a rate refusal and an admission refusal are both `429`, the status alone cannot tell an -operator which one fired, and the two need opposite reactions. That is why `GET /api/node/details` -reports limiter occupancy under `concurrency`: active count and limit for uploads, incoming copies, -and downloads. See [Monitoring](/operations/monitoring). +operator which one fired, and the two need opposite reactions. The JSON `code` tells them apart +without reading the message text. `GET /api/node/details` also reports limiter occupancy under +`concurrency`: active count and limit for uploads, incoming copies, and downloads. See +[Monitoring](/operations/monitoring). ## Public upload decision @@ -237,14 +277,16 @@ directly to the node. The decision is bounded rather than open-ended: - `uploadLimitSizeBytes` caps one file and `maxFileCount` caps the number of files in a request; both are enforced by the streaming multipart parser, so an over-limit part never reaches the blockstore - `storage.maxRequestSizeBytes` caps the combined size of one request and is checked from - `Content-Length` before the parser runs, answering `413` -- the multipart body accepts `files` parts only; any text field is rejected with `400` -- the upload rate limiter and the upload admission limiter apply, answering `429` + `Content-Length` before the parser runs, answering `413` with `code` `request_too_large` +- the multipart body accepts `files` parts only; any text field is rejected with `400` and `code` + `multipart_fields`. A missing file part is `400` with `code` `no_file` +- the upload rate limiter and the upload admission limiter apply, answering `429` with `code` + `rate_limited` or `upload_concurrency` - `storage.diskReserveBytes` is claimed before any block is written, so a request that would consume - the reserve is refused with `507` + the reserve is refused with `507` and `code` `insufficient_storage` - `storage.confirmationRequired` keeps an upload temporary until an authenticated confirmation, and - `replication.requireQuorumOnUpload` refuses an upload with `503` when the required copies were not - acknowledged + `replication.requireQuorumOnUpload` refuses an upload with `503` and `code` `replication_quorum` + when the required copies were not acknowledged Those limits bound what an uploader can consume. They are not an authorization guarantee: anyone who can reach the port can store bytes on the node. A deployment that needs signed upload authorization diff --git a/docs/openapi.yaml b/docs/openapi.yaml index fa40d4a..8c7d24e 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -66,13 +66,13 @@ paths: '400': $ref: '#/components/responses/BadRequest' '413': - description: Request or file limit exceeded + description: Combined request size exceeded (`code` `request_too_large`) '429': - description: Upload rate or concurrency limit exceeded + description: Upload rate window (`code` `rate_limited`) or concurrency limit (`code` `upload_concurrency`) '503': - description: Required replication quorum was not reached + description: Required replication quorum was not reached (`code` `replication_quorum`) '507': - description: The disk reserve would be consumed + description: The disk reserve would be consumed (`code` `insufficient_storage`) /api/file/{cid}: parameters: - $ref: '#/components/parameters/Cid' @@ -97,9 +97,9 @@ paths: '400': $ref: '#/components/responses/BadRequest' '408': - description: Discovery or complete retrieval timed out + description: Discovery or complete retrieval timed out (`code` `file_timeout`) '429': - description: Read rate, per-client, or global download concurrency limit exceeded + description: Read rate window (`code` `rate_limited`) or download concurrency (`code` `download_concurrency` or `download_client_concurrency`) /api/file/{cid}/status: parameters: - $ref: '#/components/parameters/Cid' @@ -387,9 +387,30 @@ components: availableSizeInMb: { type: number } Error: type: object + description: > + `error` is always present. Operational upload and read failures also set `code`. + Other responses omit `code`. required: [error] properties: error: { type: string } + code: + type: string + description: Stable identifier for an operational upload or read failure. Absent on other errors. + enum: + - no_file + - too_many_files + - file_too_large + - multipart_fields + - invalid_multipart + - request_too_large + - rate_limited + - upload_concurrency + - download_concurrency + - download_client_concurrency + - file_timeout + - lifecycle_busy + - replication_quorum + - insufficient_storage responses: BadRequest: description: Invalid request diff --git a/docs/operations/monitoring.md b/docs/operations/monitoring.md index feda6a4..628be24 100644 --- a/docs/operations/monitoring.md +++ b/docs/operations/monitoring.md @@ -188,7 +188,18 @@ placeholders. Two warnings are expected on a fresh installation and are not faults: the TLS notice at listen time, and the `trustProxy is false` notice. Do not widen the proxy trust rule merely to silence the -second; see [Security and privacy](/guide/security). +second; see [Security and privacy](/guide/security). Silence it when a reverse proxy really is in +front, on a test network the same way as on a public one, by setting hop count `1` or the proxy's +addresses. + +Health probes and browser clients are not the same request. `GET /api/node/health` from Zabbix, the +container health check, or `curl` sends no `Origin` header, so CORS does not apply and a `ready` +body means the process is up. A browser or an Electron renderer sends `Origin`. If that origin is +not allowed, the HTTP status can still be 200 while the page never sees the body: `fetch` and axios +report a network error, and the app shows the node offline. Confirm with +`curl -D - -H 'Origin: https://app.example.org' https:///api/node/info` or, for the official +ADAMANT desktop build, `-H 'Origin: app://.'`, and look for `Access-Control-Allow-Origin` echoing +that value. See [Troubleshooting](/operations/troubleshooting). ## Staged rollouts and membership epochs diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index b9e1c6c..f0f1bf2 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -65,16 +65,66 @@ Expected at a round boundary: peers may attest an adjacent round. That is the fail-safe result. Set `health.requiredPeerCount: 0` on the transitioning fleet and raise it once every required peer is upgraded; see [Upgrades and rollback](/operations/upgrades). +## The app shows the node offline while probes succeed + +`curl` and a Zabbix or container probe call the API with no `Origin` header. CORS does not apply, +so `GET /api/node/health` can report `ready` while a browser or desktop renderer never sees the +body. `fetch` and axios then surface a network error, and the client shows the node offline. + +Check the origin the client actually sends: + +- A PWA or other web app sends `https://`. It must match an entry in + `cors.allowedOrigins` on scheme, host, and port. `https://*.example.org` does not match + `http://`, a different port, or the bare `https://example.org`. +- The official ADAMANT Electron build sends `Origin: app://.`. Other Electron builds send + `app://localhost` or `app://` when they load pages from that host. The entry has to be + exact, and it is commented out until an operator adds it. A web wildcard does not cover it. +- Confirm with `curl -D - -H 'Origin: app://.' https:///api/node/info`. A working desktop + origin answers `Access-Control-Allow-Origin: app://.`. A missing header with HTTP 200 is this + failure. Injecting the header in the client hides it only until every node the client uses lists + the origin. + +The same miss fails `POST /api/file/upload`. Clients that retry across nodes and then show a generic +"failed to upload" message are often aggregating these network errors. This repository does not +emit that client string. Read the node response, or the `curl` check above, before raising rate +limits. + +`GET /api/node/health` staying `degraded` is a different fault. The probe is then telling the truth; +read `checks` in the health body. A desktop CORS failure does not change `state`. + ## Uploads are rejected -| Status | Cause | Action | -| ------ | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `400` | No file was sent, more files than `maxFileCount`, one file over `uploadLimitSizeBytes`, or a non-file text field | Send `files` parts only, within both limits | -| `413` | Combined size over `storage.maxRequestSizeBytes` | Raise the limit or split the request; the check runs against `Content-Length` before parsing | -| `429` | Upload rate limit, or the concurrent upload limit | Compare `concurrency.uploads` on `GET /api/node/details` against the limit: a full limiter is capacity, an empty one is rate | -| `503` | `replication.requireQuorumOnUpload` is on and the quorum was not reached | Check peer connectivity; the upload was rolled back cleanly | -| `507` | The request would consume `storage.diskReserveBytes` | Free space, lower the reserve, or collect garbage with `POST /api/storage/gc?force=true` | -| `500` | Storage or replica settlement failed | Not a clean rejection. The content may be partially durable; re-uploading is safe because content-addressed dedup absorbs it | +Operational failures on the upload and read paths include a stable `code` next to `error`. Branch +on `code`. The full list is in [API overview](/reference/api). + +| Status | `code` | Cause | Action | +| ------ | ---------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| `400` | `no_file` | No file part was sent | Send one or more `files` parts | +| `400` | `too_many_files` | More files than `maxFileCount` | Stay within the limit | +| `400` | `file_too_large` | One file is over `uploadLimitSizeBytes` | Stay within the per-file limit | +| `400` | `multipart_fields` | A non-file text field was sent | Send `files` parts only | +| `413` | `request_too_large` | Combined size over `storage.maxRequestSizeBytes` | Raise the limit or split the request; the check runs against `Content-Length` before parsing | +| `429` | `rate_limited` | Upload rate window exceeded. Default is 10 requests per 900000 ms per client address | Wait for `Retry-After`. If every client shares one bucket, fix `trustProxy` before raising the limit | +| `429` | `upload_concurrency` | `storage.maxConcurrentUploads` slots are full | `Retry-After: 5`. Compare `concurrency.uploads` on `GET /api/node/details` with the limit | +| `503` | `replication_quorum` | `replication.requireQuorumOnUpload` is on and the quorum was not reached | Check peer connectivity; the upload was rolled back cleanly | +| `507` | `insufficient_storage` | The request would consume `storage.diskReserveBytes` | Free space, lower the reserve, or collect garbage with `POST /api/storage/gc?force=true` | +| `500` | | Storage or replica settlement failed | Not a clean rejection. The content may be partially durable; re-uploading is safe because content-addressed dedup absorbs it | + +**Ten uploads fail in a row and the client stops.** +The default upload window is 10 requests per 15 minutes for one client address. Eleven rapid +uploads from that address produce `429` / `rate_limited` on the eleventh, with `Retry-After` and +`RateLimit` headers. Ten failures can also be ten CORS or connection errors that never reached this +counter. A shared address makes the window a deployment-wide budget: with `trustProxy: false` +behind nginx, every client is the proxy. The startup log warns about that. Set hop count `1` or +the proxy CIDR, on a test network the same way as on a public deployment. Do not set `true`. + +**The upload never returns a status.** +Admission answers `507` when the disk reserve would be consumed, and `429` / `upload_concurrency` +when the in-flight slots are full. A request that sits with no status is not one of those +refusals. The process does not cap its own memory; the host cgroup or service limit does. A +blockstore scan on `diskUsageScanPeriod` shares the disk with uploads, and a node whose +`storageFresh` check is failing is already late on that scan. Check host memory and disk wait, and +`checks.storageFresh` on `GET /api/node/health`, before treating the hang as a client bug. **A proxy rejects the upload before the node sees it.** Set the proxy body limit at or below `uploadLimitSizeBytes` and disable request buffering for the @@ -82,7 +132,8 @@ upload path. An nginx example is in [Installation](/guide/installation). ## Downloads time out -`408` is the only timeout answer, and three separate deadlines can produce it: discovery bounded by +`408` with `code` `file_timeout` is the only timeout answer, and three separate deadlines can +produce it: discovery bounded by `findFileTimeout`, a stalled transfer bounded by `downloadIdleTimeout`, and a size-aware complete deadline derived from `downloadMinBytesPerSecond` and capped by `downloadMaxDurationMs`. A failing request may spend more than one `findFileTimeout` before answering. @@ -101,6 +152,12 @@ Likely causes: The node never answers `404` for a CID it cannot retrieve, because it cannot know whether the content exists elsewhere. A `404` on this API means an unknown registry record or an unrouted path. +A client that just uploaded can still see `408` / `file_timeout` on the node it did not upload to. +That is replication lag or a peer that does not hold the CID yet, not a failed upload. Check +`GET /api/file/:cid/status`, `replication` on `GET /api/node/health`, and whether repair is behind +(`repairFresh`, `replication.backlog`). Raising `findFileTimeout` waits longer for the same lookup; +it does not place a missing copy. + ## The process exits with `Cannot find module node_datachannel.node` Helia depends on `@libp2p/webrtc`, which loads the `node-datachannel` native binary at import time. @@ -147,8 +204,11 @@ own counters, so the effective limit is per process. Enforce rates at the proxy multi-process deployment. **A `429` with no obvious rate abuse.** -It may be admission, not rate. `GET /api/node/details` reports limiter occupancy under -`concurrency`; an admission refusal also carries `Retry-After: 5`. +Read `code`. `rate_limited` is the window and carries `Retry-After` plus `RateLimit` headers. +`upload_concurrency`, `download_concurrency`, and `download_client_concurrency` are admission: +`Retry-After: 5`, and `GET /api/node/details` shows occupancy under `concurrency`. An empty +limiter with `rate_limited` from many users at once is a shared client address; see the upload +section above. ## Disk fills up diff --git a/docs/reference/api.md b/docs/reference/api.md index 8fdde44..a699b4d 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -100,15 +100,20 @@ new clients should read the per-file field. The report is counts and per-attempt outcomes only. It never carries node names, peer ids, or peer error text. -| Status | Meaning | -| ------ | ----------------------------------------------------------------------------- | -| `200` | Every file was stored and pinned | -| `400` | No file was sent, too many files, or one file exceeded `uploadLimitSizeBytes` | -| `413` | The combined size exceeded `storage.maxRequestSizeBytes` | -| `429` | The upload rate limit or the concurrent upload limit was exceeded | -| `503` | A required replication quorum could not be reached | -| `507` | Storing the request would consume `storage.diskReserveBytes` | -| `500` | Storage or replica settlement failed; do not treat it as a clean rejection | +| Status | `code` | Meaning | +| ------ | ---------------------- | -------------------------------------------------------------------------- | +| `200` | | Every file was stored and pinned | +| `400` | `no_file` | No file was sent | +| `400` | `too_many_files` | More files than `maxFileCount` | +| `400` | `file_too_large` | One file exceeded `uploadLimitSizeBytes` | +| `400` | `multipart_fields` | A non-file text field was sent | +| `400` | `invalid_multipart` | The multipart body could not be parsed | +| `413` | `request_too_large` | The combined size exceeded `storage.maxRequestSizeBytes` | +| `429` | `rate_limited` | The upload rate window was exceeded. `Retry-After` and `RateLimit` are set | +| `429` | `upload_concurrency` | The concurrent upload limit was exceeded. `Retry-After` is `5` | +| `503` | `replication_quorum` | A required replication quorum could not be reached | +| `507` | `insufficient_storage` | Storing the request would consume `storage.diskReserveBytes` | +| `500` | | Storage or replica settlement failed; do not treat it as a clean rejection | An interrupted upload leaves no blocks behind: each request owns a session that removes exactly the blocks it created. See [the storage lifecycle](/storage-lifecycle). @@ -127,17 +132,19 @@ successful response carries an `ETag` holding the quoted CID, `Cache-Control: private, max-age=3600, must-revalidate`, and `Accept-Ranges: none`. A matching `If-None-Match` answers `304` after availability has been checked. -| Status | Meaning | -| ------ | ------------------------------------------------------------------------------------------ | -| `200` | The response contains the requested file as an attachment | -| `304` | The `If-None-Match` validator matched | -| `400` | The CID is invalid | -| `408` | The file could not be found or retrieved before the configured timeout | -| `429` | The read rate limit, the per-client, or the global download concurrency limit was exceeded | -| `500` | An unexpected internal failure occurred before streaming started | +| Status | `code` | Meaning | +| ------ | ----------------------------- | --------------------------------------------------------------------------- | +| `200` | | The response contains the requested file as an attachment | +| `304` | | The `If-None-Match` validator matched | +| `400` | | The CID is invalid. The body is `{"error":"Invalid CID"}` and has no `code` | +| `408` | `file_timeout` | The file could not be found or retrieved before the configured timeout | +| `429` | `rate_limited` | The read rate window was exceeded. `Retry-After` and `RateLimit` are set | +| `429` | `download_concurrency` | The global download slot limit was exceeded. `Retry-After` is `5` | +| `429` | `download_client_concurrency` | This client address holds its download share. `Retry-After` is `5` | +| `500` | | An unexpected internal failure occurred before streaming started | Range headers are ignored. Discovery, idle transfer time, and the size-aware complete transfer all -have bounded deadlines and all end in `408 File request timed out`, so a failed request may spend +have bounded deadlines and all end in `408` with `code` `file_timeout`, so a failed request may spend more than one `findFileTimeout` before answering. A client disconnect cancels the underlying retrieval. If an error occurs after response bytes have started, the server terminates the incomplete response, because a status and a JSON body can no longer be sent safely. @@ -296,8 +303,32 @@ and this node registers none. `/api/debug/*` is not mounted at all unless `enabl ## Error format -Errors are JSON objects with a single `error` string. `x-powered-by` is disabled, and an unmatched -path answers `404` from the not-found handler. +Errors are JSON objects with an `error` string. Operational upload and read failures also set +`code` to a stable identifier. Clients should branch on `code` when it is present. Responses that +are not in the table below omit `code`, including validation messages such as `Invalid CID`, +authentication failures, and `500` `Internal Server Error`. + +| `code` | Status | Meaning | +| ----------------------------- | ------ | --------------------------------------------------------------------- | +| `no_file` | `400` | The upload contained no `files` part | +| `too_many_files` | `400` | The upload contained more parts than `maxFileCount` | +| `file_too_large` | `400` | One part exceeded `uploadLimitSizeBytes` | +| `multipart_fields` | `400` | The multipart body contained a text field | +| `invalid_multipart` | `400` | The multipart body was rejected for another parser limit | +| `request_too_large` | `413` | The declared or streamed size exceeded `storage.maxRequestSizeBytes` | +| `rate_limited` | `429` | The fixed window for this route was exceeded | +| `upload_concurrency` | `429` | No upload slot was free | +| `download_concurrency` | `429` | No download slot was free | +| `download_client_concurrency` | `429` | This client address holds its download share | +| `file_timeout` | `408` | Discovery or retrieval hit its deadline | +| `lifecycle_busy` | `409` | A confirm, release, or replication step found the file mid-transition | +| `replication_quorum` | `503` | `requireQuorumOnUpload` rejected the upload | +| `insufficient_storage` | `507` | Admitting the upload would consume `storage.diskReserveBytes` | + +A `429` with `code` `rate_limited` also sends `Retry-After` (seconds left in the window) and the +draft-8 `RateLimit` and `RateLimit-Policy` headers. An admission `429` sends `Retry-After: 5` and +does not send `RateLimit`. `x-powered-by` is disabled, and an unmatched path answers `404` from the +not-found handler. ## Machine-readable specification diff --git a/scripts/docker-smoke-test.sh b/scripts/docker-smoke-test.sh index 99d114a..c54f074 100755 --- a/scripts/docker-smoke-test.sh +++ b/scripts/docker-smoke-test.sh @@ -91,6 +91,9 @@ write_config() { maxFileCount: $2, findFileTimeout: 20000, cors: { allowedOrigins: ['http://localhost:8080'] }, + // Direct probe of the published port. No Origin header and no forwarding + // hop, so a green run does not prove reverse-proxy client identity or + // desktop CORS. Those live in the mounted config; see docs/guide/docker.md. trustProxy: false, rateLimits: { upload: { windowMs: 900000, limit: 100 }, diff --git a/src/api/uploadRoute.ts b/src/api/uploadRoute.ts index ce2455a..a44f3ff 100644 --- a/src/api/uploadRoute.ts +++ b/src/api/uploadRoute.ts @@ -1,5 +1,6 @@ import type { NextFunction, Request, RequestHandler, Response } from 'express' import type { IpfsNode } from '../ipfs-node.js' +import { publicErrorBodies } from '../security/errors.js' import { pinFile, unpinFile } from '../storage/pinning.js' import type { FileRecord, FileRegistry } from '../storage/registry.js' import type { ReplicationReport } from '../storage/replication.js' @@ -251,7 +252,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ try { if (!Array.isArray(req.files) || req.files.length === 0) { res.statusCode = 400 - return res.send({ error: 'No file uploaded' }) + return res.send(publicErrorBodies.noFile) } session = dependencies.getSession(req) @@ -456,7 +457,7 @@ export function createUploadHandler(dependencies: UploadRouteDependencies): Requ ) } - return res.status(503).send({ error: 'Replication quorum not reached' }) + return res.status(503).send(publicErrorBodies.replicationQuorum) } // Commit the local decision before making prepared remote copies diff --git a/src/middleware/downloadAdmission.ts b/src/middleware/downloadAdmission.ts index 7d7031b..c9338bc 100644 --- a/src/middleware/downloadAdmission.ts +++ b/src/middleware/downloadAdmission.ts @@ -1,4 +1,5 @@ import type { Request, RequestHandler } from 'express' +import { publicErrorBodies } from '../security/errors.js' export interface DownloadConcurrencyLimiter { tryAcquire(): boolean @@ -37,15 +38,13 @@ export function createDownloadAdmission( if (perClientLimit !== undefined && held >= perClientLimit) { res.set('Retry-After', '5') - res - .status(429) - .send({ error: 'Too many concurrent downloads from this client. Please try again later.' }) + res.status(429).send(publicErrorBodies.downloadClientConcurrency) return } if (!limiter.tryAcquire()) { res.set('Retry-After', '5') - res.status(429).send({ error: 'Too many concurrent downloads. Please try again later.' }) + res.status(429).send(publicErrorBodies.downloadConcurrency) return } diff --git a/src/middleware/uploadAdmission.ts b/src/middleware/uploadAdmission.ts index ddfd2ac..80cc9df 100644 --- a/src/middleware/uploadAdmission.ts +++ b/src/middleware/uploadAdmission.ts @@ -5,6 +5,7 @@ import { checkRequestSize, parseContentLength } from '../storage/limits.js' import type { StorageOperationLease, StorageOperationLock } from '../storage/operationLock.js' import { claimSpace } from '../storage/reservation.js' import type { StorageConfig } from '../storage/config.js' +import { publicErrorBodies } from '../security/errors.js' import { UploadSession } from '../storage/uploadSession.js' const SESSION_KEY = Symbol('uploadSession') @@ -64,7 +65,7 @@ export function createUploadAdmission(dependencies: UploadAdmissionDependencies) if (!dependencies.limiter.tryAcquire()) { res.set('Retry-After', '5') - res.status(429).send({ error: 'Too many concurrent uploads. Please try again later.' }) + res.status(429).send(publicErrorBodies.uploadConcurrency) return } @@ -117,7 +118,7 @@ export function createUploadAdmission(dependencies: UploadAdmissionDependencies) if (!checkRequestSize(declaredBytes, storage.maxRequestSizeBytes).allowed) { release() - res.status(413).send({ error: 'Upload size limit exceeded' }) + res.status(413).send(publicErrorBodies.requestTooLarge) return } @@ -154,7 +155,7 @@ export function createUploadAdmission(dependencies: UploadAdmissionDependencies) `${storage.diskReserveBytes} byte reserve` ) release() - res.status(507).send({ error: 'Insufficient storage' }) + res.status(507).send(publicErrorBodies.insufficientStorage) return } diff --git a/src/security/config.ts b/src/security/config.ts index 89eadeb..e7ddd85 100644 --- a/src/security/config.ts +++ b/src/security/config.ts @@ -21,6 +21,8 @@ type SecurityConfigInput = { /** * Validate configuration values that define HTTP security boundaries. * Missing optional admin and proxy settings fail closed at runtime. + * Origin rules, including exact `app://` desktop origins, are compiled by + * `createOriginMatcher`. * * @param config parsed application configuration */ diff --git a/src/security/cors.ts b/src/security/cors.ts index 2647447..f4591ad 100644 --- a/src/security/cors.ts +++ b/src/security/cors.ts @@ -7,10 +7,21 @@ type OriginRule = { wildcard: boolean } +/** + * Exact `app://` origins. The dot host is what Chromium serializes for a + * standard Electron scheme loaded as `app://./…` (`Origin: app://.`). + * A normal host covers builds that load `app://localhost/…` or `app:///…`. + * No wildcard, port, userinfo, path, query, or fragment: each entry is one origin. + */ +const APP_ORIGIN = + /^app:\/\/(?:\.|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*)$/ + /** * Compile a list of exact origins and any-depth subdomain suffix wildcards. - * Wildcards use the form `https://*.example.org`; paths, credentials, query - * strings, fragments, and the suffix origin itself are rejected. + * HTTP(S) wildcards use the form `https://*.example.org`; paths, credentials, + * query strings, fragments, and the suffix origin itself are rejected. + * Desktop clients opt in with an exact `app://.` or `app://` entry. + * `file:`, `data:`, `blob:`, and a bare `*` stay rejected. * * @param allowedOrigins browser origins accepted by the API * @returns a predicate suitable for testing a request Origin value @@ -30,13 +41,23 @@ export function createOriginMatcher(allowedOrigins: unknown): (origin: string) = return false } - if ( - parsed.username || - parsed.password || - parsed.pathname !== '/' || - parsed.search || - parsed.hash - ) { + if (parsed.username || parsed.password || parsed.search || parsed.hash) { + return false + } + + // `new URL('app://.')` has an empty path, not `/`, and an opaque origin. + // Only that canonical form matches a configured app rule. + if (parsed.protocol === 'app:') { + if (parsed.pathname !== '' || parsed.port !== '') { + return false + } + + return rules.some( + (rule) => rule.protocol === 'app:' && !rule.wildcard && parsed.hostname === rule.hostname + ) + } + + if (parsed.pathname !== '/') { return false } @@ -74,6 +95,10 @@ function parseOriginRule(value: unknown): OriginRule { throw new Error('Each CORS origin must be a non-empty string of at most 255 characters') } + if (/^app:/i.test(value)) { + return parseAppOrigin(value) + } + const wildcardMatch = /^(https?):\/\/\*\.([a-z0-9.-]+)(?::([0-9]{1,5}))?$/i.exec(value) if (wildcardMatch) { const [, protocol, hostname, port = ''] = wildcardMatch @@ -115,6 +140,45 @@ function parseOriginRule(value: unknown): OriginRule { } } +/** + * Accept one exact desktop origin. The configured string must already be the + * canonical serialization Chromium puts in `Origin`, so `APP://.` and + * `app://./` are rejected rather than silently rewritten. + * + * @param value candidate `app://` entry + * @returns a non-wildcard rule + */ +function parseAppOrigin(value: string): OriginRule { + if (!APP_ORIGIN.test(value)) { + throw new Error( + `CORS app origins must be canonical app://. or app:// values with no wildcard, port, path, or userinfo: ${value}` + ) + } + + const parsed = new URL(value) + if ( + parsed.href !== value || + parsed.protocol !== 'app:' || + parsed.username || + parsed.password || + parsed.port !== '' || + parsed.search || + parsed.hash || + parsed.pathname !== '' + ) { + throw new Error( + `CORS app origins must be canonical app://. or app:// values with no wildcard, port, path, or userinfo: ${value}` + ) + } + + return { + protocol: 'app:', + hostname: parsed.hostname, + port: '', + wildcard: false + } +} + function validateHostname(hostname: string): void { if ( hostname.length > 253 || diff --git a/src/security/errors.ts b/src/security/errors.ts index 9d059a4..d9ee682 100644 --- a/src/security/errors.ts +++ b/src/security/errors.ts @@ -3,11 +3,80 @@ import { RequestSizeLimitError } from '../storage/limits.js' import { FileLifecycleBusyError } from '../storage/registry.js' import { FileNotFoundError } from '../utils/fileErrors.js' +/** JSON body of a client-facing error. `code` is set on operational upload and read failures. */ +export type PublicErrorBody = { + error: string + code?: string +} + export type PublicError = { status: number - body: { error: string } + body: PublicErrorBody } +/** + * Stable bodies for failures a client can branch on. + * The `error` text stays the historical message; `code` is the identifier. + */ +export const publicErrorBodies = { + rateLimited: { + error: 'Too many requests. Please try again later.', + code: 'rate_limited' + }, + uploadConcurrency: { + error: 'Too many concurrent uploads. Please try again later.', + code: 'upload_concurrency' + }, + downloadConcurrency: { + error: 'Too many concurrent downloads. Please try again later.', + code: 'download_concurrency' + }, + downloadClientConcurrency: { + error: 'Too many concurrent downloads from this client. Please try again later.', + code: 'download_client_concurrency' + }, + requestTooLarge: { + error: 'Upload size limit exceeded', + code: 'request_too_large' + }, + fileTooLarge: { + error: 'Upload size limit exceeded', + code: 'file_too_large' + }, + tooManyFiles: { + error: 'Upload file count limit exceeded', + code: 'too_many_files' + }, + multipartFields: { + error: 'Multipart fields are not allowed', + code: 'multipart_fields' + }, + invalidMultipart: { + error: 'Invalid multipart upload', + code: 'invalid_multipart' + }, + insufficientStorage: { + error: 'Insufficient storage', + code: 'insufficient_storage' + }, + replicationQuorum: { + error: 'Replication quorum not reached', + code: 'replication_quorum' + }, + fileTimeout: { + error: 'File request timed out', + code: 'file_timeout' + }, + lifecycleBusy: { + error: 'File lifecycle is busy', + code: 'lifecycle_busy' + }, + noFile: { + error: 'No file uploaded', + code: 'no_file' + } +} as const satisfies Record + /** Error whose constructor accepts only an approved client-facing message. */ export class InvalidRequestError extends Error { constructor( @@ -33,35 +102,35 @@ export function getPublicError(error: unknown): PublicError { } if (error instanceof FileNotFoundError) { - return { status: 408, body: { error: 'File request timed out' } } + return { status: 408, body: publicErrorBodies.fileTimeout } } if (error instanceof FileLifecycleBusyError) { - return { status: 409, body: { error: 'File lifecycle is busy' } } + return { status: 409, body: publicErrorBodies.lifecycleBusy } } // Raised while streaming when the parts of one request exceed the aggregate // limit. The configured limit is not echoed back to the client. if (error instanceof RequestSizeLimitError) { - return { status: 413, body: { error: 'Upload size limit exceeded' } } + return { status: 413, body: publicErrorBodies.requestTooLarge } } if (error instanceof multer.MulterError) { - return { status: 400, body: { error: multerErrorMessage(error.code) } } + return { status: 400, body: multerErrorBody(error.code) } } return { status: 500, body: { error: 'Internal Server Error' } } } -function multerErrorMessage(code: string): string { +function multerErrorBody(code: string): PublicErrorBody { if (code === 'LIMIT_FILE_SIZE') { - return 'Upload size limit exceeded' + return publicErrorBodies.fileTooLarge } if (code === 'LIMIT_FILE_COUNT' || code === 'LIMIT_PART_COUNT') { - return 'Upload file count limit exceeded' + return publicErrorBodies.tooManyFiles } if (code === 'LIMIT_FIELD_COUNT') { - return 'Multipart fields are not allowed' + return publicErrorBodies.multipartFields } - return 'Invalid multipart upload' + return publicErrorBodies.invalidMultipart } diff --git a/src/security/rateLimit.ts b/src/security/rateLimit.ts index a0ec37c..b54676d 100644 --- a/src/security/rateLimit.ts +++ b/src/security/rateLimit.ts @@ -1,4 +1,5 @@ import { rateLimit, type RateLimitRequestHandler } from 'express-rate-limit' +import { publicErrorBodies } from './errors.js' export type RateLimitPolicy = { windowMs: number @@ -19,7 +20,7 @@ export function createRateLimiter(policy: RateLimitPolicy): RateLimitRequestHand limit: policy.limit, standardHeaders: 'draft-8', legacyHeaders: false, - message: { error: 'Too many requests. Please try again later.' } + message: publicErrorBodies.rateLimited }) } diff --git a/test/downloadAdmission.test.ts b/test/downloadAdmission.test.ts index 31c8bcf..472ec3e 100644 --- a/test/downloadAdmission.test.ts +++ b/test/downloadAdmission.test.ts @@ -42,6 +42,10 @@ describe('download concurrency admission', () => { const rejected = await fetch(`${url}/file`) assert.equal(rejected.status, 429) assert.equal(rejected.headers.get('retry-after'), '5') + assert.deepEqual(await rejected.json(), { + error: 'Too many concurrent downloads. Please try again later.', + code: 'download_concurrency' + }) finishFirst() assert.equal((await first).status, 200) @@ -89,7 +93,13 @@ describe('per-client download share', () => { const held = fetch(`${url}/file`) while (release === undefined) await new Promise((resolve) => setImmediate(resolve)) - assert.equal((await fetch(`${url}/file`)).status, 429) + const refused = await fetch(`${url}/file`) + assert.equal(refused.status, 429) + assert.equal(refused.headers.get('retry-after'), '5') + assert.deepEqual(await refused.json(), { + error: 'Too many concurrent downloads from this client. Please try again later.', + code: 'download_client_concurrency' + }) // A different address is unaffected: the global limiter still has room. client = 'second' diff --git a/test/downloadResponse.test.ts b/test/downloadResponse.test.ts index 3824722..1702cbc 100644 --- a/test/downloadResponse.test.ts +++ b/test/downloadResponse.test.ts @@ -44,7 +44,10 @@ describe('download response streaming', () => { assert.equal(response.status, 408) assert.match(response.headers.get('content-type') ?? '', /^application\/json/) assert.equal(response.headers.get('content-disposition'), null) - assert.deepEqual(await response.json(), { error: 'File request timed out' }) + assert.deepEqual(await response.json(), { + error: 'File request timed out', + code: 'file_timeout' + }) } finally { await server.close() } diff --git a/test/security.test.ts b/test/security.test.ts index 6344d50..8211643 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -19,7 +19,9 @@ describe('CORS origin policy', () => { const matches = createOriginMatcher([ 'https://adm.im', 'https://*.adamant.im', - 'http://localhost:8080' + 'http://localhost:8080', + 'app://.', + 'app://localhost' ]) it('accepts exact and wildcard subdomain origins', () => { @@ -29,17 +31,35 @@ describe('CORS origin policy', () => { assert.equal(matches('http://localhost:8080'), true) }) + it('accepts a configured desktop origin and no other app host', () => { + assert.equal(matches('app://.'), true) + assert.equal(matches('app://localhost'), true) + assert.equal(matches('APP://.'), true) + assert.equal(matches('app://bundle'), false) + assert.equal(matches('app://.evil.example'), false) + assert.equal(matches('app://./'), false) + assert.equal(matches('app://./index.html'), false) + assert.equal(matches('app://user@.'), false) + }) + it('rejects suffix confusion, paths, and unlisted schemes', () => { assert.equal(matches('https://adamant.im.evil.example'), false) assert.equal(matches('https://adamant.im'), false) assert.equal(matches('https://chat.adamant.im/path'), false) assert.equal(matches('http://chat.adamant.im'), false) + assert.equal(matches('file:///'), false) }) it('rejects invalid configured origin rules', () => { assert.throws(() => createOriginMatcher(['*'])) assert.throws(() => createOriginMatcher(['https://example.org/path'])) assert.throws(() => createOriginMatcher(['https://*example.org'])) + assert.throws(() => createOriginMatcher(['app://*'])) + assert.throws(() => createOriginMatcher(['app://*.localhost'])) + assert.throws(() => createOriginMatcher(['app://./'])) + assert.throws(() => createOriginMatcher(['APP://.'])) + assert.throws(() => createOriginMatcher(['file:///'])) + assert.throws(() => createOriginMatcher(['app://localhost:80'])) }) it('emits an allow-origin header only for an accepted browser origin', async () => { @@ -51,9 +71,61 @@ describe('CORS origin policy', () => { try { const allowed = await fetch(server.url, { headers: { origin: 'https://adm.im' } }) const rejected = await fetch(server.url, { headers: { origin: 'https://evil.example' } }) + const desktop = await fetch(server.url, { headers: { origin: 'app://.' } }) assert.equal(allowed.headers.get('access-control-allow-origin'), 'https://adm.im') assert.equal(rejected.headers.get('access-control-allow-origin'), null) + assert.equal(desktop.headers.get('access-control-allow-origin'), null) + } finally { + await server.close() + } + }) + + it('allows a configured app://. origin on info and upload, including preflight', async () => { + const app = express() + app.use( + cors({ + origin: createCorsOriginDelegate(['https://*.adamant.im', 'app://.']), + credentials: false, + methods: ['GET', 'POST'], + allowedHeaders: ['content-type', 'x-api-key'], + maxAge: 600 + }) + ) + app.get('/api/node/info', (req, res) => res.send({ ok: true })) + app.post('/api/file/upload', (req, res) => res.send({ ok: true })) + const server = await startServer(app) + + try { + const info = await fetch(`${server.url}/api/node/info`, { headers: { origin: 'app://.' } }) + const preflight = await fetch(`${server.url}/api/file/upload`, { + method: 'OPTIONS', + headers: { + origin: 'app://.', + 'access-control-request-method': 'POST', + 'access-control-request-headers': 'content-type' + } + }) + const upload = await fetch(`${server.url}/api/file/upload`, { + method: 'POST', + headers: { origin: 'app://.', 'content-type': 'text/plain' }, + body: 'desktop' + }) + const otherHost = await fetch(`${server.url}/api/node/info`, { + headers: { origin: 'app://localhost' } + }) + const wildcard = await fetch(`${server.url}/api/node/info`, { + headers: { origin: 'https://msg.adamant.im' } + }) + + assert.equal(info.status, 200) + assert.equal(info.headers.get('access-control-allow-origin'), 'app://.') + assert.equal(preflight.headers.get('access-control-allow-origin'), 'app://.') + assert.match(preflight.headers.get('access-control-allow-methods') ?? '', /POST/) + assert.equal(upload.status, 200) + assert.equal(upload.headers.get('access-control-allow-origin'), 'app://.') + assert.equal(otherHost.headers.get('access-control-allow-origin'), null) + assert.equal(wildcard.headers.get('access-control-allow-origin'), 'https://msg.adamant.im') } finally { await server.close() } @@ -72,6 +144,12 @@ describe('security configuration', () => { it('accepts a fail-closed default configuration', () => { assert.doesNotThrow(() => validateSecurityConfig(baseConfig)) + assert.doesNotThrow(() => + validateSecurityConfig({ + ...baseConfig, + cors: { allowedOrigins: ['https://adm.im', 'app://.'] } + }) + ) }) it('rejects unsafe proxy trust and placeholder admin keys', () => { @@ -111,7 +189,7 @@ describe('public error mapping', () => { it('preserves the public timeout response without exposing internal details', () => { assert.deepEqual(getPublicError(new FileNotFoundError('/private/path was not found')), { status: 408, - body: { error: 'File request timed out' } + body: { error: 'File request timed out', code: 'file_timeout' } }) }) @@ -128,7 +206,7 @@ describe('public error mapping', () => { it('reports an active lifecycle without exposing transaction details', () => { assert.deepEqual(getPublicError(new FileLifecycleBusyError('secret-cid')), { status: 409, - body: { error: 'File lifecycle is busy' } + body: { error: 'File lifecycle is busy', code: 'lifecycle_busy' } }) }) }) @@ -169,7 +247,10 @@ describe('streaming multipart limits', () => { const response = await sendFiles(serverUrl, 1, true) assert.equal(response.status, 400) - assert.deepEqual(await response.json(), { error: 'Multipart fields are not allowed' }) + assert.deepEqual(await response.json(), { + error: 'Multipart fields are not allowed', + code: 'multipart_fields' + }) }) }) @@ -345,7 +426,14 @@ describe('rate limiting behind a trusted proxy', () => { const secondClient = { 'x-forwarded-for': '198.51.100.11' } assert.equal((await fetch(`${serverUrl}/upload`, { headers: firstClient })).status, 200) - assert.equal((await fetch(`${serverUrl}/upload`, { headers: firstClient })).status, 429) + const limited = await fetch(`${serverUrl}/upload`, { headers: firstClient }) + assert.equal(limited.status, 429) + assert.deepEqual(await limited.json(), { + error: 'Too many requests. Please try again later.', + code: 'rate_limited' + }) + assert.ok(Number(limited.headers.get('retry-after')) >= 1) + assert.match(limited.headers.get('ratelimit') ?? '', /r=0/) assert.equal((await fetch(`${serverUrl}/upload`, { headers: secondClient })).status, 200) }) }) diff --git a/test/uploadAdmission.test.ts b/test/uploadAdmission.test.ts new file mode 100644 index 0000000..4ea723d --- /dev/null +++ b/test/uploadAdmission.test.ts @@ -0,0 +1,113 @@ +import assert from 'node:assert/strict' +import type { AddressInfo } from 'node:net' +import { describe, it } from 'node:test' +import express, { type Express } from 'express' +import type { Blockstore } from 'interface-blockstore' +import type { CID } from 'multiformats/cid' +import { + createUploadAdmission, + type UploadAdmissionDependencies +} from '../src/middleware/uploadAdmission.js' +import { resetClaims } from '../src/storage/reservation.js' + +describe('upload admission diagnostics', () => { + it('names a concurrency refusal separately from the rate limiter', async () => { + const app = express() + app.post('/upload', createUploadAdmission(dependencies(() => false)), (req, res) => + res.send({ ok: true }) + ) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/upload`, { method: 'POST' }) + + assert.equal(response.status, 429) + assert.equal(response.headers.get('retry-after'), '5') + assert.deepEqual(await response.json(), { + error: 'Too many concurrent uploads. Please try again later.', + code: 'upload_concurrency' + }) + } finally { + await server.close() + } + }) + + it('names a request that declares more than the aggregate limit', async () => { + const app = express() + app.post('/upload', createUploadAdmission(dependencies(() => true)), (req, res) => + res.send({ ok: true }) + ) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/upload`, { + method: 'POST', + headers: { 'content-type': 'application/octet-stream' }, + body: 'x'.repeat(2048) + }) + + assert.equal(response.status, 413) + assert.deepEqual(await response.json(), { + error: 'Upload size limit exceeded', + code: 'request_too_large' + }) + } finally { + await server.close() + } + }) + + it('names a request that would consume the disk reserve', async () => { + resetClaims() + const app = express() + app.post('/upload', createUploadAdmission(dependencies(() => true)), (req, res) => + res.send({ ok: true }) + ) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/upload`, { + method: 'POST', + headers: { 'content-type': 'application/octet-stream' }, + body: 'x'.repeat(32) + }) + + assert.equal(response.status, 507) + assert.deepEqual(await response.json(), { + error: 'Insufficient storage', + code: 'insufficient_storage' + }) + } finally { + resetClaims() + await server.close() + } + }) +}) + +function dependencies(tryAcquire: () => boolean): UploadAdmissionDependencies { + return { + storage: { maxRequestSizeBytes: 1024, diskReserveBytes: 10_000 }, + limiter: { tryAcquire, release() {} }, + operationLock: { acquireShared: async () => ({ release() {} }) }, + availableStorageSize: async () => 100n, + blockstore: {} as Blockstore, + isPinned: async () => false, + deleteBlock: async () => undefined, + parseCid: (cid: string) => cid as unknown as CID, + log: { info() {}, warn() {}, error() {} } + } +} + +async function startServer(app: Express): Promise<{ url: string; close: () => Promise }> { + const server = await new Promise>((resolve) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + }) + const address = server.address() as AddressInfo + + return { + url: `http://127.0.0.1:${address.port}`, + close: () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + } +} From a030bd36b14295d7817c05e43cc475395eff1743 Mon Sep 17 00:00:00 2001 From: metalisk Date: Mon, 28 Sep 2026 17:05:31 +0300 Subject: [PATCH 162/167] docs: clarify CORS configuration and proxy trust settings in documentation --- config.default.json5 | 11 ++++--- docker/config.example.json5 | 22 ++++++++------ docs/guide/configuration.md | 17 ++++++----- docs/guide/docker.md | 12 +++++--- docs/guide/installation.md | 10 +++--- docs/guide/security.md | 22 +++++++++----- docs/operations/monitoring.md | 5 +-- docs/operations/troubleshooting.md | 3 +- docs/reference/api.md | 10 +++--- test/config.test.ts | 49 ++++++++++++++++++++++++++++++ test/uploadAdmission.test.ts | 27 ++++++++++++++++ 11 files changed, 142 insertions(+), 46 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 7800092..df3a111 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -41,7 +41,8 @@ // Exact HTTP(S) origins and left-most subdomain wildcards are supported. // A desktop client that loads the UI from Electron's `app` scheme sends // `Origin: app://.` (some builds send `app://localhost` or `app://`). - // Uncomment the matching entry only for a node that serves that client. + // Uncomment the matching entry, including its comma, only for a node that + // serves that client. // A web page cannot forge `app://`; any local app that registered the // scheme can. `*` and `file:` are rejected. allowedOrigins: [ @@ -49,16 +50,16 @@ 'https://adm.im', 'https://*.vercel.app', 'https://*.surge.sh', + // 'app://.', 'http://localhost:8080' - // 'app://.' ] }, // Keep false when clients connect to this process directly. - // Behind one reverse proxy that overwrites X-Forwarded-For, use hop count 1 - // or the proxy's own addresses, for example: + // One reverse proxy that overwrites X-Forwarded-For, on every path: // trustProxy: 1 + // The proxy connects from loopback. That is nginx beside pm2 or systemd. + // It is not the socket a container sees for a host-published port: // trustProxy: ['127.0.0.1/8', '::1/128'] - // The same choice applies on every environment that uses that topology. // `true` is rejected. Leaving this false behind a proxy makes every client // share the proxy address for rate limits. trustProxy: false, diff --git a/docker/config.example.json5 b/docker/config.example.json5 index 0c21dfa..892f87f 100644 --- a/docker/config.example.json5 +++ b/docker/config.example.json5 @@ -48,27 +48,29 @@ // // Electron and other custom-scheme desktops send a non-HTTP Origin. The // official ADAMANT desktop build sends `app://.`. Other builds may send - // `app://localhost` or `app://`. Uncomment the exact value only when - // this node should answer those clients. A web page cannot forge `app://`; + // `app://localhost` or `app://`. Uncomment the exact value, including + // its comma, only when this node should answer those clients. A web page + // cannot forge `app://`; // any local app that registered the scheme can. Wildcards are not accepted // for that scheme. allowedOrigins: [ + // 'app://.', 'http://localhost:8080' - // 'app://.' ] }, // false is correct when clients connect to this process directly. The // container smoke test does that, and it does not cover a proxied deployment. // - // Behind one reverse proxy that overwrites X-Forwarded-For — host nginx in - // front of a loopback publish (`127.0.0.1:4000:4000`), or nginx in front of - // a pm2 or systemd process — set the hop count, or trust only the proxy: + // Host nginx in front of a published port is one hop and must overwrite + // X-Forwarded-For. The socket inside the container is the bridge gateway, + // not the host loopback, even when the port is published on 127.0.0.1: // trustProxy: 1 - // trustProxy: ['127.0.0.1/8', '::1/128'] - // Use the same setting on every environment with that topology. `true` is - // rejected. Leaving this false behind a proxy puts every client in one - // rate-limit bucket, the proxy's own address. + // or the address this process actually sees for that proxy connection. + // ['127.0.0.1/8', '::1/128'] is for a process that accepts the proxy from + // loopback, such as pm2 or systemd beside nginx. It does not match this + // container. `true` is rejected. Leaving this false shares one rate-limit + // bucket, the proxy's own address. trustProxy: false, rateLimits: { diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 5da5163..beca200 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -196,11 +196,13 @@ The choice is the same whether operators call the deployment production or a tes matters is the path in front of the process: - Clients connect straight to `serverPort`, and nothing adds `X-Forwarded-For`. Keep `false`. -- One reverse proxy overwrites `X-Forwarded-For` and is the only hop. Use `1`. That covers host - nginx in front of Docker published as `127.0.0.1:4000:4000`, and nginx on the same machine as a - PM2 or systemd process. The nginx config is in [Installation](/guide/installation). -- Proxy connections come from loopback and a hop count would be wider than the socket you want to - trust. Use `['127.0.0.1/8', '::1/128']`, or the single address of that proxy. +- One reverse proxy overwrites `X-Forwarded-For` and every path crosses that single hop. Use `1`. + That covers host nginx in front of a container and nginx on the same machine as a PM2 or systemd + process. The nginx config is in [Installation](/guide/installation). +- The proxy connects from loopback. Use `['127.0.0.1/8', '::1/128']`. That is nginx beside a PM2 or + systemd process. A container published on the host's `127.0.0.1` still sees the bridge gateway, so + this list does not match that socket. Use `1`, or the address observed inside the container. See + [Docker](/guide/docker). - More than one trusted proxy appends a sanitized forwarding header. Set the hop count to that number, and only if every path crosses exactly those hops. @@ -483,11 +485,12 @@ its node reports at `GET /api/node/details`, and use the reachable address of th cors: { // Replace with the origins of your own application. - // Uncomment 'app://.' only for a desktop client that sends that origin. + // Uncomment 'app://.', including its comma, only for a desktop client that + // sends that origin. allowedOrigins: [ 'https://app.example.org', + // 'app://.', 'https://*.example.org' - // 'app://.' ] }, diff --git a/docs/guide/docker.md b/docs/guide/docker.md index 3b72d7f..334e16d 100644 --- a/docs/guide/docker.md +++ b/docs/guide/docker.md @@ -182,11 +182,13 @@ restore procedures are in [Persistence](/operations/persistence). Both ports are declared with `EXPOSE`; publishing them is the operator's decision. -- Bind port 4000 to loopback and put a TLS-terminating reverse proxy in front of it. The image - contains no TLS, the process serves plain HTTP, and it logs a warning about that at startup. - With one such proxy, set `trustProxy: 1` or `trustProxy: ['127.0.0.1/8', '::1/128']` in the - mounted `config.json5`. The image does not bake origins or proxy trust in; `config.default.json5` - inside the image is a template and is never selected automatically +- Bind port 4000 to loopback on the host and put a TLS-terminating reverse proxy in front of it. + The image contains no TLS, the process serves plain HTTP, and it logs a warning about that at + startup. Publishing `127.0.0.1:4000:4000` does not make the accepted socket loopback inside the + container. With one proxy that overwrites `X-Forwarded-For`, set `trustProxy: 1`, or set the + proxy address the process observes, which is the bridge gateway. `127.0.0.1/8` does not match + that socket. The image does not bake origins or proxy trust in; `config.default.json5` inside + the image is a template and is never selected automatically - Port 4001 has to be reachable by every peer listed in `nodes`, otherwise replication and health attestations cannot be exchanged - Upload and download routes are public by design, and rate limits and size limits are the only diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 4aa3fe0..48a147c 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -273,11 +273,11 @@ trustProxy: 1 trustProxy: ['127.0.0.1/8', '::1/128'] ``` -Use `1` when every request crosses exactly this one proxy, including a PM2 or systemd process on -the same host with nginx in front. Use the address list when you would rather trust the loopback -socket than a hop count. The same pair of choices applies to a test network and to a public -deployment: the label does not change the header the process sees. Docker published only on -loopback uses this same nginx. The container side is in [Docker](/guide/docker). +Use `1` when every request crosses exactly this one proxy. Use the loopback list when this process +accepts the proxy from loopback, which is a PM2 or systemd process on the same host. A container +does not: a port published on the host's `127.0.0.1` is still the bridge gateway inside the +container, and that list would share one rate-limit bucket. The container setting is in +[Docker](/guide/docker). The same distinction applies on a test network and on a public deployment. A numeric hop count is accepted only for a fixed topology where every path to the application crosses exactly that many trusted hops. The blanket value `true` is rejected, because a client diff --git a/docs/guide/security.md b/docs/guide/security.md index 9b54396..a3dc8a5 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -123,13 +123,18 @@ terminate TLS at nginx and forward to this process need the same `trustProxy` va the name of the network changes how Express reads `X-Forwarded-For`. Direct access to the process port, with no forwarding headers, keeps `false`. -One proxy hop that overwrites `X-Forwarded-For` is hop count `1`. That is the usual shape for host -nginx in front of a container published on `127.0.0.1:4000:4000`, and for nginx on the same machine -as a [PM2 or systemd](/guide/installation) process. An address list such as -`['127.0.0.1/8', '::1/128']` trusts the socket instead of counting hops, which is the tighter choice -when every proxy connection comes from loopback. Use a hop count only when every path to the process -crosses exactly that many trusted proxies. The nginx snippet is in -[Installation](/guide/installation), and the container publish is in [Docker](/guide/docker). +One proxy hop that overwrites `X-Forwarded-For`, on every path, is hop count `1`. That fits host +nginx in front of a container and nginx on the same machine as a +[PM2 or systemd](/guide/installation) process. Use a hop count only for a fixed topology where every +path crosses exactly that many trusted proxies. + +An address list trusts the socket instead of counting hops. `['127.0.0.1/8', '::1/128']` is that +list when the proxy connection arrives from loopback, which is nginx beside a PM2 or systemd +process. It is not the address a container sees. Publishing the host port on `127.0.0.1` still +presents the bridge gateway as the socket inside the container, so that list puts every forwarded +client in one bucket. For the container, use hop count `1` or the gateway address the process +observes. The nginx snippet is in [Installation](/guide/installation), and the container publish is +in [Docker](/guide/docker). ## Administrative API key @@ -232,6 +237,9 @@ Rate limiters count requests per client address in a fixed window, using `expres `draft-8` standard headers and legacy headers disabled. A window refusal is `429` with `{"error":"Too many requests. Please try again later.","code":"rate_limited"}`, a `Retry-After` header set to the seconds remaining in the window, and `RateLimit` / `RateLimit-Policy` headers. +The limiter writes those rate-window headers on every request it handles, including one it allows +through. Upload and download routes run it before admission, so a later admission `429` can still +carry them. Identify the refusal by `code`. | Limiter | Option | Default | Routes | | ------- | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | diff --git a/docs/operations/monitoring.md b/docs/operations/monitoring.md index 628be24..e5b4f4a 100644 --- a/docs/operations/monitoring.md +++ b/docs/operations/monitoring.md @@ -189,8 +189,9 @@ placeholders. Two warnings are expected on a fresh installation and are not faults: the TLS notice at listen time, and the `trustProxy is false` notice. Do not widen the proxy trust rule merely to silence the second; see [Security and privacy](/guide/security). Silence it when a reverse proxy really is in -front, on a test network the same way as on a public one, by setting hop count `1` or the proxy's -addresses. +front, on a test network the same way as on a public one, by setting hop count `1` when that is +the only proxy, or the address this process actually accepts the proxy from. A container's socket +is the bridge gateway, not the host loopback it was published on. Health probes and browser clients are not the same request. `GET /api/node/health` from Zabbix, the container health check, or `curl` sends no `Origin` header, so CORS does not apply and a `ready` diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index f0f1bf2..9a05ddb 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -206,7 +206,8 @@ multi-process deployment. **A `429` with no obvious rate abuse.** Read `code`. `rate_limited` is the window and carries `Retry-After` plus `RateLimit` headers. `upload_concurrency`, `download_concurrency`, and `download_client_concurrency` are admission: -`Retry-After: 5`, and `GET /api/node/details` shows occupancy under `concurrency`. An empty +`Retry-After: 5`, and `GET /api/node/details` shows occupancy under `concurrency`. The window +limiter runs first, so its `RateLimit` headers can still be present on an admission `429`. An empty limiter with `rate_limited` from many users at once is a shared client address; see the upload section above. diff --git a/docs/reference/api.md b/docs/reference/api.md index a699b4d..b6f4759 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -325,10 +325,12 @@ authentication failures, and `500` `Internal Server Error`. | `replication_quorum` | `503` | `requireQuorumOnUpload` rejected the upload | | `insufficient_storage` | `507` | Admitting the upload would consume `storage.diskReserveBytes` | -A `429` with `code` `rate_limited` also sends `Retry-After` (seconds left in the window) and the -draft-8 `RateLimit` and `RateLimit-Policy` headers. An admission `429` sends `Retry-After: 5` and -does not send `RateLimit`. `x-powered-by` is disabled, and an unmatched path answers `404` from the -not-found handler. +A `429` with `code` `rate_limited` is the window limiter. It sets `Retry-After` to the seconds left +in the window and sends draft-8 `RateLimit` and `RateLimit-Policy` headers. An admission `429` sets +`Retry-After: 5` and does not add its own rate-window headers. `POST /api/file/upload` and +`GET /api/file/:cid` run the window limiter first, so an admission refusal can still carry the +`RateLimit` headers that limiter already wrote. Identify the refusal by `code`. `x-powered-by` is +disabled, and an unmatched path answers `404` from the not-found handler. ## Machine-readable specification diff --git a/test/config.test.ts b/test/config.test.ts index 6f1efec..1adde5d 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -1,6 +1,11 @@ import assert from 'node:assert/strict' +import { existsSync, readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' import { describe, it } from 'node:test' +import { fileURLToPath } from 'node:url' +import JSON5 from 'json5' import { ConfigError, config, configFileName, validateConfig } from '../src/config.js' +import { createOriginMatcher } from '../src/security/cors.js' const PEER_ID = '12D3KooWSUCe86zWfas1Lo1UQzXzquZgS81d1DpPPYAuTNjSyniq' @@ -233,3 +238,47 @@ describe('the config loaded at startup', () => { assert.equal(config.enableDebugApi, false) }) }) + +describe('commented desktop CORS examples', () => { + it('parses each example after the desktop origin is uncommented', () => { + const root = repoRoot() + const sources = [ + readFileSync(join(root, 'config.default.json5'), 'utf8'), + readFileSync(join(root, 'docker/config.example.json5'), 'utf8'), + json5Fence(readFileSync(join(root, 'docs/guide/configuration.md'), 'utf8'), "// 'app://.'") + ] + + for (const source of sources) { + assert.match(source, /\/\/ 'app:\/\/\.'/) + const parsed = JSON5.parse(source.replace("// 'app://.',", "'app://.',")) as { + cors: { allowedOrigins: string[] } + } + + assert.ok(parsed.cors.allowedOrigins.includes('app://.')) + assert.equal(createOriginMatcher(parsed.cors.allowedOrigins)('app://.'), true) + } + }) +}) + +function repoRoot(): string { + let dir = dirname(fileURLToPath(import.meta.url)) + for (;;) { + if (existsSync(join(dir, 'package.json'))) { + return dir + } + const parent = dirname(dir) + if (parent === dir) { + throw new Error('Cannot locate the repository root') + } + dir = parent + } +} + +function json5Fence(markdown: string, needle: string): string { + for (const match of markdown.matchAll(/```json5\n([\s\S]*?)```/g)) { + if (match[1].includes(needle)) { + return match[1] + } + } + throw new Error(`No json5 example contains ${needle}`) +} diff --git a/test/uploadAdmission.test.ts b/test/uploadAdmission.test.ts index 4ea723d..17e21bc 100644 --- a/test/uploadAdmission.test.ts +++ b/test/uploadAdmission.test.ts @@ -8,9 +8,36 @@ import { createUploadAdmission, type UploadAdmissionDependencies } from '../src/middleware/uploadAdmission.js' +import { createRateLimiter } from '../src/security/rateLimit.js' import { resetClaims } from '../src/storage/reservation.js' describe('upload admission diagnostics', () => { + it('keeps the window RateLimit headers on a later admission refusal', async () => { + const app = express() + app.post( + '/upload', + createRateLimiter({ windowMs: 60_000, limit: 10 }), + createUploadAdmission(dependencies(() => false)), + (req, res) => res.send({ ok: true }) + ) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/upload`, { method: 'POST' }) + + assert.equal(response.status, 429) + assert.equal(response.headers.get('retry-after'), '5') + assert.deepEqual(await response.json(), { + error: 'Too many concurrent uploads. Please try again later.', + code: 'upload_concurrency' + }) + assert.match(response.headers.get('ratelimit') ?? '', /r=9/) + assert.ok(response.headers.get('ratelimit-policy')) + } finally { + await server.close() + } + }) + it('names a concurrency refusal separately from the rate limiter', async () => { const app = express() app.post('/upload', createUploadAdmission(dependencies(() => false)), (req, res) => From ba7dea23397a3e5c48193cdfb297060fd9d71f3d Mon Sep 17 00:00:00 2001 From: metalisk Date: Mon, 28 Sep 2026 18:28:20 +0300 Subject: [PATCH 163/167] feat: enhance CORS configuration to support Tor hidden services and opaque browser origins --- config.default.json5 | 11 +++++++++ docs/guide/adamant-messenger.md | 8 ++++-- docs/guide/configuration.md | 6 ++++- docs/guide/security.md | 3 +++ src/security/cors.ts | 43 ++++++++++++++++++++++++++++++++- test/security.test.ts | 42 ++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 4 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index df3a111..38c250d 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -47,9 +47,20 @@ // scheme can. `*` and `file:` are rejected. allowedOrigins: [ 'https://*.adamant.im', + // HTTP mirrors such as msg-testnet.adamant.im (Surge); HTTPS subdomains use the rule above. + 'http://*.adamant.im', 'https://adm.im', 'https://*.vercel.app', 'https://*.surge.sh', + // GitHub Pages builds, including user forks from repository Actions. + 'https://*.github.io', + // Massa DeWeb PWA hosts; add your own frontend origins explicitly. + 'https://adm.massahub.network', + 'https://adm.deweb.half-red.net', + // Tor: one wildcard for v3 hidden services, or list exact PWA / site onions. + // 'http://*.onion', + // Tor Browser may send `Origin: null` for cross-onion API calls; opt in explicitly: + // 'null', // 'app://.', 'http://localhost:8080' ] diff --git a/docs/guide/adamant-messenger.md b/docs/guide/adamant-messenger.md index 52e52ae..e305eef 100644 --- a/docs/guide/adamant-messenger.md +++ b/docs/guide/adamant-messenger.md @@ -74,8 +74,12 @@ entries are ADAMANT web origins (`https://*.adamant.im` and the compatibility ho Those web origins are one adopter's set, not a required list. The Electron build of the same client loads from the `app` scheme and sends `Origin: app://.`. That entry is commented in the template until an operator enables it. A PWA on `https://msg.adamant.im` is already covered by the wildcard -and does not need the desktop origin. Another application lists its own origins and, if it has a -desktop shell, the exact custom-scheme origin that shell sends. +and does not need the desktop origin. The Tor PWA is served from its own `.onion` hidden service; +add `http://*.onion` on nodes that Tor clients call, or list each PWA onion exactly. Some Tor +Browser builds send `Origin: null` for cross-`.onion` API calls instead of the PWA onion; that +requires the separate `null` entry documented in [Security](/guide/security). Another application +lists its own origins and, if it has a desktop shell, the exact custom-scheme origin that shell +sends. Start at [Quick start](/guide/quick-start), or [Docker](/guide/docker) for a container. The messenger client itself is developed in diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index beca200..08717c4 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -128,7 +128,11 @@ Entry rules, enforced when the matcher is compiled: equal the parsed origin exactly. - A wildcard has the form `https://*.example.org`, optionally with a port. The suffix hostname must contain a dot, must not start or end with a dot, must not contain `..`, and must be at most 253 - characters. A port above 65535 is rejected. + characters. A port above 65535 is rejected. The sole exception is `http://*.onion` (or + `https://*.onion`): the suffix label is `onion`, and only origins whose hostname matches a v3 + onion shape (`<56-character base32 label>.onion`, with optional subdomain labels) are accepted. +- The opaque browser origin is configured as the literal string `null` when clients send + `Origin: null` (some Tor Browser cross-`.onion` requests). - An exact desktop origin is `app://.` or `app://`. The host is either the single dot Chromium uses for `app://./…`, or a lowercase hostname (`localhost`, `bundle`, `files.example.org`). No wildcard, port, userinfo, path, query, or fragment. `APP://.` and `app://./` are rejected; write diff --git a/docs/guide/security.md b/docs/guide/security.md index a3dc8a5..dea76cb 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -179,6 +179,9 @@ Operational notes: - a canonical HTTP or HTTPS origin, such as `https://adm.im` or `http://localhost:8080` - an any-depth subdomain wildcard of the form `https://*.adamant.im` - an exact desktop origin, `app://.` or `app://` +- the opaque browser origin, configured as the literal string `null`, when clients send + `Origin: null` (Tor Browser does this for some cross-`.onion` API calls) +- a Tor hidden-service wildcard `http://*.onion` (suffix label `onion`; v3 hostname shape only) Startup rejects a bare `*`, an entry carrying a path, credentials, query string, or fragment, a scheme other than `http`, `https`, or exact `app`, an entry longer than 255 characters, a wildcard diff --git a/src/security/cors.ts b/src/security/cors.ts index f4591ad..a80fc45 100644 --- a/src/security/cors.ts +++ b/src/security/cors.ts @@ -16,11 +16,19 @@ type OriginRule = { const APP_ORIGIN = /^app:\/\/(?:\.|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*)$/ +/** v3 onion service names: optional subdomain labels, then a 56-character base32 label. */ +const ONION_V3_ORIGIN_HOST = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z2-7]{56}\.onion$/ + /** * Compile a list of exact origins and any-depth subdomain suffix wildcards. * HTTP(S) wildcards use the form `https://*.example.org`; paths, credentials, * query strings, fragments, and the suffix origin itself are rejected. * Desktop clients opt in with an exact `app://.` or `app://` entry. + * Tor hidden services use `http://*.onion` (suffix `onion` is the only wildcard label without an + * interior dot). Matching requires a v3 onion hostname shape, not merely any string ending in + * `.onion`. + * Tor Browser may send `Origin: null` for cross-`.onion` fetches; opt in with the exact entry + * `null` (the four-character string, not JSON null). * `file:`, `data:`, `blob:`, and a bare `*` stay rejected. * * @param allowedOrigins browser origins accepted by the API @@ -34,6 +42,10 @@ export function createOriginMatcher(allowedOrigins: unknown): (origin: string) = const rules = allowedOrigins.map(parseOriginRule) return (origin: string): boolean => { + if (origin === 'null') { + return rules.some((rule) => rule.protocol === 'null:') + } + let parsed: URL try { parsed = new URL(origin) @@ -70,6 +82,10 @@ export function createOriginMatcher(allowedOrigins: unknown): (origin: string) = return parsed.hostname === rule.hostname } + if (rule.hostname === 'onion') { + return matchesOnionV3OriginHost(parsed.hostname) + } + return parsed.hostname !== rule.hostname && parsed.hostname.endsWith(`.${rule.hostname}`) }) } @@ -95,6 +111,15 @@ function parseOriginRule(value: unknown): OriginRule { throw new Error('Each CORS origin must be a non-empty string of at most 255 characters') } + if (value === 'null') { + return { + protocol: 'null:', + hostname: '', + port: '', + wildcard: false + } + } + if (/^app:/i.test(value)) { return parseAppOrigin(value) } @@ -102,7 +127,7 @@ function parseOriginRule(value: unknown): OriginRule { const wildcardMatch = /^(https?):\/\/\*\.([a-z0-9.-]+)(?::([0-9]{1,5}))?$/i.exec(value) if (wildcardMatch) { const [, protocol, hostname, port = ''] = wildcardMatch - validateHostname(hostname) + validateWildcardSuffix(hostname) validatePort(port) return { protocol: `${protocol.toLowerCase()}:`, @@ -191,6 +216,22 @@ function validateHostname(hostname: string): void { } } +/** + * Wildcard suffix for `http://*.onion` uses the label `onion` alone; every other suffix still + * needs an interior dot. + */ +function validateWildcardSuffix(hostname: string): void { + if (hostname === 'onion') { + return + } + + validateHostname(hostname) +} + +function matchesOnionV3OriginHost(hostname: string): boolean { + return ONION_V3_ORIGIN_HOST.test(hostname.toLowerCase()) +} + function validatePort(port: string): void { if (port && Number(port) > 65535) { throw new Error(`Invalid CORS port: ${port}`) diff --git a/test/security.test.ts b/test/security.test.ts index 8211643..5c26a4d 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -48,6 +48,34 @@ describe('CORS origin policy', () => { assert.equal(matches('https://chat.adamant.im/path'), false) assert.equal(matches('http://chat.adamant.im'), false) assert.equal(matches('file:///'), false) + assert.equal(matches('null'), false) + }) + + it('accepts the opaque browser origin when null is configured', () => { + const opaque = createOriginMatcher(['https://adm.im', 'null']) + assert.equal(opaque('null'), true) + assert.equal(opaque('https://adm.im'), true) + assert.equal(opaque('https://evil.example'), false) + }) + + it('accepts http://*.onion for v3 hidden-service origins', () => { + const tor = createOriginMatcher(['http://*.onion']) + const pwa = 'http://adamant6457join2rxdkr2y7iqatar7n4n72lordxeknj435i4cjhpyd.onion' + const ipfs = 'http://z455rax4mwcseyc7efog7czrbwdvphwocatl5sjcc6htcoj2k2vz7dad.onion' + + assert.equal(tor(pwa), true) + assert.equal(tor(ipfs), true) + assert.equal(tor('http://short.onion'), false) + assert.equal( + tor('https://z455rax4mwcseyc7efog7czrbwdvphwocatl5sjcc6htcoj2k2vz7dad.onion'), + false + ) + assert.equal(tor('http://notadamant.im'), false) + }) + + it('parses http://*.onion at startup', () => { + assert.doesNotThrow(() => createOriginMatcher(['http://*.onion', 'https://*.onion'])) + assert.throws(() => createOriginMatcher(['http://*..onion'])) }) it('rejects invalid configured origin rules', () => { @@ -81,6 +109,20 @@ describe('CORS origin policy', () => { } }) + it('reflects Access-Control-Allow-Origin: null for the opaque origin', async () => { + const app = express() + app.use(cors({ origin: createCorsOriginDelegate(['null']) })) + app.get('/api/node/info', (req, res) => res.send({ ok: true })) + const server = await startServer(app) + + try { + const response = await fetch(`${server.url}/api/node/info`, { headers: { origin: 'null' } }) + assert.equal(response.headers.get('access-control-allow-origin'), 'null') + } finally { + await server.close() + } + }) + it('allows a configured app://. origin on info and upload, including preflight', async () => { const app = express() app.use( From d0e82e801ef5aef361d58a5f43ad261271d587a5 Mon Sep 17 00:00:00 2001 From: metalisk Date: Mon, 28 Sep 2026 18:43:40 +0300 Subject: [PATCH 164/167] docs: update CORS configuration documentation to clarify implications of opaque `null` origin --- config.default.json5 | 3 ++- docs/guide/configuration.md | 5 ++++- docs/guide/security.md | 22 ++++++++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/config.default.json5 b/config.default.json5 index 38c250d..392cf57 100644 --- a/config.default.json5 +++ b/config.default.json5 @@ -59,7 +59,8 @@ 'https://adm.deweb.half-red.net', // Tor: one wildcard for v3 hidden services, or list exact PWA / site onions. // 'http://*.onion', - // Tor Browser may send `Origin: null` for cross-onion API calls; opt in explicitly: + // Opaque origin `null` (literal string): Tor cross-onion, but also ANY sandboxed iframe, + // data:, and some file: documents — not Tor-specific. See docs/guide/security.md. // 'null', // 'app://.', 'http://localhost:8080' diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 08717c4..d2df84d 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -132,7 +132,10 @@ Entry rules, enforced when the matcher is compiled: `https://*.onion`): the suffix label is `onion`, and only origins whose hostname matches a v3 onion shape (`<56-character base32 label>.onion`, with optional subdomain labels) are accepted. - The opaque browser origin is configured as the literal string `null` when clients send - `Origin: null` (some Tor Browser cross-`.onion` requests). + `Origin: null` (some Tor Browser cross-`.onion` requests). **Warning:** this entry allows + **every** opaque origin, not only Tor: sandboxed iframes, `data:` pages, and some `file:` pages + also send `Origin: null`, and the matcher cannot restrict `null` to a specific client. See + [Security](/guide/security#opaque-null-origin). - An exact desktop origin is `app://.` or `app://`. The host is either the single dot Chromium uses for `app://./…`, or a lowercase hostname (`localhost`, `bundle`, `files.example.org`). No wildcard, port, userinfo, path, query, or fragment. `APP://.` and `app://./` are rejected; write diff --git a/docs/guide/security.md b/docs/guide/security.md index dea76cb..71db9f9 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -179,8 +179,7 @@ Operational notes: - a canonical HTTP or HTTPS origin, such as `https://adm.im` or `http://localhost:8080` - an any-depth subdomain wildcard of the form `https://*.adamant.im` - an exact desktop origin, `app://.` or `app://` -- the opaque browser origin, configured as the literal string `null`, when clients send - `Origin: null` (Tor Browser does this for some cross-`.onion` API calls) +- the opaque browser origin, configured as the literal string `null` (see below) - a Tor hidden-service wildcard `http://*.onion` (suffix label `onion`; v3 hostname shape only) Startup rejects a bare `*`, an entry carrying a path, credentials, query string, or fragment, a @@ -224,6 +223,25 @@ document that made the request, so allowing `app://.` does not let an arbitrary API. It does let any local application that registered the `app` scheme. That is why the entry is an explicit opt-in rather than a default. +### Opaque `null` origin + +Some browsers, including Tor Browser on certain cross-`.onion` fetches, send the header +`Origin: null`. The configuration entry is the literal four-character string `null`, not JSON +null. Enabling it tells the CORS middleware to reflect `Access-Control-Allow-Origin: null` for +those requests. + +**This permission is not limited to Tor.** Any opaque browser origin serializes as `null`. That +includes `data:` documents, some `file:` documents, and a cross-origin +`