From c3fea9e6062c3cab1e56ae52716b0d73309e6a65 Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Fri, 6 Jun 2025 12:37:43 +0200 Subject: [PATCH 1/7] feat: Add AWS S3 storage support with modular architecture - Implement StorageManager with automatic S3/local detection - Support S3 storage for assets, collections, and storage.json - Add CloudFront integration and URL handling fixes - Maintain full backward compatibility with local storage --- .env.example | 5 + package-lock.json | 4092 +++++++++++++++++++++-------- package.json | 4 + scripts/clean-world-s3.mjs | 192 ++ src/core/systems/ServerLoader.js | 147 +- src/core/systems/ServerNetwork.js | 2 +- src/server/CloudStorage.js | 72 + src/server/FileStorage.js | 203 ++ src/server/S3Storage.js | 502 ++++ src/server/StorageManager.js | 246 ++ src/server/collectionsManager.js | 267 ++ src/server/index.js | 104 +- 12 files changed, 4632 insertions(+), 1204 deletions(-) create mode 100644 scripts/clean-world-s3.mjs create mode 100644 src/server/CloudStorage.js create mode 100644 src/server/FileStorage.js create mode 100644 src/server/S3Storage.js create mode 100644 src/server/StorageManager.js create mode 100644 src/server/collectionsManager.js diff --git a/.env.example b/.env.example index 656d0648..f0fc51bd 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,11 @@ PUBLIC_API_URL=http://localhost:3000/api # The public url used by clients to fetch assets PUBLIC_ASSETS_URL=http://localhost:3000/assets +S3_BUCKET_NAME=bucket-name +AWS_ACCESS_KEY_ID=access-key-id +AWS_SECRET_ACCESS_KEY=secret-key-id +S3_REGION=eu-west-1 + # LiveKit (voice chat) LIVEKIT_WS_URL= LIVEKIT_API_KEY= diff --git a/package-lock.json b/package-lock.json index 3c0f7bb4..47471d07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,10 @@ "engines": { "node": "22.11.0", "npm": ">=10.0.0" + }, + "optionalDependencies": { + "@aws-sdk/client-s3": "^3.685.0", + "@aws-sdk/s3-request-presigner": "^3.685.0" } }, "node_modules/@ampproject/remapping": { @@ -74,1567 +78,3274 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.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" + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=14.0.0" } }, - "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==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/eslint-parser": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz", - "integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.1" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/eslint-parser/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, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", - "engines": { - "node": ">=10" + "optional": true, + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/eslint-parser/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, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/types": "^7.25.9" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.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==", - "dev": true, - "license": "ISC", - "peer": true, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "yallist": "^3.0.2" + "tslib": "^2.6.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==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/client-s3": { + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.824.0.tgz", + "integrity": "sha512-7neTQIdSVP/F4RTWG5T87LDpB955iQD6lxg9nJ00fdkIPczDcRtAEXow44NjF4fEdpQ1A9jokUtBSVE+GMXZ/A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/credential-provider-node": "3.823.0", + "@aws-sdk/middleware-bucket-endpoint": "3.821.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.823.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.823.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.824.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.823.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.823.0.tgz", + "integrity": "sha512-dBWdsbyGw8rPfdCsZySNtTOGQK4EZ8lxB/CneSQWRBPHgQ+Ys88NXxImO8xfWO7Itt1eh8O7UDTZ9+smcvw2pw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.823.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.823.0.tgz", + "integrity": "sha512-1Cf4w8J7wYexz0KU3zpaikHvldGXQEjFldHOhm0SBGRy7qfYNXecfJAamccF7RdgLxKGgkv5Pl9zX/Z/DcW9zg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.823.0.tgz", + "integrity": "sha512-AIrLLwumObge+U1klN4j5ToIozI+gE9NosENRyHe0GIIZgTLOG/8jxrMFVYFeNHs7RUtjDTxxewislhFyGxJ/w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.823.0.tgz", + "integrity": "sha512-u4DXvB/J/o2bcvP1JP6n3ch7V3/NngmiJFPsM0hKUyRlLuWM37HEDEdjPRs3/uL/soTxrEhWKTA9//YVkvzI0w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.823.0.tgz", + "integrity": "sha512-C0o63qviK5yFvjH9zKWAnCUBkssJoQ1A1XAHe0IAQkurzoNBSmu9oVemqwnKKHA4H6QrmusaEERfL00yohIkJA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/credential-provider-env": "3.823.0", + "@aws-sdk/credential-provider-http": "3.823.0", + "@aws-sdk/credential-provider-process": "3.823.0", + "@aws-sdk/credential-provider-sso": "3.823.0", + "@aws-sdk/credential-provider-web-identity": "3.823.0", + "@aws-sdk/nested-clients": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.823.0.tgz", + "integrity": "sha512-nfSxXVuZ+2GJDpVFlflNfh55Yb4BtDsXLGNssXF5YU6UgSPsi8j2YkaE92Jv2s7dlUK07l0vRpLyPuXMaGeiRQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@aws-sdk/credential-provider-env": "3.823.0", + "@aws-sdk/credential-provider-http": "3.823.0", + "@aws-sdk/credential-provider-ini": "3.823.0", + "@aws-sdk/credential-provider-process": "3.823.0", + "@aws-sdk/credential-provider-sso": "3.823.0", + "@aws-sdk/credential-provider-web-identity": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.823.0.tgz", + "integrity": "sha512-U/A10/7zu2FbMFFVpIw95y0TZf+oYyrhZTBn9eL8zgWcrYRqxrxdqtPj/zMrfIfyIvQUhuJSENN4dx4tfpCMWQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/types": "^7.27.0" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.823.0.tgz", + "integrity": "sha512-ff8IM80Wqz1V7VVMaMUqO2iR417jggfGWLPl8j2l7uCgwpEyop1ZZl5CFVYEwSupRBtwp+VlW1gTCk7ke56MUw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/client-sso": "3.823.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/token-providers": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", - "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.823.0.tgz", + "integrity": "sha512-lzoZdJMQq9w7i4lXVka30cVBe/dZoUDZST8Xz/soEd73gg7RTKgG+0szL4xFWgdBDgcJDWLfZfJzlbyIVyAyOA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/core": "3.823.0", + "@aws-sdk/nested-clients": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", - "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.821.0.tgz", + "integrity": "sha512-cebgeytKlWOgGczLo3BPvNY9XlzAzGZQANSysgJ2/8PSldmUpXRIF+GKPXDVhXeInWYHIfB8zZi3RqrPoXcNYQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/types": "^7.25.9" + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", - "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", + "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.9" + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", - "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.823.0.tgz", + "integrity": "sha512-Elt6G1ryEEdkrppqbyJON0o2x4x9xKknimJtMLdfG1b4YfO9X+UB31pk4R2SHvMYfrJ+p8DE2jRAhvV4g/dwIQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/preset-react": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", - "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-react-display-name": "^7.25.9", - "@babel/plugin-transform-react-jsx": "^7.25.9", - "@babel/plugin-transform-react-jsx-development": "^7.25.9", - "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", + "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.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==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.823.0.tgz", + "integrity": "sha512-UV755wt2HDru8PbxLn2S0Fvwgdn9mYamexn31Q6wyUGQ6rkpjKNEzL+oNDGQQmDQAOcQO+nLubKFsCwtBM02fQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", + "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@bufbuild/protobuf": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", - "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "node_modules/@endo/env-options": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@endo/env-options/-/env-options-1.1.8.tgz", - "integrity": "sha512-Xtxw9n33I4guo8q0sDyZiRuxlfaopM454AKiELgU7l3tqsylCut6IBZ0fPy4ltSHsBib7M3yF7OEMoIuLwzWVg==", - "license": "Apache-2.0" + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.823.0.tgz", + "integrity": "sha512-TKRQK09ld1LrIPExC9rIDpqnMsWcv+eq8ABKFHVo8mDLTSuWx/IiQ4eCh9T5zDuEZcLY4nNYCSzXKqw6XKcMCA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", + "node_modules/@aws-sdk/nested-clients": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.823.0.tgz", + "integrity": "sha512-/BcyOBubrJnd2gxlbbmNJR1w0Z3OVN/UE8Yz20e+ou+Mijjv7EbtVwmWvio1e3ZjphwdA8tVfPYZKwXmrvHKmQ==", + "license": "Apache-2.0", "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.823.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", - "cpu": [ - "arm" - ], - "license": "MIT", + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.824.0.tgz", + "integrity": "sha512-r8NueKxJaoWbZTnfENmIeoDFjdYbgA9sxALrT1mDKU6+sHeAMNZLJfgEtSFKm7CjVmmdk2ZbYblrP3DY9Ftqsg==", + "license": "Apache-2.0", "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.824.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-format-url": "3.821.0", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.824.0.tgz", + "integrity": "sha512-HBjuWeN6Z1pvJjUvGXdMNLwEypKKB4km6zXj9jsbOOwP8NTL6J5rY+JmlX/mfBTmvzmI0kMu2bxlQ4ME2CIRbA==", + "license": "Apache-2.0", "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", - "cpu": [ - "x64" - ], - "license": "MIT", + "node_modules/@aws-sdk/token-providers": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.823.0.tgz", + "integrity": "sha512-vz6onCb/+g4y+owxGGPMEMdN789dTfBOgz/c9pFv0f01840w9Rrt46l+gjQlnXnx+0KG6wNeBIVhFdbCfV3HyQ==", + "license": "Apache-2.0", "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/nested-clients": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", - "cpu": [ - "arm64" - ], - "license": "MIT", + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", - "cpu": [ - "x64" - ], - "license": "MIT", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", - "cpu": [ - "arm64" - ], - "license": "MIT", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.821.0.tgz", + "integrity": "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A==", + "license": "Apache-2.0", "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", - "cpu": [ - "x64" - ], - "license": "MIT", + "node_modules/@aws-sdk/util-format-url": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.821.0.tgz", + "integrity": "sha512-h+xqmPToxDrZ0a7rxE1a8Oh4zpWfZe9oiQUphGtfiGFA6j75UiURH5J3MmGHa/G4t15I3iLLbYtUXxvb1i7evg==", + "license": "Apache-2.0", "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", - "cpu": [ - "arm" - ], - "license": "MIT", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", - "cpu": [ - "arm64" - ], - "license": "MIT", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", - "cpu": [ - "ia32" - ], - "license": "MIT", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.823.0.tgz", + "integrity": "sha512-WvNeRz7HV3JLBVGTXW4Qr5QvvWY0vtggH5jW/NqHFH+ZEliVQaUIJ/HNLMpMoCSiu/DlpQAyAjRZXAptJ0oqbw==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", - "cpu": [ - "loong64" - ], - "license": "MIT", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", - "cpu": [ - "mips64el" - ], + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.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": ">=18" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "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==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/eslint-parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz", + "integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/eslint-parser/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, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "@babel/types": "^7.25.9" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", - "cpu": [ - "x64" - ], + "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==", + "dev": true, + "license": "ISC", + "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==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "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" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.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/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@endo/env-options": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@endo/env-options/-/env-options-1.1.8.tgz", + "integrity": "sha512-Xtxw9n33I4guo8q0sDyZiRuxlfaopM454AKiELgU7l3tqsylCut6IBZ0fPy4ltSHsBib7M3yF7OEMoIuLwzWVg==", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "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" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "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" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "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/eslintrc/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.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, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.0.tgz", + "integrity": "sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==", + "license": "MIT" + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.1.tgz", + "integrity": "sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==", + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", + "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==", + "license": "MIT" + }, + "node_modules/@fastify/compress": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@fastify/compress/-/compress-8.0.1.tgz", + "integrity": "sha512-yWNfKhvL4orfN45LKCHCo8Fcsbj1kdNgwyShw2xpdHfzPf4A3MESmgSfUm3TCKQwgqDdrPnLfy1E+3I/DVP+BQ==", + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "fastify-plugin": "^5.0.0", + "mime-db": "^1.52.0", + "minipass": "^7.0.4", + "peek-stream": "^1.1.3", + "pump": "^3.0.0", + "pumpify": "^2.0.1", + "readable-stream": "^4.5.2" + } + }, + "node_modules/@fastify/cors": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-10.0.1.tgz", + "integrity": "sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA==", + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "mnemonist": "0.39.8" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.0.tgz", + "integrity": "sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==", + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.0.0.tgz", + "integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.1.tgz", + "integrity": "sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@fastify/multipart": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.0.1.tgz", + "integrity": "sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@fastify/deepmerge": "^2.0.0", + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0", + "secure-json-parse": "^3.0.0" + } + }, + "node_modules/@fastify/send": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-3.3.0.tgz", + "integrity": "sha512-hvrgPVG3oehn4wSPmRdqZcBCsEt7Lp6WOd6vsJ3Ms4hc5r5zouT9Ls9wq6R2tHMgJGHhNtsmd0CnhP7lmF7OTg==", + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/static": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.0.3.tgz", + "integrity": "sha512-GHSoOVDIxEYEeVR5l044bRCuAKDErD/+9VE+Z9fnaTRr+DDz0Avrm4kKai1mHbPx6C0U7BVNthjd/gcMquZZUA==", + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^3.2.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, + "node_modules/@fastify/websocket": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.0.1.tgz", + "integrity": "sha512-44yam5+t1I9v09hWBYO+ezV88+mb9Se2BjgERtzB/68+0mGeTfFkjBeDBe2y+ZdiPpeO2rhevhdnfrBm5mqH+Q==", + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.3", + "fastify-plugin": "^5.0.0", + "ws": "^8.16.0" + } + }, + "node_modules/@firebolt-dev/css": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@firebolt-dev/css/-/css-0.4.3.tgz", + "integrity": "sha512-qmtCltoW/RFe2Fa5Oa/U9nt4ncAhcwkHeM7OR8N9suqgjHLfOuKFnyJ6SYRbf7YCZaM4WDdspFzFiK/asU8XEA==", + "dependencies": { + "react": "canary", + "stylis": "^4.3.1" + } + }, + "node_modules/@firebolt-dev/jsx": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@firebolt-dev/jsx/-/jsx-0.4.3.tgz", + "integrity": "sha512-VGXzXxojPTpTxNvtN3XuTS+enqwG8ms4Km2usE694zAmVMXxcL4ZmOCqAYgCD1kguzwAANVJtQl/zRr/LqaFDQ==", + "dependencies": { + "@firebolt-dev/css": "0.4.3", + "react": "canary" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/@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, + "license": "BSD-3-Clause" + }, + "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==", + "license": "ISC", + "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/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "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/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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jspm/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@jspm/core/-/core-2.1.0.tgz", + "integrity": "sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==", + "license": "Apache-2.0" + }, + "node_modules/@livekit/mutex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz", + "integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==", + "license": "Apache-2.0" + }, + "node_modules/@livekit/protocol": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.34.0.tgz", + "integrity": "sha512-bU7pCLAMRVTVZb1KSxA46q55bhOc4iATrY/gccy2/oX1D57tiZEI+8wGRWHeDwBb0UwnABu6JXzC4tTFkdsaOg==", + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "^1.10.0" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/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, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.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==", + "dev": true, + "license": "MIT", + "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, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pixiv/three-vrm": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm/-/three-vrm-3.3.3.tgz", + "integrity": "sha512-XivYX9N4dSi+qwRg+4+ouaMP2y0+xOJ5YQyTpeGYOOFACwEUaTovaJiDgqFNX7nF49JK6B6kXygmPijTIJJVhA==", + "license": "MIT", + "dependencies": { + "@pixiv/three-vrm-core": "3.3.3", + "@pixiv/three-vrm-materials-hdr-emissive-multiplier": "3.3.3", + "@pixiv/three-vrm-materials-mtoon": "3.3.3", + "@pixiv/three-vrm-materials-v0compat": "3.3.3", + "@pixiv/three-vrm-node-constraint": "3.3.3", + "@pixiv/three-vrm-springbone": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/three-vrm-core": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-core/-/three-vrm-core-3.3.3.tgz", + "integrity": "sha512-5I5Q0DhdnBAg6W0jBFAIn7NBQ7XIpmylZ6GcX3oGUhiYw44mTxfONIHTl6C7XedESJ8lpKGy0jZZIA7QMWE2Lg==", + "license": "MIT", + "dependencies": { + "@pixiv/types-vrm-0.0": "3.3.3", + "@pixiv/types-vrmc-vrm-1.0": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/three-vrm-materials-hdr-emissive-multiplier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-materials-hdr-emissive-multiplier/-/three-vrm-materials-hdr-emissive-multiplier-3.3.3.tgz", + "integrity": "sha512-34owe3gck/N3ElDE32g50at+lVq2ou50Tis6sU2B6RQWU3mc17RQqWnawbEKauMfPYg2fGN0SpsLbVfBymWSiQ==", + "license": "MIT", + "dependencies": { + "@pixiv/types-vrmc-materials-hdr-emissive-multiplier-1.0": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/three-vrm-materials-mtoon": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-materials-mtoon/-/three-vrm-materials-mtoon-3.3.3.tgz", + "integrity": "sha512-mpLGV++bA90ot1vnLY835rmPl4ju4ReVbcfJMjDcNiHniHoyuP+peiUX76dNwBEkqW584tjqrXputPzcB/ydtg==", + "license": "MIT", + "dependencies": { + "@pixiv/types-vrm-0.0": "3.3.3", + "@pixiv/types-vrmc-materials-mtoon-1.0": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/three-vrm-materials-v0compat": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-materials-v0compat/-/three-vrm-materials-v0compat-3.3.3.tgz", + "integrity": "sha512-l7jPWlU+KSpt/VgsQ6Z+dGKWN/ZSXh8WaSNcxmO1J3q3OCg+AYsKrunajotKtqw/EWXTa9z1LguNG8qB5L1XZg==", + "license": "MIT", + "dependencies": { + "@pixiv/types-vrm-0.0": "3.3.3", + "@pixiv/types-vrmc-materials-mtoon-1.0": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/three-vrm-node-constraint": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-node-constraint/-/three-vrm-node-constraint-3.3.3.tgz", + "integrity": "sha512-1Z6F6uuo69tz9ZN1Cg5H5RSkhbYwCAunVPl69meEIT1k51s5tE6/EKuVK4LR7vcoL7ooGmrMy7IHTW4lAAblgg==", + "license": "MIT", + "dependencies": { + "@pixiv/types-vrmc-node-constraint-1.0": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/three-vrm-springbone": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-springbone/-/three-vrm-springbone-3.3.3.tgz", + "integrity": "sha512-ixdVwQM73pmjaFGPdRSjYSWAX4EkAA8aUlzwexX/lQBZBjyBHat8hdpnyz4W2d2xziGQbtcjNOhpQfPiBe7u2w==", + "license": "MIT", + "dependencies": { + "@pixiv/types-vrm-0.0": "3.3.3", + "@pixiv/types-vrmc-springbone-1.0": "3.3.3", + "@pixiv/types-vrmc-springbone-extended-collider-1.0": "3.3.3" + }, + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/@pixiv/types-vrm-0.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrm-0.0/-/types-vrm-0.0-3.3.3.tgz", + "integrity": "sha512-+nqVpgwwwDsy2UiQd+r6vaziqvpzWwYVGw04ry2yKJ5RjRC36t+KQ8YhLLgSr9xDG5wC/JJk/r19Q3bBKeGJKA==", + "license": "MIT" + }, + "node_modules/@pixiv/types-vrmc-materials-hdr-emissive-multiplier-1.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-materials-hdr-emissive-multiplier-1.0/-/types-vrmc-materials-hdr-emissive-multiplier-1.0-3.3.3.tgz", + "integrity": "sha512-QhyWSSyAhxzKL0pWnYi5+74V6OjacvpN6mtYX966UBo0S18gRxg61B/E2CKKhGZN9adCOqf1VbKC9mCxuvPzyA==", + "license": "MIT" + }, + "node_modules/@pixiv/types-vrmc-materials-mtoon-1.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-materials-mtoon-1.0/-/types-vrmc-materials-mtoon-1.0-3.3.3.tgz", + "integrity": "sha512-8VmtwPn2nqLB6nj16gljodOLCjXBM3aLyRZXXidntZNrG6n+UvB1vEfBnrpRAkg6jUi0nt71f15+b7IJv6xVkw==", + "license": "MIT" + }, + "node_modules/@pixiv/types-vrmc-node-constraint-1.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-node-constraint-1.0/-/types-vrmc-node-constraint-1.0-3.3.3.tgz", + "integrity": "sha512-giXa4unY9DezdieL96XXHsK8SvnF+hnouyXPK6ucXM1HDmrX++T33WriNguB17CVChhb+H5SKAj381+PWbctRw==", + "license": "MIT" + }, + "node_modules/@pixiv/types-vrmc-springbone-1.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-springbone-1.0/-/types-vrmc-springbone-1.0-3.3.3.tgz", + "integrity": "sha512-mK875IfFz0jNoD5kB2bRyo2QvbN03rslMNBGmmmpmWW99rN0zqvwALyQO/oKVIm4oe07iXGaFjh/aoDV5Xrylg==", + "license": "MIT" + }, + "node_modules/@pixiv/types-vrmc-springbone-extended-collider-1.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-springbone-extended-collider-1.0/-/types-vrmc-springbone-extended-collider-1.0-3.3.3.tgz", + "integrity": "sha512-xlLmMGw5G5NuRMhOKw6/kNa0PoWobP6nlBdNDNa1FyMYtx3TFNN4Lz2UWysxBJbrFXCi2CzpPSvlHmDIIPEXeg==", + "license": "MIT" + }, + "node_modules/@pixiv/types-vrmc-vrm-1.0": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-vrm-1.0/-/types-vrmc-vrm-1.0-3.3.3.tgz", + "integrity": "sha512-/EqTI0MakfG4J2tMHLHS0lYmH3NjjyV2mFTEEbb9qkCO1yM31bsmwUSE3lY/BWuN3+r03+YQJQbIErj+sLxXDQ==", + "license": "MIT" + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "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, - "license": "MIT", + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "optional": 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" + "tslib": "^2.6.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "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, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" }, "engines": { - "node": "*" + "node": ">=18.0.0" } }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" + "node_modules/@smithy/core": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", + "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@fastify/accept-negotiator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.0.tgz", - "integrity": "sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==", - "license": "MIT" - }, - "node_modules/@fastify/ajv-compiler": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.1.tgz", - "integrity": "sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==", - "license": "MIT", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", - "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==", - "license": "MIT" - }, - "node_modules/@fastify/compress": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@fastify/compress/-/compress-8.0.1.tgz", - "integrity": "sha512-yWNfKhvL4orfN45LKCHCo8Fcsbj1kdNgwyShw2xpdHfzPf4A3MESmgSfUm3TCKQwgqDdrPnLfy1E+3I/DVP+BQ==", - "license": "MIT", + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@fastify/accept-negotiator": "^2.0.0", - "fastify-plugin": "^5.0.0", - "mime-db": "^1.52.0", - "minipass": "^7.0.4", - "peek-stream": "^1.1.3", - "pump": "^3.0.0", - "pumpify": "^2.0.1", - "readable-stream": "^4.5.2" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/cors": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-10.0.1.tgz", - "integrity": "sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA==", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "fastify-plugin": "^5.0.0", - "mnemonist": "0.39.8" + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/deepmerge": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.0.tgz", - "integrity": "sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==", - "license": "MIT" - }, - "node_modules/@fastify/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.0.0.tgz", - "integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==", - "license": "MIT" - }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.1.tgz", - "integrity": "sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "fast-json-stringify": "^6.0.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", - "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "fast-deep-equal": "^3.1.3" + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/multipart": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.0.1.tgz", - "integrity": "sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@fastify/busboy": "^3.0.0", - "@fastify/deepmerge": "^2.0.0", - "@fastify/error": "^4.0.0", - "fastify-plugin": "^5.0.0", - "secure-json-parse": "^3.0.0" + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/send": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@fastify/send/-/send-3.3.0.tgz", - "integrity": "sha512-hvrgPVG3oehn4wSPmRdqZcBCsEt7Lp6WOd6vsJ3Ms4hc5r5zouT9Ls9wq6R2tHMgJGHhNtsmd0CnhP7lmF7OTg==", - "license": "MIT", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@lukeed/ms": "^2.0.2", - "escape-html": "~1.0.3", - "fast-decode-uri-component": "^1.0.1", - "http-errors": "^2.0.0", - "mime": "^3" + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/static": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.0.3.tgz", - "integrity": "sha512-GHSoOVDIxEYEeVR5l044bRCuAKDErD/+9VE+Z9fnaTRr+DDz0Avrm4kKai1mHbPx6C0U7BVNthjd/gcMquZZUA==", - "license": "MIT", + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@fastify/accept-negotiator": "^2.0.0", - "@fastify/send": "^3.2.0", - "content-disposition": "^0.5.4", - "fastify-plugin": "^5.0.0", - "fastq": "^1.17.1", - "glob": "^11.0.0" + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@fastify/websocket": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.0.1.tgz", - "integrity": "sha512-44yam5+t1I9v09hWBYO+ezV88+mb9Se2BjgERtzB/68+0mGeTfFkjBeDBe2y+ZdiPpeO2rhevhdnfrBm5mqH+Q==", - "license": "MIT", + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "duplexify": "^4.1.3", - "fastify-plugin": "^5.0.0", - "ws": "^8.16.0" + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@firebolt-dev/css": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@firebolt-dev/css/-/css-0.4.3.tgz", - "integrity": "sha512-qmtCltoW/RFe2Fa5Oa/U9nt4ncAhcwkHeM7OR8N9suqgjHLfOuKFnyJ6SYRbf7YCZaM4WDdspFzFiK/asU8XEA==", + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "react": "canary", - "stylis": "^4.3.1" + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@firebolt-dev/jsx": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@firebolt-dev/jsx/-/jsx-0.4.3.tgz", - "integrity": "sha512-VGXzXxojPTpTxNvtN3XuTS+enqwG8ms4Km2usE694zAmVMXxcL4ZmOCqAYgCD1kguzwAANVJtQl/zRr/LqaFDQ==", + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@firebolt-dev/css": "0.4.3", - "react": "canary" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "license": "Apache-2.0", + "optional": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "tslib": "^2.6.2" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.0.0" } }, - "node_modules/@humanwhocodes/config-array/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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": "*" + "node": ">=18.0.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, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", + "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "optional": true, + "dependencies": { + "@smithy/core": "^3.5.3", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": ">=18.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, - "license": "BSD-3-Clause" - }, - "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==", - "license": "ISC", + "node_modules/@smithy/middleware-retry": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", + "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", + "license": "Apache-2.0", + "optional": 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" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=12" + "node": ">=18.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18.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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "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==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT", + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jspm/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@jspm/core/-/core-2.1.0.tgz", - "integrity": "sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==", - "license": "Apache-2.0" - }, - "node_modules/@livekit/mutex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz", - "integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==", - "license": "Apache-2.0" - }, - "node_modules/@livekit/protocol": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.34.0.tgz", - "integrity": "sha512-bU7pCLAMRVTVZb1KSxA46q55bhOc4iATrY/gccy2/oX1D57tiZEI+8wGRWHeDwBb0UwnABu6JXzC4tTFkdsaOg==", + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "license": "Apache-2.0", + "optional": true, "dependencies": { - "@bufbuild/protobuf": "^1.10.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@lukeed/ms": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", - "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", - "license": "MIT", + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", + "node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", + "node_modules/@smithy/smithy-client": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", + "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", + "license": "Apache-2.0", "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "license": "MIT", "dependencies": { - "eslint-scope": "5.1.1" + "@smithy/core": "^3.5.3", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/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, - "license": "BSD-2-Clause", + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4.0" + "node": ">=18.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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8" + "node": ">=18.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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 8" + "node": ">=18.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==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm/-/three-vrm-3.3.3.tgz", - "integrity": "sha512-XivYX9N4dSi+qwRg+4+ouaMP2y0+xOJ5YQyTpeGYOOFACwEUaTovaJiDgqFNX7nF49JK6B6kXygmPijTIJJVhA==", - "license": "MIT", + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/three-vrm-core": "3.3.3", - "@pixiv/three-vrm-materials-hdr-emissive-multiplier": "3.3.3", - "@pixiv/three-vrm-materials-mtoon": "3.3.3", - "@pixiv/three-vrm-materials-v0compat": "3.3.3", - "@pixiv/three-vrm-node-constraint": "3.3.3", - "@pixiv/three-vrm-springbone": "3.3.3" + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm-core": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-core/-/three-vrm-core-3.3.3.tgz", - "integrity": "sha512-5I5Q0DhdnBAg6W0jBFAIn7NBQ7XIpmylZ6GcX3oGUhiYw44mTxfONIHTl6C7XedESJ8lpKGy0jZZIA7QMWE2Lg==", - "license": "MIT", + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/types-vrm-0.0": "3.3.3", - "@pixiv/types-vrmc-vrm-1.0": "3.3.3" + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm-materials-hdr-emissive-multiplier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-materials-hdr-emissive-multiplier/-/three-vrm-materials-hdr-emissive-multiplier-3.3.3.tgz", - "integrity": "sha512-34owe3gck/N3ElDE32g50at+lVq2ou50Tis6sU2B6RQWU3mc17RQqWnawbEKauMfPYg2fGN0SpsLbVfBymWSiQ==", - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", + "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/types-vrmc-materials-hdr-emissive-multiplier-1.0": "3.3.3" + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm-materials-mtoon": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-materials-mtoon/-/three-vrm-materials-mtoon-3.3.3.tgz", - "integrity": "sha512-mpLGV++bA90ot1vnLY835rmPl4ju4ReVbcfJMjDcNiHniHoyuP+peiUX76dNwBEkqW584tjqrXputPzcB/ydtg==", - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", + "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/types-vrm-0.0": "3.3.3", - "@pixiv/types-vrmc-materials-mtoon-1.0": "3.3.3" + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm-materials-v0compat": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-materials-v0compat/-/three-vrm-materials-v0compat-3.3.3.tgz", - "integrity": "sha512-l7jPWlU+KSpt/VgsQ6Z+dGKWN/ZSXh8WaSNcxmO1J3q3OCg+AYsKrunajotKtqw/EWXTa9z1LguNG8qB5L1XZg==", - "license": "MIT", + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/types-vrm-0.0": "3.3.3", - "@pixiv/types-vrmc-materials-mtoon-1.0": "3.3.3" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm-node-constraint": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-node-constraint/-/three-vrm-node-constraint-3.3.3.tgz", - "integrity": "sha512-1Z6F6uuo69tz9ZN1Cg5H5RSkhbYwCAunVPl69meEIT1k51s5tE6/EKuVK4LR7vcoL7ooGmrMy7IHTW4lAAblgg==", - "license": "MIT", + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/types-vrmc-node-constraint-1.0": "3.3.3" + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/three-vrm-springbone": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/three-vrm-springbone/-/three-vrm-springbone-3.3.3.tgz", - "integrity": "sha512-ixdVwQM73pmjaFGPdRSjYSWAX4EkAA8aUlzwexX/lQBZBjyBHat8hdpnyz4W2d2xziGQbtcjNOhpQfPiBe7u2w==", - "license": "MIT", + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@pixiv/types-vrm-0.0": "3.3.3", - "@pixiv/types-vrmc-springbone-1.0": "3.3.3", - "@pixiv/types-vrmc-springbone-extended-collider-1.0": "3.3.3" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "three": ">=0.137" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pixiv/types-vrm-0.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrm-0.0/-/types-vrm-0.0-3.3.3.tgz", - "integrity": "sha512-+nqVpgwwwDsy2UiQd+r6vaziqvpzWwYVGw04ry2yKJ5RjRC36t+KQ8YhLLgSr9xDG5wC/JJk/r19Q3bBKeGJKA==", - "license": "MIT" - }, - "node_modules/@pixiv/types-vrmc-materials-hdr-emissive-multiplier-1.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-materials-hdr-emissive-multiplier-1.0/-/types-vrmc-materials-hdr-emissive-multiplier-1.0-3.3.3.tgz", - "integrity": "sha512-QhyWSSyAhxzKL0pWnYi5+74V6OjacvpN6mtYX966UBo0S18gRxg61B/E2CKKhGZN9adCOqf1VbKC9mCxuvPzyA==", - "license": "MIT" - }, - "node_modules/@pixiv/types-vrmc-materials-mtoon-1.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-materials-mtoon-1.0/-/types-vrmc-materials-mtoon-1.0-3.3.3.tgz", - "integrity": "sha512-8VmtwPn2nqLB6nj16gljodOLCjXBM3aLyRZXXidntZNrG6n+UvB1vEfBnrpRAkg6jUi0nt71f15+b7IJv6xVkw==", - "license": "MIT" + "node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pixiv/types-vrmc-node-constraint-1.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-node-constraint-1.0/-/types-vrmc-node-constraint-1.0-3.3.3.tgz", - "integrity": "sha512-giXa4unY9DezdieL96XXHsK8SvnF+hnouyXPK6ucXM1HDmrX++T33WriNguB17CVChhb+H5SKAj381+PWbctRw==", - "license": "MIT" + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pixiv/types-vrmc-springbone-1.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-springbone-1.0/-/types-vrmc-springbone-1.0-3.3.3.tgz", - "integrity": "sha512-mK875IfFz0jNoD5kB2bRyo2QvbN03rslMNBGmmmpmWW99rN0zqvwALyQO/oKVIm4oe07iXGaFjh/aoDV5Xrylg==", - "license": "MIT" + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pixiv/types-vrmc-springbone-extended-collider-1.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-springbone-extended-collider-1.0/-/types-vrmc-springbone-extended-collider-1.0-3.3.3.tgz", - "integrity": "sha512-xlLmMGw5G5NuRMhOKw6/kNa0PoWobP6nlBdNDNa1FyMYtx3TFNN4Lz2UWysxBJbrFXCi2CzpPSvlHmDIIPEXeg==", - "license": "MIT" + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pixiv/types-vrmc-vrm-1.0": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@pixiv/types-vrmc-vrm-1.0/-/types-vrmc-vrm-1.0-3.3.3.tgz", - "integrity": "sha512-/EqTI0MakfG4J2tMHLHS0lYmH3NjjyV2mFTEEbb9qkCO1yM31bsmwUSE3lY/BWuN3+r03+YQJQbIErj+sLxXDQ==", - "license": "MIT" + "node_modules/@smithy/util-waiter": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.5.tgz", + "integrity": "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, "node_modules/@ungap/structured-clone": { "version": "1.2.1", @@ -2014,6 +3725,13 @@ "node": ">= 6" } }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT", + "optional": true + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3855,6 +5573,29 @@ "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", "license": "BSD-3-Clause" }, + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastify": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.2.0.tgz", @@ -7114,6 +8855,19 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/stylis": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", @@ -7522,6 +9276,20 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/webrtc-adapter": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz", diff --git a/package.json b/package.json index 99ef7db7..bb1a1780 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,10 @@ "three-mesh-bvh": "^0.8.3", "yoga-layout": "^3.2.1" }, + "optionalDependencies": { + "@aws-sdk/client-s3": "^3.685.0", + "@aws-sdk/s3-request-presigner": "^3.685.0" + }, "devDependencies": { "@babel/eslint-parser": "^7.23.10", "@babel/preset-react": "^7.23.10", diff --git a/scripts/clean-world-s3.mjs b/scripts/clean-world-s3.mjs new file mode 100644 index 00000000..e901066b --- /dev/null +++ b/scripts/clean-world-s3.mjs @@ -0,0 +1,192 @@ +import 'dotenv-flow/config' +import fs from 'fs-extra' +import path from 'path' +import Knex from 'knex' +import moment from 'moment' +import { fileURLToPath } from 'url' +import { S3Client, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3' + +const DRY_RUN = false + +const world = process.env.WORLD || 'world' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.join(__dirname, '../') +const worldDir = path.join(rootDir, world) + +// Initialize S3 if configured +let s3Client = null +let bucketName = null +let assetsPrefix = null + +if (process.env.S3_BUCKET_NAME) { + console.log('Using S3 storage for cleanup') + s3Client = new S3Client({ + region: process.env.S3_REGION || 'us-east-1', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, + }) + bucketName = process.env.S3_BUCKET_NAME + assetsPrefix = process.env.S3_ASSETS_PREFIX || 'assets/' +} else { + console.log('S3 not configured, exiting...') + process.exit(1) +} + +const db = Knex({ + client: 'better-sqlite3', + connection: { + filename: `./${world}/db.sqlite`, + }, + useNullAsDefault: true, +}) + +// TODO: run any missing migrations first? + +let blueprints = new Set() +const blueprintRows = await db('blueprints') +for (const row of blueprintRows) { + const blueprint = JSON.parse(row.data) + blueprints.add(blueprint) +} + +const entities = [] +const entityRows = await db('entities') +for (const row of entityRows) { + const entity = JSON.parse(row.data) + entities.push(entity) +} + +const vrms = new Set() +const userRows = await db('users').select('avatar') +for (const user of userRows) { + if (!user.avatar) continue + const avatar = user.avatar.replace('asset://', '') + vrms.add(avatar) +} + +// Get list of files in S3 +const s3Assets = new Set() +console.log('Fetching S3 assets...') +let continuationToken = undefined +do { + const command = new ListObjectsV2Command({ + Bucket: bucketName, + Prefix: assetsPrefix, + ContinuationToken: continuationToken, + }) + + const response = await s3Client.send(command) + + if (response.Contents) { + for (const object of response.Contents) { + const key = object.Key + const filename = key.replace(assetsPrefix, '') + + // Check if it's a hashed asset (64 character hash) + const isAsset = filename.split('.')[0].length === 64 + if (isAsset) { + s3Assets.add(filename) + } + } + } + + continuationToken = response.NextContinuationToken +} while (continuationToken) + +console.log(`Found ${s3Assets.size} S3 assets`) + +let worldImage +let worldModel +let worldAvatar +let settings = await db('config').where('key', 'settings').first() +if (settings) { + settings = JSON.parse(settings.value) + if (settings.image) worldImage = settings.image.url.replace('asset://', '') + if (settings.model) worldModel = settings.model.url.replace('asset://', '') + if (settings.avatar) worldAvatar = settings.avatar.url.replace('asset://', '') +} + +/** + * Phase 1: + * Remove all blueprints that no entities reference any more. + * The world doesn't need them, and we shouldn't be loading them in and sending dead blueprints to all the clients. + */ + +const blueprintsToDelete = [] +for (const blueprint of blueprints) { + const canDelete = !entities.find(e => e.blueprint === blueprint.id) + if (canDelete) { + blueprintsToDelete.push(blueprint) + } +} +console.log(`deleting ${blueprintsToDelete.length} blueprints`) +for (const blueprint of blueprintsToDelete) { + blueprints.delete(blueprint) + if (!DRY_RUN) { + await db('blueprints').where('id', blueprint.id).delete() + } + console.log('delete blueprint:', blueprint.id) +} + +/** + * Phase 2: + * Remove all S3 asset files that are not: + * - referenced by a blueprint + * - used as a player avatar + * - used as the world image + * - used as the world avatar + * - used as the world model + */ + +const blueprintAssets = new Set() +for (const blueprint of blueprints) { + if (blueprint.model && blueprint.model.startsWith('asset://')) { + const asset = blueprint.model.replace('asset://', '') + blueprintAssets.add(asset) + } + if (blueprint.script && blueprint.script.startsWith('asset://')) { + const asset = blueprint.script.replace('asset://', '') + blueprintAssets.add(asset) + } + if (blueprint.image?.url && blueprint.image.url.startsWith('asset://')) { + const asset = blueprint.image.url.replace('asset://', '') + blueprintAssets.add(asset) + } + for (const key in blueprint.props) { + const url = blueprint.props[key]?.url + if (!url) continue + const asset = url.replace('asset://', '') + blueprintAssets.add(asset) + } +} + +const s3FilesToDelete = [] +for (const s3Asset of s3Assets) { + const isUsedByBlueprint = blueprintAssets.has(s3Asset) + const isUsedByUser = vrms.has(s3Asset) + const isWorldImage = s3Asset === worldImage + const isWorldModel = s3Asset === worldModel + const isWorldAvatar = s3Asset === worldAvatar + if (!isUsedByBlueprint && !isUsedByUser && !isWorldModel && !isWorldAvatar && !isWorldImage) { + s3FilesToDelete.push(s3Asset) + } +} + +console.log(`deleting ${s3FilesToDelete.length} S3 assets`) +for (const s3Asset of s3FilesToDelete) { + const s3Key = `${assetsPrefix}${s3Asset}` + if (!DRY_RUN) { + const deleteCommand = new DeleteObjectCommand({ + Bucket: bucketName, + Key: s3Key, + }) + await s3Client.send(deleteCommand) + } + console.log('delete S3 asset:', s3Asset) +} + +console.log('Cleanup completed') +process.exit() \ No newline at end of file diff --git a/src/core/systems/ServerLoader.js b/src/core/systems/ServerLoader.js index 9633e22e..c6d9d608 100644 --- a/src/core/systems/ServerLoader.js +++ b/src/core/systems/ServerLoader.js @@ -63,14 +63,65 @@ export class ServerLoader extends System { async fetchArrayBuffer(url) { const isRemote = url.startsWith('http://') || url.startsWith('https://') + + // Log específico para emote-idle + if (url.includes('emote-idle')) { + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Fetching ${url}`) + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: isRemote=${isRemote}`) + } + if (isRemote) { + try { const response = await fetch(url) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText} for ${url}`) + } + const contentType = response.headers.get('content-type') || '' + if (contentType.includes('text/xml') || contentType.includes('application/xml')) { + const text = await response.text() + console.error(`Received XML response instead of binary file for ${url}:`, text.substring(0, 200)) + throw new Error(`File not found or access denied: ${url}`) + } const arrayBuffer = await response.arrayBuffer() + + // Log específico para emote-idle + if (url.includes('emote-idle')) { + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Successfully fetched ${arrayBuffer.byteLength} bytes`) + } + return arrayBuffer + } catch (error) { + console.error(`Error fetching ${url}:`, error.message) + + // Log específico para emote-idle + if (url.includes('emote-idle')) { + console.error(`[ServerLoader] EMOTE-IDLE DEBUG: FAILED to fetch - ${error.message}`) + } + + throw error + } } else { - const buffer = await fs.readFile(url) - const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) - return arrayBuffer + // Local file access + try { + const filePath = url.startsWith('file://') ? url.slice(7) : url + + // Log específico para emote-idle + if (url.includes('emote-idle')) { + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Loading local file ${filePath}`) + } + + const buffer = await fs.readFile(filePath) + return buffer.buffer + } catch (error) { + console.error(`Error reading local file ${url}:`, error.message) + + // Log específico para emote-idle + if (url.includes('emote-idle')) { + console.error(`[ServerLoader] EMOTE-IDLE DEBUG: FAILED to read local file - ${error.message}`) + } + + throw error + } } } @@ -86,6 +137,96 @@ export class ServerLoader extends System { } } + async loadModel(url) { + const resolvedUrl = this.world.resolveURL(url, true) + console.log(`[ServerLoader] Loading model: ${url} -> ${resolvedUrl}`) + + // Log específico para emote-idle + if (url.includes('emote-idle') || resolvedUrl.includes('emote-idle')) { + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Original URL: ${url}`) + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Resolved URL: ${resolvedUrl}`) + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: World assetsUrl: ${this.world.assetsUrl}`) + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: World assetsDir: ${this.world.assetsDir}`) + } + + console.log(`[ServerLoader] Fetching model from: ${resolvedUrl}`) + + try { + const arrayBuffer = await this.fetchArrayBuffer(resolvedUrl) + + // Log específico para emote-idle + if (url.includes('emote-idle') || resolvedUrl.includes('emote-idle')) { + console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Successfully loaded, size: ${arrayBuffer.byteLength} bytes`) + } + + const loader = new THREE.GLTFLoader() + + const gltf = await new Promise((resolve, reject) => { + loader.parse(arrayBuffer, '', resolve, reject) + }) + + return gltf + + } catch (error) { + console.error(`[ServerLoader] Error loading model ${url}:`, error.message) + + // FALLBACK: If it's a hasheaded asset that failed, try to find a built-in equivalent + if (resolvedUrl.includes('.glb') && error.message.includes('404') || error.message.includes('403')) { + const fallbackUrl = this.tryGetBuiltInFallback(url, resolvedUrl) + if (fallbackUrl && fallbackUrl !== resolvedUrl) { + console.log(`[ServerLoader] Trying fallback for ${url}: ${fallbackUrl}`) + try { + const fallbackBuffer = await this.fetchArrayBuffer(fallbackUrl) + const loader = new THREE.GLTFLoader() + const gltf = await new Promise((resolve, reject) => { + loader.parse(fallbackBuffer, '', resolve, reject) + }) + console.log(`[ServerLoader] Fallback successful for ${url}`) + return gltf + } catch (fallbackError) { + console.error(`[ServerLoader] Fallback also failed for ${url}:`, fallbackError.message) + } + } + } + + throw error + } + } + + /** + * Try to find a built-in asset fallback for a failed hasheaded asset + */ + tryGetBuiltInFallback(originalUrl, resolvedUrl) { + // If it's already a built-in asset (not hasheaded), don't try fallback + if (!resolvedUrl.match(/[a-f0-9]{64}\.glb/)) { + return null + } + + // Common built-in assets that might be used as fallbacks + const builtInAssets = [ + 'crash-block.glb', + 'emote-idle.glb', + 'emote-walk.glb', + 'emote-run.glb', + 'emote-jump.glb', + 'emote-fall.glb', + 'emote-flip.glb', + 'emote-float.glb', + 'emote-talk.glb' + ] + + // For emote-related assets, try to match by name + for (const builtIn of builtInAssets) { + if (originalUrl.includes('emote') && builtIn.includes('emote-idle')) { + // Default emote fallback + return this.world.resolveURL(`asset://${builtIn}`, true) + } + } + + // Generic fallback for any .glb - use crash-block as a placeholder + return this.world.resolveURL('asset://crash-block.glb', true) + } + load(type, url) { const key = `${type}/${url}` if (this.promises.has(key)) { diff --git a/src/core/systems/ServerNetwork.js b/src/core/systems/ServerNetwork.js index 84d62a6d..fe08c4eb 100644 --- a/src/core/systems/ServerNetwork.js +++ b/src/core/systems/ServerNetwork.js @@ -295,7 +295,7 @@ export class ServerNetwork extends System { socket.send('snapshot', { id: socket.id, serverTime: performance.now(), - assetsUrl: process.env.PUBLIC_ASSETS_URL, + assetsUrl: this.world.assetsUrl, apiUrl: process.env.PUBLIC_API_URL, maxUploadSize: process.env.PUBLIC_MAX_UPLOAD_SIZE, collections: this.world.collections.serialize(), diff --git a/src/server/CloudStorage.js b/src/server/CloudStorage.js new file mode 100644 index 00000000..8fbfc5d1 --- /dev/null +++ b/src/server/CloudStorage.js @@ -0,0 +1,72 @@ +import { cloneDeep, throttle } from 'lodash-es' + +export class CloudStorage { + constructor(storageManager) { + this.storageManager = storageManager + this.data = {} + this.loaded = false + + // Throttle saves to avoid too many writes + this.save = throttle(() => this.persist(), 1000, { leading: true, trailing: true }) + } + + /** + * Initialize the storage by loading existing data + */ + async init() { + try { + this.data = await this.storageManager.loadStorageData() + this.loaded = true + console.log('Storage data loaded successfully') + } catch (err) { + console.error('Error loading storage data:', err) + this.data = {} + this.loaded = true + } + } + + get(key) { + if (!this.loaded) { + console.warn('Storage not yet loaded, returning undefined') + return undefined + } + return this.data[key] + } + + set(key, value) { + if (!this.loaded) { + console.warn('Storage not yet loaded, cannot set value') + return + } + + try { + // Ensure value is serializable + value = JSON.parse(JSON.stringify(value)) + this.data[key] = value + this.save() + } catch (err) { + console.error('Error setting storage value:', err) + } + } + + async persist() { + if (!this.loaded) { + console.warn('Storage not yet loaded, cannot persist') + return + } + + try { + await this.storageManager.saveStorageData(this.data) + // console.log('Storage data persisted successfully') + } catch (err) { + console.error('Failed to persist storage:', err) + } + } + + /** + * Force an immediate save (bypass throttling) + */ + async forcePersist() { + return await this.persist() + } +} \ No newline at end of file diff --git a/src/server/FileStorage.js b/src/server/FileStorage.js new file mode 100644 index 00000000..68e9192a --- /dev/null +++ b/src/server/FileStorage.js @@ -0,0 +1,203 @@ +import fs from 'fs-extra' +import path from 'path' + +export class FileStorage { + constructor(config = {}) { + this.rootDir = path.join(__dirname, '../') + this.worldDir = path.join(this.rootDir, process.env.WORLD || 'world') + this.assetsDir = path.join(this.worldDir, '/assets') + this.collectionsDir = path.join(this.worldDir, '/collections') + this.assetsUrl = config.assetsUrl || '/assets' + } + + /** + * Initialize directories and copy built-in assets + */ + async initialize() { + await fs.ensureDir(this.worldDir) + await fs.ensureDir(this.assetsDir) + await fs.ensureDir(this.collectionsDir) + + // Copy over built-in assets and collections + const builtInAssetsDir = path.join(this.rootDir, 'src/world/assets') + const builtInCollectionsDir = path.join(this.rootDir, 'src/world/collections') + + if (await fs.exists(builtInAssetsDir)) { + await fs.copy(builtInAssetsDir, this.assetsDir) + console.log('Built-in assets copied to local storage') + } + + if (await fs.exists(builtInCollectionsDir)) { + await fs.copy(builtInCollectionsDir, this.collectionsDir) + console.log('Built-in collections copied to local storage') + } + } + + /** + * Get the paths for local storage + */ + getPaths() { + return { + worldDir: this.worldDir, + assetsDir: this.assetsDir, + collectionsDir: this.collectionsDir, + } + } + + /** + * Upload a file to local filesystem + * @param {string} filename - The filename to use + * @param {Buffer} buffer - The file data + * @param {string} contentType - The MIME type of the file (not used for local storage) + * @returns {Promise} The local URL of the file + */ + async uploadFile(filename, buffer, contentType) { + const filePath = path.join(this.assetsDir, filename) + + // Check if file already exists + const exists = await fs.exists(filePath) + if (!exists) { + await fs.writeFile(filePath, buffer) + console.log(`File saved locally: ${filename}`) + } else { + console.log(`File already exists locally: ${filename}`) + } + + return `${this.assetsUrl}/${filename}` + } + + /** + * Check if a file exists locally + * @param {string} filename - The filename to check + * @returns {Promise} Whether the file exists + */ + async fileExists(filename) { + const filePath = path.join(this.assetsDir, filename) + return await fs.exists(filePath) + } + + /** + * Get the public URL for a file + * @param {string} filename - The filename + * @returns {string} The public URL + */ + getPublicUrl(filename) { + return `${this.assetsUrl}/${filename}` + } + + /** + * Delete a file from local storage + * @param {string} filename - The filename to delete + * @returns {Promise} Whether the deletion was successful + */ + async deleteFile(filename) { + try { + const filePath = path.join(this.assetsDir, filename) + await fs.remove(filePath) + return true + } catch (error) { + console.error('Error deleting local file:', error) + return false + } + } + + /** + * List all files in the assets directory + * @returns {Promise} Array of filenames + */ + async listFiles() { + try { + const files = await fs.readdir(this.assetsDir) + return files.filter(file => { + const filePath = path.join(this.assetsDir, file) + const stat = fs.statSync(filePath) + return stat.isFile() && file.split('.')[0].length === 64 // Only hashed assets + }) + } catch (error) { + console.error('Error listing local files:', error) + return [] + } + } + + /** + * Get file stats + * @param {string} filename - The filename + * @returns {Promise} File stats or null if not found + */ + async getFileStats(filename) { + try { + const filePath = path.join(this.assetsDir, filename) + const stats = await fs.stat(filePath) + return { + size: stats.size, + created: stats.birthtime, + modified: stats.mtime, + } + } catch (error) { + return null + } + } + + /** + * Upload a collection file + * @param {string} filename - The collection filename + * @param {Buffer} buffer - The file data + * @returns {Promise} The local path of the file + */ + async uploadCollection(filename, buffer) { + const filePath = path.join(this.collectionsDir, filename) + await fs.writeFile(filePath, buffer) + console.log(`Collection saved locally: ${filename}`) + return filePath + } + + /** + * Read a collection file + * @param {string} filename - The collection filename + * @returns {Promise} The file data or null if not found + */ + async readCollection(filename) { + try { + const filePath = path.join(this.collectionsDir, filename) + return await fs.readFile(filePath) + } catch (error) { + return null + } + } + + /** + * List collection files + * @returns {Promise} Array of collection filenames + */ + async listCollections() { + try { + return await fs.readdir(this.collectionsDir) + } catch (error) { + console.error('Error listing collections:', error) + return [] + } + } + + /** + * Save storage.json data + * @param {object} data - The storage data + * @returns {Promise} + */ + async saveStorageData(data) { + const filePath = path.join(this.worldDir, 'storage.json') + await fs.writeJson(filePath, data) + } + + /** + * Load storage.json data + * @returns {Promise} The storage data + */ + async loadStorageData() { + try { + const filePath = path.join(this.worldDir, 'storage.json') + return await fs.readJson(filePath) + } catch (error) { + return {} + } + } +} \ No newline at end of file diff --git a/src/server/S3Storage.js b/src/server/S3Storage.js new file mode 100644 index 00000000..89eb75a0 --- /dev/null +++ b/src/server/S3Storage.js @@ -0,0 +1,502 @@ +import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import fs from 'fs-extra' +import path from 'path' + +export class S3Storage { + constructor(config) { + this.bucketName = config.bucketName + this.region = config.region || 'us-east-1' + this.assetsPrefix = config.assetsPrefix || 'assets/' + this.collectionsPrefix = config.collectionsPrefix || 'collections/' + this.storagePrefix = config.storagePrefix || 'storage/' + this.cloudfrontUrl = config.cloudfrontUrl // Optional CloudFront URL + + this.client = new S3Client({ + region: this.region, + credentials: config.credentials || { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, + }) + } + + /** + * Initialize S3 storage (validate connection and bucket access) + */ + async initialize() { + try { + // Test S3 connection by listing objects (with limit to avoid large responses) + const command = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: this.assetsPrefix, + MaxKeys: 1, + }) + + await this.client.send(command) + console.log(`S3 storage initialized successfully: bucket=${this.bucketName}, region=${this.region}`) + + // Copy built-in assets and collections if they don't exist + await this.copyBuiltInContent() + + } catch (error) { + console.error('Failed to initialize S3 storage:', error) + throw new Error(`S3 initialization failed: ${error.message}`) + } + } + + /** + * Copy built-in assets and collections to S3 if they don't exist + */ + async copyBuiltInContent() { + try { + // Get root directory - assuming this is called from the server context + const rootDir = path.join(__dirname, '../') + const builtInAssetsDir = path.join(rootDir, 'src/world/assets') + const builtInCollectionsDir = path.join(rootDir, 'src/world/collections') + + console.log('[S3] Checking for built-in content to copy...') + + // Copy built-in assets + if (await fs.exists(builtInAssetsDir)) { + console.log('[S3] Copying built-in assets...') + await this.copyDirectoryToS3(builtInAssetsDir, this.assetsPrefix) + } + + // Copy built-in collections + if (await fs.exists(builtInCollectionsDir)) { + console.log('[S3] Copying built-in collections...') + await this.copyDirectoryToS3(builtInCollectionsDir, this.collectionsPrefix) + } + + console.log('[S3] Built-in content copy completed') + + } catch (error) { + console.error('[S3] Error copying built-in content:', error.message) + // Don't throw - this is not critical for S3 initialization + } + } + + /** + * Recursively copy a directory to S3 + */ + async copyDirectoryToS3(localDir, s3Prefix) { + const files = await fs.readdir(localDir, { withFileTypes: true }) + + for (const file of files) { + const localPath = path.join(localDir, file.name) + const s3Key = `${s3Prefix}${file.name}` + + if (file.isDirectory()) { + // Recursively copy subdirectory + const subS3Prefix = `${s3Prefix}${file.name}/` + await this.copyDirectoryToS3(localPath, subS3Prefix) + } else { + // Check if file already exists in S3 + try { + const headCommand = new HeadObjectCommand({ + Bucket: this.bucketName, + Key: s3Key, + }) + await this.client.send(headCommand) + console.log(`[S3] File already exists, skipping: ${s3Key}`) + } catch (error) { + if (error.name === 'NotFound') { + // File doesn't exist, copy it + try { + const fileBuffer = await fs.readFile(localPath) + const contentType = this.getContentType(file.name) + + const putCommand = new PutObjectCommand({ + Bucket: this.bucketName, + Key: s3Key, + Body: fileBuffer, + ContentType: contentType, + }) + + await this.client.send(putCommand) + console.log(`[S3] Copied file: ${s3Key}`) + } catch (copyError) { + console.error(`[S3] Error copying file ${localPath}:`, copyError.message) + } + } else { + console.error(`[S3] Error checking file ${s3Key}:`, error.message) + } + } + } + } + } + + /** + * Get content type based on file extension + */ + getContentType(filename) { + const ext = filename.toLowerCase().split('.').pop() + const mimeTypes = { + 'json': 'application/json', + 'js': 'application/javascript', + 'glb': 'application/octet-stream', + 'gltf': 'application/json', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'png': 'image/png', + 'gif': 'image/gif', + 'webp': 'image/webp', + 'mp3': 'audio/mpeg', + 'wav': 'audio/wav', + 'mp4': 'video/mp4', + 'webm': 'video/webm', + 'txt': 'text/plain', + 'html': 'text/html', + 'css': 'text/css', + } + return mimeTypes[ext] || 'application/octet-stream' + } + + /** + * Get the base S3 URL + */ + getS3BaseUrl() { + return `https://${this.bucketName}.s3.${this.region}.amazonaws.com` + } + + /** + * Get the CloudFront URL if configured, otherwise S3 URL + */ + getAssetsBaseUrl() { + if (this.cloudfrontUrl) { + return this.cloudfrontUrl.endsWith('/') ? this.cloudfrontUrl.slice(0, -1) : this.cloudfrontUrl + } + return this.getS3BaseUrl() + } + + /** + * Upload a file to S3 + * @param {string} filename - The filename to use in S3 + * @param {Buffer} buffer - The file data + * @param {string} contentType - The MIME type of the file + * @returns {Promise} The S3 URL of the uploaded file + */ + async uploadFile(filename, buffer, contentType) { + const key = `${this.assetsPrefix}${filename}` + + const command = new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + Body: buffer, + ContentType: contentType, + CacheControl: 'public, max-age=31536000, immutable', // 1 year cache + }) + + await this.client.send(command) + + // Return the public URL + return `${this.getS3BaseUrl()}/${key}` + } + + /** + * Check if a file exists in S3 + * @param {string} filename - The filename to check + * @returns {Promise} Whether the file exists + */ + async fileExists(filename) { + try { + const key = `${this.assetsPrefix}${filename}` + const command = new HeadObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + await this.client.send(command) + return true + } catch (error) { + if (error.name === 'NotFound') { + return false + } + throw error + } + } + + /** + * Get a signed URL for direct upload (optional feature for client-side uploads) + * @param {string} filename - The filename + * @param {string} contentType - The MIME type + * @param {number} expiresIn - URL expiration time in seconds (default: 300) + * @returns {Promise} The signed URL + */ + async getPresignedUploadUrl(filename, contentType, expiresIn = 300) { + const key = `${this.assetsPrefix}${filename}` + + const command = new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + ContentType: contentType, + CacheControl: 'public, max-age=31536000, immutable', + }) + + return await getSignedUrl(this.client, command, { expiresIn }) + } + + /** + * Get the public URL for a file + * @param {string} filename - The filename + * @returns {string} The public URL + */ + getPublicUrl(filename) { + const assetsPrefix = this.assetsPrefix.endsWith('/') ? this.assetsPrefix.slice(0, -1) : this.assetsPrefix + return `${this.getAssetsBaseUrl()}/${assetsPrefix}/${filename}` + } + + /** + * Get a presigned URL for accessing a file (alternative to public URLs) + * @param {string} filename - The filename + * @param {number} expiresIn - URL expiration time in seconds (default: 3600) + * @returns {Promise} The presigned URL + */ + async getPresignedUrl(filename, expiresIn = 3600) { + const key = `${this.assetsPrefix}${filename}` + + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + return await getSignedUrl(this.client, command, { expiresIn }) + } + + /** + * Get a signed URL for downloading a file (for private buckets) + * @param {string} filename - The filename + * @param {number} expiresIn - URL expiration time in seconds (default: 3600) + * @returns {Promise} The signed URL + */ + async getPresignedDownloadUrl(filename, expiresIn = 3600) { + const key = `${this.assetsPrefix}${filename}` + + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + return await getSignedUrl(this.client, command, { expiresIn }) + } + + /** + * Delete a file from S3 + * @param {string} filename - The filename to delete + * @returns {Promise} Whether the deletion was successful + */ + async deleteFile(filename) { + try { + const key = `${this.assetsPrefix}${filename}` + const command = new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + await this.client.send(command) + return true + } catch (error) { + console.error('Error deleting S3 file:', error) + return false + } + } + + /** + * List all files in S3 with the assets prefix + * @returns {Promise} Array of filenames + */ + async listFiles() { + try { + const files = [] + let continuationToken = undefined + + do { + const command = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: this.assetsPrefix, + ContinuationToken: continuationToken, + }) + + const response = await this.client.send(command) + + if (response.Contents) { + for (const object of response.Contents) { + const filename = object.Key.replace(this.assetsPrefix, '') + // Only include hashed assets (64 character hash) + if (filename.split('.')[0].length === 64) { + files.push(filename) + } + } + } + + continuationToken = response.NextContinuationToken + } while (continuationToken) + + return files + } catch (error) { + console.error('Error listing S3 files:', error) + return [] + } + } + + /** + * Get file stats from S3 + * @param {string} filename - The filename + * @returns {Promise} File stats or null if not found + */ + async getFileStats(filename) { + try { + const key = `${this.assetsPrefix}${filename}` + const command = new HeadObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + const response = await this.client.send(command) + return { + size: response.ContentLength, + created: response.LastModified, + modified: response.LastModified, + etag: response.ETag, + contentType: response.ContentType, + } + } catch (error) { + if (error.name === 'NotFound') { + return null + } + throw error + } + } + + /** + * Upload a collection file to S3 + * @param {string} filename - The collection filename + * @param {Buffer} buffer - The file data + * @returns {Promise} The S3 key of the file + */ + async uploadCollection(filename, buffer) { + const key = `${this.collectionsPrefix}${filename}` + + const command = new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + Body: buffer, + ContentType: 'application/json', + }) + + await this.client.send(command) + console.log(`Collection saved to S3: ${filename}`) + return key + } + + /** + * Read a collection file from S3 + * @param {string} filename - The collection filename + * @returns {Promise} The file data or null if not found + */ + async readCollection(filename) { + try { + const key = `${this.collectionsPrefix}${filename}` + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + const response = await this.client.send(command) + const chunks = [] + for await (const chunk of response.Body) { + chunks.push(chunk) + } + return Buffer.concat(chunks) + } catch (error) { + if (error.name === 'NoSuchKey') { + return null + } + throw error + } + } + + /** + * List collection files in S3 + * @returns {Promise} Array of collection filenames + */ + async listCollections() { + try { + const files = [] + let continuationToken = undefined + + do { + const command = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: this.collectionsPrefix, + ContinuationToken: continuationToken, + }) + + const response = await this.client.send(command) + + if (response.Contents) { + for (const object of response.Contents) { + const filename = object.Key.replace(this.collectionsPrefix, '') + if (filename) { + files.push(filename) + } + } + } + + continuationToken = response.NextContinuationToken + } while (continuationToken) + + return files + } catch (error) { + console.error('Error listing S3 collections:', error) + return [] + } + } + + /** + * Save storage.json data to S3 + * @param {object} data - The storage data + * @returns {Promise} + */ + async saveStorageData(data) { + const key = `${this.storagePrefix}storage.json` + const buffer = Buffer.from(JSON.stringify(data, null, 2)) + + const command = new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + Body: buffer, + ContentType: 'application/json', + }) + + await this.client.send(command) + } + + /** + * Load storage.json data from S3 + * @returns {Promise} The storage data + */ + async loadStorageData() { + try { + const key = `${this.storagePrefix}storage.json` + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key, + }) + + const response = await this.client.send(command) + const chunks = [] + for await (const chunk of response.Body) { + chunks.push(chunk) + } + const buffer = Buffer.concat(chunks) + return JSON.parse(buffer.toString()) + } catch (error) { + if (error.name === 'NoSuchKey') { + return {} + } + throw error + } + } +} \ No newline at end of file diff --git a/src/server/StorageManager.js b/src/server/StorageManager.js new file mode 100644 index 00000000..8719b798 --- /dev/null +++ b/src/server/StorageManager.js @@ -0,0 +1,246 @@ +import { FileStorage } from './FileStorage.js' +import { S3Storage } from './S3Storage.js' + +export class StorageManager { + constructor() { + this.storage = null + this.isS3 = false + } + + /** + * Initialize storage based on environment configuration + */ + async initialize() { + if (process.env.S3_BUCKET_NAME) { + // Initialize S3 storage + this.isS3 = true + this.storage = new S3Storage({ + bucketName: process.env.S3_BUCKET_NAME, + region: process.env.S3_REGION || 'us-east-1', + assetsPrefix: process.env.S3_ASSETS_PREFIX || 'assets/', + collectionsPrefix: process.env.S3_COLLECTIONS_PREFIX || 'collections/', + storagePrefix: process.env.S3_STORAGE_PREFIX || 'storage/', + cloudfrontUrl: process.env.CLOUDFRONT_URL, // Optional CloudFront URL + }) + + console.log('Initializing S3 storage...') + await this.storage.initialize() + + } else { + // Initialize local file storage + this.isS3 = false + this.storage = new FileStorage({ + assetsUrl: '/assets/', + }) + + console.log('Initializing local file storage...') + await this.storage.initialize() + } + } + + /** + * Get the assets URL based on storage type and CloudFront configuration + * @returns {string} The assets URL + */ + getAssetsUrl() { + if (this.isS3) { + // If CloudFront URL is configured, use it with assets prefix + if (process.env.CLOUDFRONT_URL) { + const baseUrl = process.env.CLOUDFRONT_URL.endsWith('/') + ? process.env.CLOUDFRONT_URL.slice(0, -1) // Remove trailing slash + : process.env.CLOUDFRONT_URL + const assetsPrefix = (process.env.S3_ASSETS_PREFIX || 'assets/').replace(/\/$/, '') // Remove trailing slash + return `${baseUrl}/${assetsPrefix}` + } + // Otherwise use S3 direct URL + const assetsPrefix = (process.env.S3_ASSETS_PREFIX || 'assets/').replace(/\/$/, '') // Remove trailing slash + return `https://${process.env.S3_BUCKET_NAME}.s3.${process.env.S3_REGION || 'us-east-1'}.amazonaws.com/${assetsPrefix}` + } else { + return process.env.PUBLIC_ASSETS_URL || '/assets' // No trailing slash for local + } + } + + /** + * Get paths for local storage (when not using S3) + * @returns {object|null} Object with paths or null if using S3 + */ + getPaths() { + if (!this.isS3 && this.storage.getPaths) { + return this.storage.getPaths() + } + return null + } + + /** + * Check if using S3 storage + * @returns {boolean} + */ + isUsingS3() { + return this.isS3 + } + + /** + * Upload a file + * @param {string} filename - The filename + * @param {Buffer} buffer - The file data + * @param {string} contentType - The MIME type + * @returns {Promise} The file URL + */ + async uploadFile(filename, buffer, contentType) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.uploadFile(filename, buffer, contentType) + } + + /** + * Check if a file exists + * @param {string} filename - The filename + * @returns {Promise} + */ + async fileExists(filename) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.fileExists(filename) + } + + /** + * Get the public URL for a file + * @param {string} filename - The filename + * @returns {string} + */ + getPublicUrl(filename) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return this.storage.getPublicUrl(filename) + } + + /** + * Delete a file + * @param {string} filename - The filename + * @returns {Promise} + */ + async deleteFile(filename) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.deleteFile(filename) + } + + /** + * List all files + * @returns {Promise} + */ + async listFiles() { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.listFiles() + } + + /** + * Get file stats + * @param {string} filename - The filename + * @returns {Promise} + */ + async getFileStats(filename) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.getFileStats(filename) + } + + /** + * Upload a collection file + * @param {string} filename - The collection filename + * @param {Buffer} buffer - The file data + * @returns {Promise} The file path/key + */ + async uploadCollection(filename, buffer) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.uploadCollection(filename, buffer) + } + + /** + * Read a collection file + * @param {string} filename - The collection filename + * @returns {Promise} The file data or null if not found + */ + async readCollection(filename) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.readCollection(filename) + } + + /** + * List collection files + * @returns {Promise} Array of collection filenames + */ + async listCollections() { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.listCollections() + } + + /** + * Save storage.json data + * @param {object} data - The storage data + * @returns {Promise} + */ + async saveStorageData(data) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.saveStorageData(data) + } + + /** + * Load storage.json data + * @returns {Promise} The storage data + */ + async loadStorageData() { + if (!this.storage) { + throw new Error('Storage not initialized') + } + return await this.storage.loadStorageData() + } + + /** + * Get signed upload URL (S3 only) + * @param {string} filename - The filename + * @param {string} contentType - The MIME type + * @param {number} expiresIn - Expiration time in seconds + * @returns {Promise} + */ + async getPresignedUploadUrl(filename, contentType, expiresIn = 300) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + if (!this.isS3) { + throw new Error('Presigned URLs are only available with S3 storage') + } + return await this.storage.getPresignedUploadUrl(filename, contentType, expiresIn) + } + + /** + * Get signed download URL (S3 only) + * @param {string} filename - The filename + * @param {number} expiresIn - Expiration time in seconds + * @returns {Promise} + */ + async getPresignedDownloadUrl(filename, expiresIn = 3600) { + if (!this.storage) { + throw new Error('Storage not initialized') + } + if (!this.isS3) { + throw new Error('Presigned URLs are only available with S3 storage') + } + return await this.storage.getPresignedDownloadUrl(filename, expiresIn) + } +} \ No newline at end of file diff --git a/src/server/collectionsManager.js b/src/server/collectionsManager.js new file mode 100644 index 00000000..bd4dd53f --- /dev/null +++ b/src/server/collectionsManager.js @@ -0,0 +1,267 @@ +import fs from 'fs-extra' +import path from 'path' +import { importApp } from '../core/extras/appTools' + +export async function initCollections({ storageManager }) { + const collections = [] + + if (storageManager.isUsingS3()) { + // Load collections from S3 + await initCollectionsFromS3(storageManager, collections) + } else { + // Load collections from local filesystem + const paths = storageManager.getPaths() + await initCollectionsFromLocal(paths.collectionsDir, paths.assetsDir, collections) + } + + return collections +} + +async function initCollectionsFromS3(storageManager, collections) { + try { + console.log('[Collections] Loading collections from S3...') + // List all collection files in S3 + const collectionFiles = await storageManager.listCollections() + console.log(`[Collections] Found ${collectionFiles.length} files in S3`) + + // Group files by collection (manifest files indicate collection folders) + const collectionManifests = collectionFiles.filter(file => file.endsWith('/manifest.json')) + console.log(`[Collections] Found ${collectionManifests.length} manifest files`) + + for (const manifestFile of collectionManifests) { + const collectionId = manifestFile.replace('/manifest.json', '') + console.log(`[Collections] Processing collection: ${collectionId}`) + + try { + // Read manifest + const manifestBuffer = await storageManager.readCollection(manifestFile) + if (!manifestBuffer) { + console.warn(`[Collections] Manifest buffer is empty for: ${manifestFile}`) + continue + } + + const manifest = JSON.parse(manifestBuffer.toString()) + const blueprints = [] + + console.log(`[Collections] Collection ${collectionId} has ${manifest.apps?.length || 0} apps`) + + // Process each app in the manifest + for (const appFilename of manifest.apps || []) { + const appFile = `${collectionId}/${appFilename}` + console.log(`[Collections] Loading app: ${appFile}`) + + try { + const appBuffer = await storageManager.readCollection(appFile) + + if (!appBuffer) { + console.warn(`[Collections] App file not found in S3: ${appFile}`) + continue + } + + const file = new File([appBuffer], appFilename, { + type: 'application/octet-stream', + }) + + // FOR BUILT-IN COLLECTIONS (like "default"), don't process assets - keep simple references + // Only process assets for user-uploaded collections + if (collectionId === 'default' || collectionId.startsWith('built-in')) { + // For built-in collections, import but don't upload/process assets - they should already exist as built-ins + const app = await importApp(file) + console.log(`[Collections] Built-in app ${appFilename} imported with ${app.assets?.length || 0} assets (keeping original references)`) + + // Don't upload/process assets for built-in collections - they should reference built-in assets directly + // The assets should already be available as simple names like emote-idle.glb, crash-block.glb, etc. + + blueprints.push(app.blueprint) + } else { + // For user collections, process assets normally (with hashing) + const app = await importApp(file) + console.log(`[Collections] User app ${appFilename} imported successfully, has ${app.assets?.length || 0} assets`) + + // Upload assets to S3 if they don't exist + for (const asset of app.assets || []) { + const assetFilename = asset.url.slice(8) // remove 'asset://' prefix + console.log(`[Collections] Checking asset: ${assetFilename}`) + + try { + const exists = await storageManager.fileExists(assetFilename) + + if (!exists) { + console.log(`[Collections] Uploading missing asset: ${assetFilename}`) + const arrayBuffer = await asset.file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const contentType = asset.file.type || 'application/octet-stream' + await storageManager.uploadFile(assetFilename, buffer, contentType) + console.log(`[Collections] Asset uploaded successfully: ${assetFilename}`) + } else { + console.log(`[Collections] Asset already exists: ${assetFilename}`) + } + } catch (assetError) { + console.error(`[Collections] Error handling asset ${assetFilename}:`, assetError.message) + // Continue with other assets even if one fails + } + } + + blueprints.push(app.blueprint) + } + + } catch (appError) { + console.error(`[Collections] Error processing app ${appFile}:`, appError.message) + // Continue with other apps even if one fails + } + } + + if (blueprints.length > 0) { + collections.push({ + id: collectionId, + name: manifest.name, + blueprints, + }) + console.log(`[Collections] Successfully loaded collection ${collectionId} with ${blueprints.length} blueprints`) + } else { + console.warn(`[Collections] Collection ${collectionId} has no valid blueprints`) + } + + } catch (error) { + console.error(`[Collections] Error processing collection ${collectionId}:`, error.message) + } + } + + // Sort collections (default first, then alphabetically) + collections.sort((a, b) => { + if (a.id === 'default') return -1 + if (b.id === 'default') return 1 + return a.id.localeCompare(b.id) + }) + + console.log(`[Collections] Successfully loaded ${collections.length} collections from S3`) + + } catch (error) { + console.error('[Collections] Error loading collections from S3:', error) + } +} + +async function initCollectionsFromLocal(collectionsDir, assetsDir, collections) { + try { + console.log('[Collections] Loading collections from local filesystem...') + let folderNames = fs.readdirSync(collectionsDir) + folderNames.sort((a, b) => { + // keep "default" first then sort alphabetically + if (a === 'default') return -1 + if (b === 'default') return 1 + return a.localeCompare(b) + }) + + console.log(`[Collections] Found ${folderNames.length} collection folders`) + + for (const folderName of folderNames) { + const folderPath = path.join(collectionsDir, folderName) + const stats = fs.statSync(folderPath) + if (!stats.isDirectory()) continue + + console.log(`[Collections] Processing local collection: ${folderName}`) + + const manifestPath = path.join(folderPath, 'manifest.json') + if (!fs.existsSync(manifestPath)) { + console.warn(`[Collections] No manifest.json found in: ${folderPath}`) + continue + } + + try { + const manifest = fs.readJsonSync(manifestPath) + const blueprints = [] + + console.log(`[Collections] Collection ${folderName} has ${manifest.apps?.length || 0} apps`) + + for (const appFilename of manifest.apps || []) { + const appPath = path.join(folderPath, appFilename) + console.log(`[Collections] Loading local app: ${appPath}`) + + try { + const appBuffer = fs.readFileSync(appPath) + const appFile = new File([appBuffer], appFilename, { + type: 'application/octet-stream', + }) + + const app = await importApp(appFile) + console.log(`[Collections] Local app ${appFilename} imported successfully, has ${app.assets?.length || 0} assets`) + + // Save assets to local filesystem if they don't exist + for (const asset of app.assets || []) { + const file = asset.file + const assetFilename = asset.url.slice(8) // remove 'asset://' prefix + const assetPath = path.join(assetsDir, assetFilename) + + try { + const exists = await fs.exists(assetPath) + if (exists) { + console.log(`[Collections] Local asset already exists: ${assetFilename}`) + continue + } + + console.log(`[Collections] Saving missing local asset: ${assetFilename}`) + const arrayBuffer = await file.arrayBuffer() + await fs.writeFile(assetPath, Buffer.from(arrayBuffer)) + console.log(`[Collections] Local asset saved successfully: ${assetFilename}`) + } catch (assetError) { + console.error(`[Collections] Error handling local asset ${assetFilename}:`, assetError.message) + // Continue with other assets even if one fails + } + } + + blueprints.push(app.blueprint) + + } catch (appError) { + console.error(`[Collections] Error processing local app ${appFilename}:`, appError.message) + // Continue with other apps even if one fails + } + } + + if (blueprints.length > 0) { + collections.push({ + id: folderName, + name: manifest.name, + blueprints, + }) + console.log(`[Collections] Successfully loaded local collection ${folderName} with ${blueprints.length} blueprints`) + } else { + console.warn(`[Collections] Local collection ${folderName} has no valid blueprints`) + } + + } catch (error) { + console.error(`[Collections] Error processing local collection ${folderName}:`, error.message) + } + } + + console.log(`[Collections] Successfully loaded ${collections.length} collections from local filesystem`) + + } catch (error) { + console.error('[Collections] Error loading collections from local filesystem:', error) + } +} + +/** + * Upload a collection to storage (S3 or local) + * @param {object} storageManager - The storage manager instance + * @param {string} collectionId - The collection ID + * @param {object} manifest - The collection manifest + * @param {Array} appFiles - Array of app files to upload + */ +export async function uploadCollection(storageManager, collectionId, manifest, appFiles) { + try { + // Upload manifest + const manifestBuffer = Buffer.from(JSON.stringify(manifest, null, 2)) + await storageManager.uploadCollection(`${collectionId}/manifest.json`, manifestBuffer) + + // Upload app files + for (const appFile of appFiles) { + const appBuffer = Buffer.from(await appFile.arrayBuffer()) + await storageManager.uploadCollection(`${collectionId}/${appFile.name}`, appBuffer) + } + + console.log(`Collection ${collectionId} uploaded successfully`) + } catch (error) { + console.error(`Error uploading collection ${collectionId}:`, error) + throw error + } +} \ No newline at end of file diff --git a/src/server/index.js b/src/server/index.js index a6813659..fe74b922 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -15,38 +15,38 @@ import multipart from '@fastify/multipart' import { createServerWorld } from '../core/createServerWorld' import { hashFile } from '../core/utils-server' import { getDB } from './db' -import { Storage } from './Storage' -import { initCollections } from './collections' +import { CloudStorage } from './CloudStorage' +import { StorageManager } from './StorageManager' +import { initCollections } from './collectionsManager' -const rootDir = path.join(__dirname, '../') -const worldDir = path.join(rootDir, process.env.WORLD) -const assetsDir = path.join(worldDir, '/assets') -const collectionsDir = path.join(worldDir, '/collections') const port = process.env.PORT -// create world folders if needed -await fs.ensureDir(worldDir) -await fs.ensureDir(assetsDir) -await fs.ensureDir(collectionsDir) - -// copy over built-in assets and collections -await fs.copy(path.join(rootDir, 'src/world/assets'), path.join(assetsDir)) -await fs.copy(path.join(rootDir, 'src/world/collections'), path.join(collectionsDir)) +// Initialize storage manager +const storageManager = new StorageManager() +await storageManager.initialize() // init collections -const collections = await initCollections({ collectionsDir, assetsDir }) +const collections = await initCollections({ storageManager }) // init db -const db = await getDB(path.join(worldDir, '/db.sqlite')) +const paths = storageManager.getPaths() +const dbPath = paths ? path.join(paths.worldDir, '/db.sqlite') : './world/db.sqlite' +const db = await getDB(dbPath) // init storage -const storage = new Storage(path.join(worldDir, '/storage.json')) +const storage = new CloudStorage(storageManager) +await storage.init() // create world const world = createServerWorld() -world.assetsUrl = process.env.PUBLIC_ASSETS_URL +world.assetsUrl = storageManager.getAssetsUrl() world.collections.deserialize(collections) -world.init({ db, storage, assetsDir }) +world.init({ + db, + storage, + assetsDir: paths?.assetsDir || null, + storageManager +}) const fastify = Fastify({ logger: { level: 'error' } }) @@ -56,7 +56,7 @@ fastify.get('/', async (req, reply) => { const title = world.settings.title || 'World' const desc = world.settings.desc || '' const image = world.resolveURL(world.settings.image?.url) || '' - const url = process.env.PUBLIC_ASSETS_URL + const url = storageManager.getAssetsUrl() const filePath = path.join(__dirname, 'public', 'index.html') let html = fs.readFileSync(filePath, 'utf-8') html = html.replaceAll('{url}', url) @@ -75,16 +75,22 @@ fastify.register(statics, { res.setHeader('Expires', '0') }, }) -fastify.register(statics, { - root: assetsDir, - prefix: '/assets/', - decorateReply: false, - setHeaders: res => { - // all assets are hashed & immutable so we can use aggressive caching - res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') // 1 year - res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString()) // older browsers - }, -}) + +// Only register local assets serving if not using S3 +if (!storageManager.isUsingS3()) { + const assetsDir = paths.assetsDir + fastify.register(statics, { + root: assetsDir, + prefix: '/assets/', + decorateReply: false, + setHeaders: res => { + // all assets are hashed & immutable so we can use aggressive caching + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') // 1 year + res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString()) // older browsers + }, + }) +} + fastify.register(multipart, { limits: { fileSize: 100 * 1024 * 1024, // 100MB @@ -122,19 +128,41 @@ fastify.post('/api/upload', async (req, reply) => { // hash from buffer const hash = await hashFile(buffer) const filename = `${hash}.${ext}` - // save to fs - const filePath = path.join(assetsDir, filename) - const exists = await fs.exists(filePath) - if (!exists) { - await fs.writeFile(filePath, buffer) + + try { + const exists = await storageManager.fileExists(filename) + if (!exists) { + const contentType = file.mimetype || 'application/octet-stream' + const url = await storageManager.uploadFile(filename, buffer, contentType) + console.log(`File uploaded: ${filename}`) + } else { + console.log(`File already exists: ${filename}`) + } + + return reply.code(200).send({ + success: true, + filename, + url: storageManager.getPublicUrl(filename) + }) + } catch (error) { + console.error('Upload error:', error) + return reply.code(500).send({ + success: false, + error: 'Failed to upload file' + }) } }) fastify.get('/api/upload-check', async (req, reply) => { const filename = req.query.filename - const filePath = path.join(assetsDir, filename) - const exists = await fs.exists(filePath) - return { exists } + + try { + const exists = await storageManager.fileExists(filename) + return { exists, url: exists ? storageManager.getPublicUrl(filename) : null } + } catch (error) { + console.error('Upload check error:', error) + return reply.code(500).send({ error: 'Failed to check file' }) + } }) fastify.get('/health', async (request, reply) => { From fac1e840e162ea7a05e0577caae70752aa5189bb Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Fri, 6 Jun 2025 13:19:25 +0200 Subject: [PATCH 2/7] refactor: Reorganize storage architecture and fix asset processing - Move storage files to dedicated directory, rename S3Storage to AwsS3Storage, remove redundant files, integrate CloudStorage into StorageManager, fix built-in collections asset processing, unify S3/local logic, improve collection listing format consistency, fix URL issues, add detailed logging --- src/server/CloudStorage.js | 72 -------- src/server/Storage.js | 39 ----- src/server/collections.js | 47 ----- src/server/index.js | 34 +--- .../{S3Storage.js => storage/AwsS3Storage.js} | 14 +- src/server/{ => storage}/FileStorage.js | 25 ++- src/server/{ => storage}/StorageManager.js | 161 ++++++++++++++---- .../{ => storage}/collectionsManager.js | 133 ++++++++------- 8 files changed, 247 insertions(+), 278 deletions(-) delete mode 100644 src/server/CloudStorage.js delete mode 100644 src/server/Storage.js delete mode 100644 src/server/collections.js rename src/server/{S3Storage.js => storage/AwsS3Storage.js} (98%) rename src/server/{ => storage}/FileStorage.js (88%) rename src/server/{ => storage}/StorageManager.js (62%) rename src/server/{ => storage}/collectionsManager.js (68%) diff --git a/src/server/CloudStorage.js b/src/server/CloudStorage.js deleted file mode 100644 index 8fbfc5d1..00000000 --- a/src/server/CloudStorage.js +++ /dev/null @@ -1,72 +0,0 @@ -import { cloneDeep, throttle } from 'lodash-es' - -export class CloudStorage { - constructor(storageManager) { - this.storageManager = storageManager - this.data = {} - this.loaded = false - - // Throttle saves to avoid too many writes - this.save = throttle(() => this.persist(), 1000, { leading: true, trailing: true }) - } - - /** - * Initialize the storage by loading existing data - */ - async init() { - try { - this.data = await this.storageManager.loadStorageData() - this.loaded = true - console.log('Storage data loaded successfully') - } catch (err) { - console.error('Error loading storage data:', err) - this.data = {} - this.loaded = true - } - } - - get(key) { - if (!this.loaded) { - console.warn('Storage not yet loaded, returning undefined') - return undefined - } - return this.data[key] - } - - set(key, value) { - if (!this.loaded) { - console.warn('Storage not yet loaded, cannot set value') - return - } - - try { - // Ensure value is serializable - value = JSON.parse(JSON.stringify(value)) - this.data[key] = value - this.save() - } catch (err) { - console.error('Error setting storage value:', err) - } - } - - async persist() { - if (!this.loaded) { - console.warn('Storage not yet loaded, cannot persist') - return - } - - try { - await this.storageManager.saveStorageData(this.data) - // console.log('Storage data persisted successfully') - } catch (err) { - console.error('Failed to persist storage:', err) - } - } - - /** - * Force an immediate save (bypass throttling) - */ - async forcePersist() { - return await this.persist() - } -} \ No newline at end of file diff --git a/src/server/Storage.js b/src/server/Storage.js deleted file mode 100644 index e1ae8d1f..00000000 --- a/src/server/Storage.js +++ /dev/null @@ -1,39 +0,0 @@ -import fs from 'fs-extra' -import { cloneDeep, throttle } from 'lodash-es' - -export class Storage { - constructor(file) { - this.file = file - try { - this.data = fs.readJsonSync(this.file) - } catch (err) { - this.data = {} - } - this.save = throttle(() => this.persist(), 1000, { leading: true, trailing: true }) - } - - get(key) { - return this.data[key] - } - - set(key, value) { - try { - value = JSON.parse(JSON.stringify(value)) - this.data[key] = value - this.save() - } catch (err) { - console.error(err) - } - } - - async persist() { - // console.time('[storage] persist') - try { - await fs.writeJson(this.file, this.data) - } catch (err) { - console.error(err) - console.log('failed to persist storage') - } - // console.timeEnd('[storage] persist') - } -} diff --git a/src/server/collections.js b/src/server/collections.js deleted file mode 100644 index 7e54bb32..00000000 --- a/src/server/collections.js +++ /dev/null @@ -1,47 +0,0 @@ -import fs from 'fs-extra' -import path from 'path' -import { importApp } from '../core/extras/appTools' - -export async function initCollections({ collectionsDir, assetsDir }) { - let folderNames = fs.readdirSync(collectionsDir) - folderNames.sort((a, b) => { - // keep "default" first then sort alphabetically - if (a === 'default') return -1 - if (b === 'default') return 1 - return a.localeCompare(b) - }) - const collections = [] - for (const folderName of folderNames) { - const folderPath = path.join(collectionsDir, folderName) - const stats = fs.statSync(folderPath) - if (!stats.isDirectory()) continue - const manifestPath = path.join(folderPath, 'manifest.json') - if (!fs.existsSync(manifestPath)) continue - const manifest = fs.readJsonSync(manifestPath) - const blueprints = [] - for (const appFilename of manifest.apps) { - const appPath = path.join(folderPath, appFilename) - const appBuffer = fs.readFileSync(appPath) - const appFile = new File([appBuffer], appFilename, { - type: 'application/octet-stream', - }) - const app = await importApp(appFile) - for (const asset of app.assets) { - const file = asset.file - const assetFilename = asset.url.slice(8) // remove 'asset://' prefix - const assetPath = path.join(assetsDir, assetFilename) - const exists = await fs.exists(assetPath) - if (exists) continue - const arrayBuffer = await file.arrayBuffer() - await fs.writeFile(assetPath, Buffer.from(arrayBuffer)) - } - blueprints.push(app.blueprint) - } - collections.push({ - id: folderName, - name: manifest.name, - blueprints, - }) - } - return collections -} diff --git a/src/server/index.js b/src/server/index.js index fe74b922..7e2ba1d3 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -4,7 +4,6 @@ import './bootstrap' import fs from 'fs-extra' import path from 'path' -import { pipeline } from 'stream/promises' import Fastify from 'fastify' import ws from '@fastify/websocket' import cors from '@fastify/cors' @@ -15,9 +14,8 @@ import multipart from '@fastify/multipart' import { createServerWorld } from '../core/createServerWorld' import { hashFile } from '../core/utils-server' import { getDB } from './db' -import { CloudStorage } from './CloudStorage' -import { StorageManager } from './StorageManager' -import { initCollections } from './collectionsManager' +import { StorageManager } from './storage/StorageManager' +import { initCollections } from './storage/collectionsManager' const port = process.env.PORT @@ -29,21 +27,18 @@ await storageManager.initialize() const collections = await initCollections({ storageManager }) // init db -const paths = storageManager.getPaths() -const dbPath = paths ? path.join(paths.worldDir, '/db.sqlite') : './world/db.sqlite' +const dbPath = storageManager.getDbPath() const db = await getDB(dbPath) -// init storage -const storage = new CloudStorage(storageManager) -await storage.init() - // create world const world = createServerWorld() world.assetsUrl = storageManager.getAssetsUrl() world.collections.deserialize(collections) + +const paths = storageManager.getPaths() world.init({ db, - storage, + storage: storageManager, assetsDir: paths?.assetsDir || null, storageManager }) @@ -65,6 +60,7 @@ fastify.get('/', async (req, reply) => { html = html.replaceAll('{image}', image) reply.type('text/html').send(html) }) + fastify.register(statics, { root: path.join(__dirname, 'public'), prefix: '/', @@ -76,20 +72,8 @@ fastify.register(statics, { }, }) -// Only register local assets serving if not using S3 -if (!storageManager.isUsingS3()) { - const assetsDir = paths.assetsDir - fastify.register(statics, { - root: assetsDir, - prefix: '/assets/', - decorateReply: false, - setHeaders: res => { - // all assets are hashed & immutable so we can use aggressive caching - res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') // 1 year - res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString()) // older browsers - }, - }) -} +// Configure static file serving through StorageManager +storageManager.configureStaticServing(fastify, statics) fastify.register(multipart, { limits: { diff --git a/src/server/S3Storage.js b/src/server/storage/AwsS3Storage.js similarity index 98% rename from src/server/S3Storage.js rename to src/server/storage/AwsS3Storage.js index 89eb75a0..6c41b55b 100644 --- a/src/server/S3Storage.js +++ b/src/server/storage/AwsS3Storage.js @@ -1,9 +1,10 @@ -import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' -import fs from 'fs-extra' import path from 'path' +import fs from 'fs-extra' + +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3' -export class S3Storage { +export class AwsS3Storage { constructor(config) { this.bucketName = config.bucketName this.region = config.region || 'us-east-1' @@ -243,8 +244,9 @@ export class S3Storage { * @returns {string} The public URL */ getPublicUrl(filename) { - const assetsPrefix = this.assetsPrefix.endsWith('/') ? this.assetsPrefix.slice(0, -1) : this.assetsPrefix - return `${this.getAssetsBaseUrl()}/${assetsPrefix}/${filename}` + const baseUrl = this.getAssetsBaseUrl() + const assetsPrefix = this.assetsPrefix.replace(/\/$/, '') // Remove trailing slash + return `${baseUrl}/${assetsPrefix}/${filename}` } /** diff --git a/src/server/FileStorage.js b/src/server/storage/FileStorage.js similarity index 88% rename from src/server/FileStorage.js rename to src/server/storage/FileStorage.js index 68e9192a..05dd3448 100644 --- a/src/server/FileStorage.js +++ b/src/server/storage/FileStorage.js @@ -1,5 +1,5 @@ -import fs from 'fs-extra' import path from 'path' +import fs from 'fs-extra' export class FileStorage { constructor(config = {}) { @@ -171,7 +171,28 @@ export class FileStorage { */ async listCollections() { try { - return await fs.readdir(this.collectionsDir) + const files = [] + const folders = await fs.readdir(this.collectionsDir) + + for (const folder of folders) { + const folderPath = path.join(this.collectionsDir, folder) + const stat = await fs.stat(folderPath) + + if (stat.isDirectory()) { + // List files in the collection folder + const folderFiles = await fs.readdir(folderPath) + for (const file of folderFiles) { + const filePath = path.join(folderPath, file) + const fileStat = await fs.stat(filePath) + if (fileStat.isFile()) { + // Return in S3-style format: "foldername/filename" + files.push(`${folder}/${file}`) + } + } + } + } + + return files } catch (error) { console.error('Error listing collections:', error) return [] diff --git a/src/server/StorageManager.js b/src/server/storage/StorageManager.js similarity index 62% rename from src/server/StorageManager.js rename to src/server/storage/StorageManager.js index 8719b798..998e93ba 100644 --- a/src/server/StorageManager.js +++ b/src/server/storage/StorageManager.js @@ -1,10 +1,18 @@ +import path from 'path' +import { throttle } from 'lodash-es' + +import { AwsS3Storage } from './AwsS3Storage.js' import { FileStorage } from './FileStorage.js' -import { S3Storage } from './S3Storage.js' export class StorageManager { constructor() { this.storage = null this.isS3 = false + this.storageData = {} + this.storageLoaded = false + + // Throttle saves to avoid too many writes + this.saveStorageData = throttle(() => this.persistStorageData(), 1000, { leading: true, trailing: true }) } /** @@ -14,7 +22,7 @@ export class StorageManager { if (process.env.S3_BUCKET_NAME) { // Initialize S3 storage this.isS3 = true - this.storage = new S3Storage({ + this.storage = new AwsS3Storage({ bucketName: process.env.S3_BUCKET_NAME, region: process.env.S3_REGION || 'us-east-1', assetsPrefix: process.env.S3_ASSETS_PREFIX || 'assets/', @@ -36,6 +44,123 @@ export class StorageManager { console.log('Initializing local file storage...') await this.storage.initialize() } + + // Initialize storage data + await this.initStorageData() + } + + /** + * Initialize the storage data by loading existing storage.json + */ + async initStorageData() { + try { + this.storageData = await this.storage.loadStorageData() + this.storageLoaded = true + console.log('Storage data loaded successfully') + } catch (err) { + console.error('Error loading storage data:', err) + this.storageData = {} + this.storageLoaded = true + } + } + + /** + * Get a value from storage.json data + * @param {string} key - The key to retrieve + * @returns {any} The stored value + */ + getStorageValue(key) { + if (!this.storageLoaded) { + console.warn('Storage not yet loaded, returning undefined') + return undefined + } + return this.storageData[key] + } + + /** + * Set a value in storage.json data + * @param {string} key - The key to set + * @param {any} value - The value to store + */ + setStorageValue(key, value) { + if (!this.storageLoaded) { + console.warn('Storage not yet loaded, cannot set value') + return + } + + try { + // Ensure value is serializable + value = JSON.parse(JSON.stringify(value)) + this.storageData[key] = value + this.saveStorageData() + } catch (err) { + console.error('Error setting storage value:', err) + } + } + + /** + * Persist storage data (throttled) + */ + async persistStorageData() { + if (!this.storageLoaded) { + console.warn('Storage not yet loaded, cannot persist') + return + } + + try { + await this.storage.saveStorageData(this.storageData) + // console.log('Storage data persisted successfully') + } catch (err) { + console.error('Failed to persist storage:', err) + } + } + + /** + * Force an immediate save of storage data (bypass throttling) + */ + async forceStorageDataPersist() { + return await this.persistStorageData() + } + + /** + * Get a value from storage.json data (interface for world system) + * @param {string} key - The key to retrieve + * @returns {any} The stored value + */ + get(key) { + return this.getStorageValue(key) + } + + /** + * Set a value in storage.json data (interface for world system) + * @param {string} key - The key to set + * @param {any} value - The value to store + */ + set(key, value) { + this.setStorageValue(key, value) + } + + /** + * Configure Fastify instance with appropriate static file serving + * @param {object} fastify - The Fastify instance + * @param {object} statics - The @fastify/static plugin + */ + configureStaticServing(fastify, statics) { + if (!this.isS3) { + // Only register local assets serving when not using S3 + const paths = this.storage.getPaths() + fastify.register(statics, { + root: paths.assetsDir, + prefix: '/assets/', + decorateReply: false, + setHeaders: res => { + // all assets are hashed & immutable so we can use aggressive caching + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') // 1 year + res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString()) // older browsers + }, + }) + } + // For S3, files are served directly from S3/CloudFront, no local serving needed } /** @@ -72,11 +197,12 @@ export class StorageManager { } /** - * Check if using S3 storage - * @returns {boolean} + * Get the database path based on storage type + * @returns {string} The database path */ - isUsingS3() { - return this.isS3 + getDbPath() { + const paths = this.getPaths() + return paths ? path.join(paths.worldDir, '/db.sqlite') : './world/db.sqlite' } /** @@ -188,29 +314,6 @@ export class StorageManager { return await this.storage.listCollections() } - /** - * Save storage.json data - * @param {object} data - The storage data - * @returns {Promise} - */ - async saveStorageData(data) { - if (!this.storage) { - throw new Error('Storage not initialized') - } - return await this.storage.saveStorageData(data) - } - - /** - * Load storage.json data - * @returns {Promise} The storage data - */ - async loadStorageData() { - if (!this.storage) { - throw new Error('Storage not initialized') - } - return await this.storage.loadStorageData() - } - /** * Get signed upload URL (S3 only) * @param {string} filename - The filename diff --git a/src/server/collectionsManager.js b/src/server/storage/collectionsManager.js similarity index 68% rename from src/server/collectionsManager.js rename to src/server/storage/collectionsManager.js index bd4dd53f..4d4d320a 100644 --- a/src/server/collectionsManager.js +++ b/src/server/storage/collectionsManager.js @@ -1,32 +1,48 @@ -import fs from 'fs-extra' import path from 'path' -import { importApp } from '../core/extras/appTools' +import fs from 'fs-extra' + +import { importApp } from '../../core/extras/appTools' export async function initCollections({ storageManager }) { + console.log('[Collections] Starting collections initialization...') const collections = [] - if (storageManager.isUsingS3()) { - // Load collections from S3 - await initCollectionsFromS3(storageManager, collections) - } else { - // Load collections from local filesystem - const paths = storageManager.getPaths() - await initCollectionsFromLocal(paths.collectionsDir, paths.assetsDir, collections) + try { + // Load from collections storage (both S3 and local now use the same logic) + await initCollectionsFromStorage(storageManager, collections) + console.log(`[Collections] Total collections loaded: ${collections.length}`) + } catch (error) { + console.error('[Collections] Error initializing collections:', error) + // Ensure we always return an array, even if empty } + console.log('[Collections] Collections initialization complete') return collections } -async function initCollectionsFromS3(storageManager, collections) { +async function initCollectionsFromStorage(storageManager, collections) { try { - console.log('[Collections] Loading collections from S3...') - // List all collection files in S3 + console.log('[Collections] Loading collections from storage...') + + // List all collection files const collectionFiles = await storageManager.listCollections() - console.log(`[Collections] Found ${collectionFiles.length} files in S3`) + console.log(`[Collections] Found ${collectionFiles.length} files in storage:`, collectionFiles) + + // If no files found, just return - the main function will handle fallback + if (collectionFiles.length === 0) { + console.log('[Collections] No collection files found in storage') + return + } // Group files by collection (manifest files indicate collection folders) const collectionManifests = collectionFiles.filter(file => file.endsWith('/manifest.json')) - console.log(`[Collections] Found ${collectionManifests.length} manifest files`) + console.log(`[Collections] Found ${collectionManifests.length} manifest files:`, collectionManifests) + + // If no manifests found, return + if (collectionManifests.length === 0) { + console.log('[Collections] No manifest files found') + return + } for (const manifestFile of collectionManifests) { const collectionId = manifestFile.replace('/manifest.json', '') @@ -54,7 +70,7 @@ async function initCollectionsFromS3(storageManager, collections) { const appBuffer = await storageManager.readCollection(appFile) if (!appBuffer) { - console.warn(`[Collections] App file not found in S3: ${appFile}`) + console.warn(`[Collections] App file not found in storage: ${appFile}`) continue } @@ -62,49 +78,38 @@ async function initCollectionsFromS3(storageManager, collections) { type: 'application/octet-stream', }) - // FOR BUILT-IN COLLECTIONS (like "default"), don't process assets - keep simple references - // Only process assets for user-uploaded collections - if (collectionId === 'default' || collectionId.startsWith('built-in')) { - // For built-in collections, import but don't upload/process assets - they should already exist as built-ins - const app = await importApp(file) - console.log(`[Collections] Built-in app ${appFilename} imported with ${app.assets?.length || 0} assets (keeping original references)`) - - // Don't upload/process assets for built-in collections - they should reference built-in assets directly - // The assets should already be available as simple names like emote-idle.glb, crash-block.glb, etc. - - blueprints.push(app.blueprint) - } else { - // For user collections, process assets normally (with hashing) - const app = await importApp(file) - console.log(`[Collections] User app ${appFilename} imported successfully, has ${app.assets?.length || 0} assets`) + // Process assets for ALL collections (built-in and user collections) + // All collections need their assets to be properly saved to storage + const app = await importApp(file) + console.log(`[Collections] Collection ${collectionId} app ${appFilename} imported with ${app.assets?.length || 0} assets`) + + // Upload/save assets to storage if they don't exist + for (const asset of app.assets || []) { + const assetFilename = asset.url.slice(8) // remove 'asset://' prefix + console.log(`[Collections] Processing asset: ${assetFilename} for collection ${collectionId}`) - // Upload assets to S3 if they don't exist - for (const asset of app.assets || []) { - const assetFilename = asset.url.slice(8) // remove 'asset://' prefix - console.log(`[Collections] Checking asset: ${assetFilename}`) + try { + const exists = await storageManager.fileExists(assetFilename) + console.log(`[Collections] Asset ${assetFilename} exists: ${exists}`) - try { - const exists = await storageManager.fileExists(assetFilename) - - if (!exists) { - console.log(`[Collections] Uploading missing asset: ${assetFilename}`) - const arrayBuffer = await asset.file.arrayBuffer() - const buffer = Buffer.from(arrayBuffer) - const contentType = asset.file.type || 'application/octet-stream' - await storageManager.uploadFile(assetFilename, buffer, contentType) - console.log(`[Collections] Asset uploaded successfully: ${assetFilename}`) - } else { - console.log(`[Collections] Asset already exists: ${assetFilename}`) - } - } catch (assetError) { - console.error(`[Collections] Error handling asset ${assetFilename}:`, assetError.message) - // Continue with other assets even if one fails + if (!exists) { + console.log(`[Collections] Uploading missing asset: ${assetFilename}`) + const arrayBuffer = await asset.file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const contentType = asset.file.type || 'application/octet-stream' + await storageManager.uploadFile(assetFilename, buffer, contentType) + console.log(`[Collections] Asset uploaded successfully: ${assetFilename}`) + } else { + console.log(`[Collections] Asset already exists: ${assetFilename}`) } + } catch (assetError) { + console.error(`[Collections] Error handling asset ${assetFilename}:`, assetError.message) + // Continue with other assets even if one fails } - - blueprints.push(app.blueprint) } + blueprints.push(app.blueprint) + } catch (appError) { console.error(`[Collections] Error processing app ${appFile}:`, appError.message) // Continue with other apps even if one fails @@ -114,12 +119,18 @@ async function initCollectionsFromS3(storageManager, collections) { if (blueprints.length > 0) { collections.push({ id: collectionId, - name: manifest.name, - blueprints, + name: manifest.name || collectionId, + blueprints: blueprints || [], }) console.log(`[Collections] Successfully loaded collection ${collectionId} with ${blueprints.length} blueprints`) } else { console.warn(`[Collections] Collection ${collectionId} has no valid blueprints`) + // Still add the collection but with empty blueprints array + collections.push({ + id: collectionId, + name: manifest.name || collectionId, + blueprints: [], + }) } } catch (error) { @@ -134,10 +145,10 @@ async function initCollectionsFromS3(storageManager, collections) { return a.id.localeCompare(b.id) }) - console.log(`[Collections] Successfully loaded ${collections.length} collections from S3`) + console.log(`[Collections] Successfully loaded ${collections.length} collections from storage`) } catch (error) { - console.error('[Collections] Error loading collections from S3:', error) + console.error('[Collections] Error loading collections from storage:', error) } } @@ -220,12 +231,18 @@ async function initCollectionsFromLocal(collectionsDir, assetsDir, collections) if (blueprints.length > 0) { collections.push({ id: folderName, - name: manifest.name, - blueprints, + name: manifest.name || folderName, + blueprints: blueprints || [], }) console.log(`[Collections] Successfully loaded local collection ${folderName} with ${blueprints.length} blueprints`) } else { console.warn(`[Collections] Local collection ${folderName} has no valid blueprints`) + // Still add the collection but with empty blueprints array + collections.push({ + id: folderName, + name: manifest.name || folderName, + blueprints: [], + }) } } catch (error) { From 4945d97a72dc8f14edc0cafbf98123d9917369b9 Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Fri, 6 Jun 2025 15:22:29 +0200 Subject: [PATCH 3/7] chore: Remove some useless comments --- src/core/systems/ServerLoader.js | 48 ++------------------------------ 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/src/core/systems/ServerLoader.js b/src/core/systems/ServerLoader.js index c6d9d608..e830bfc1 100644 --- a/src/core/systems/ServerLoader.js +++ b/src/core/systems/ServerLoader.js @@ -64,12 +64,6 @@ export class ServerLoader extends System { async fetchArrayBuffer(url) { const isRemote = url.startsWith('http://') || url.startsWith('https://') - // Log específico para emote-idle - if (url.includes('emote-idle')) { - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Fetching ${url}`) - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: isRemote=${isRemote}`) - } - if (isRemote) { try { const response = await fetch(url) @@ -84,20 +78,10 @@ export class ServerLoader extends System { } const arrayBuffer = await response.arrayBuffer() - // Log específico para emote-idle - if (url.includes('emote-idle')) { - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Successfully fetched ${arrayBuffer.byteLength} bytes`) - } - return arrayBuffer } catch (error) { console.error(`Error fetching ${url}:`, error.message) - // Log específico para emote-idle - if (url.includes('emote-idle')) { - console.error(`[ServerLoader] EMOTE-IDLE DEBUG: FAILED to fetch - ${error.message}`) - } - throw error } } else { @@ -105,21 +89,10 @@ export class ServerLoader extends System { try { const filePath = url.startsWith('file://') ? url.slice(7) : url - // Log específico para emote-idle - if (url.includes('emote-idle')) { - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Loading local file ${filePath}`) - } - const buffer = await fs.readFile(filePath) return buffer.buffer } catch (error) { - console.error(`Error reading local file ${url}:`, error.message) - - // Log específico para emote-idle - if (url.includes('emote-idle')) { - console.error(`[ServerLoader] EMOTE-IDLE DEBUG: FAILED to read local file - ${error.message}`) - } - + console.error(`Error reading local file ${url}:`, error.message) throw error } } @@ -139,26 +112,11 @@ export class ServerLoader extends System { async loadModel(url) { const resolvedUrl = this.world.resolveURL(url, true) - console.log(`[ServerLoader] Loading model: ${url} -> ${resolvedUrl}`) - - // Log específico para emote-idle - if (url.includes('emote-idle') || resolvedUrl.includes('emote-idle')) { - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Original URL: ${url}`) - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Resolved URL: ${resolvedUrl}`) - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: World assetsUrl: ${this.world.assetsUrl}`) - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: World assetsDir: ${this.world.assetsDir}`) - } - + console.log(`[ServerLoader] Loading model: ${url} -> ${resolvedUrl}`) console.log(`[ServerLoader] Fetching model from: ${resolvedUrl}`) try { - const arrayBuffer = await this.fetchArrayBuffer(resolvedUrl) - - // Log específico para emote-idle - if (url.includes('emote-idle') || resolvedUrl.includes('emote-idle')) { - console.log(`[ServerLoader] EMOTE-IDLE DEBUG: Successfully loaded, size: ${arrayBuffer.byteLength} bytes`) - } - + const arrayBuffer = await this.fetchArrayBuffer(resolvedUrl) const loader = new THREE.GLTFLoader() const gltf = await new Promise((resolve, reject) => { From 3db7ba2539acbf1ff84225ac4b4d67770033a7a8 Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Sat, 7 Jun 2025 19:35:34 +0200 Subject: [PATCH 4/7] chore: Remove some useless methods --- src/core/systems/ServerLoader.js | 104 +------------------------------ 1 file changed, 3 insertions(+), 101 deletions(-) diff --git a/src/core/systems/ServerLoader.js b/src/core/systems/ServerLoader.js index e830bfc1..76ec3f68 100644 --- a/src/core/systems/ServerLoader.js +++ b/src/core/systems/ServerLoader.js @@ -65,36 +65,13 @@ export class ServerLoader extends System { const isRemote = url.startsWith('http://') || url.startsWith('https://') if (isRemote) { - try { const response = await fetch(url) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText} for ${url}`) - } - const contentType = response.headers.get('content-type') || '' - if (contentType.includes('text/xml') || contentType.includes('application/xml')) { - const text = await response.text() - console.error(`Received XML response instead of binary file for ${url}:`, text.substring(0, 200)) - throw new Error(`File not found or access denied: ${url}`) - } const arrayBuffer = await response.arrayBuffer() - return arrayBuffer - } catch (error) { - console.error(`Error fetching ${url}:`, error.message) - - throw error - } } else { - // Local file access - try { - const filePath = url.startsWith('file://') ? url.slice(7) : url - - const buffer = await fs.readFile(filePath) - return buffer.buffer - } catch (error) { - console.error(`Error reading local file ${url}:`, error.message) - throw error - } + const buffer = await fs.readFile(url) + const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) + return arrayBuffer } } @@ -110,81 +87,6 @@ export class ServerLoader extends System { } } - async loadModel(url) { - const resolvedUrl = this.world.resolveURL(url, true) - console.log(`[ServerLoader] Loading model: ${url} -> ${resolvedUrl}`) - console.log(`[ServerLoader] Fetching model from: ${resolvedUrl}`) - - try { - const arrayBuffer = await this.fetchArrayBuffer(resolvedUrl) - const loader = new THREE.GLTFLoader() - - const gltf = await new Promise((resolve, reject) => { - loader.parse(arrayBuffer, '', resolve, reject) - }) - - return gltf - - } catch (error) { - console.error(`[ServerLoader] Error loading model ${url}:`, error.message) - - // FALLBACK: If it's a hasheaded asset that failed, try to find a built-in equivalent - if (resolvedUrl.includes('.glb') && error.message.includes('404') || error.message.includes('403')) { - const fallbackUrl = this.tryGetBuiltInFallback(url, resolvedUrl) - if (fallbackUrl && fallbackUrl !== resolvedUrl) { - console.log(`[ServerLoader] Trying fallback for ${url}: ${fallbackUrl}`) - try { - const fallbackBuffer = await this.fetchArrayBuffer(fallbackUrl) - const loader = new THREE.GLTFLoader() - const gltf = await new Promise((resolve, reject) => { - loader.parse(fallbackBuffer, '', resolve, reject) - }) - console.log(`[ServerLoader] Fallback successful for ${url}`) - return gltf - } catch (fallbackError) { - console.error(`[ServerLoader] Fallback also failed for ${url}:`, fallbackError.message) - } - } - } - - throw error - } - } - - /** - * Try to find a built-in asset fallback for a failed hasheaded asset - */ - tryGetBuiltInFallback(originalUrl, resolvedUrl) { - // If it's already a built-in asset (not hasheaded), don't try fallback - if (!resolvedUrl.match(/[a-f0-9]{64}\.glb/)) { - return null - } - - // Common built-in assets that might be used as fallbacks - const builtInAssets = [ - 'crash-block.glb', - 'emote-idle.glb', - 'emote-walk.glb', - 'emote-run.glb', - 'emote-jump.glb', - 'emote-fall.glb', - 'emote-flip.glb', - 'emote-float.glb', - 'emote-talk.glb' - ] - - // For emote-related assets, try to match by name - for (const builtIn of builtInAssets) { - if (originalUrl.includes('emote') && builtIn.includes('emote-idle')) { - // Default emote fallback - return this.world.resolveURL(`asset://${builtIn}`, true) - } - } - - // Generic fallback for any .glb - use crash-block as a placeholder - return this.world.resolveURL('asset://crash-block.glb', true) - } - load(type, url) { const key = `${type}/${url}` if (this.promises.has(key)) { From a3a895d78a2ee0bef9321802479e641930920eb0 Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Sat, 7 Jun 2025 19:39:04 +0200 Subject: [PATCH 5/7] chore: include more needed venv variables --- .env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.example b/.env.example index f0fc51bd..c14c4b6f 100644 --- a/.env.example +++ b/.env.example @@ -30,10 +30,14 @@ PUBLIC_API_URL=http://localhost:3000/api # The public url used by clients to fetch assets PUBLIC_ASSETS_URL=http://localhost:3000/assets +# AWS S3 storage support (optional) S3_BUCKET_NAME=bucket-name AWS_ACCESS_KEY_ID=access-key-id AWS_SECRET_ACCESS_KEY=secret-key-id S3_REGION=eu-west-1 +S3_ASSETS_PREFIX= +S3_COLLECTIONS_PREFIX= +S3_STORAGE_PREFIX= # LiveKit (voice chat) LIVEKIT_WS_URL= From 4ffc1b6ab00eb751e9dbe58c46b186e2ede4c846 Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Sat, 7 Jun 2025 19:39:28 +0200 Subject: [PATCH 6/7] chore: Remove some extra lines --- src/core/systems/ServerLoader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/systems/ServerLoader.js b/src/core/systems/ServerLoader.js index 76ec3f68..89b52b2b 100644 --- a/src/core/systems/ServerLoader.js +++ b/src/core/systems/ServerLoader.js @@ -63,7 +63,7 @@ export class ServerLoader extends System { async fetchArrayBuffer(url) { const isRemote = url.startsWith('http://') || url.startsWith('https://') - + if (isRemote) { const response = await fetch(url) const arrayBuffer = await response.arrayBuffer() From a0ab3ce3a09057b86289a1ab5116aa3ddeed9fc5 Mon Sep 17 00:00:00 2001 From: DevStarlight Date: Sat, 7 Jun 2025 19:39:48 +0200 Subject: [PATCH 7/7] chore: Remove some extra lines --- src/core/systems/ServerLoader.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/systems/ServerLoader.js b/src/core/systems/ServerLoader.js index 89b52b2b..9633e22e 100644 --- a/src/core/systems/ServerLoader.js +++ b/src/core/systems/ServerLoader.js @@ -63,7 +63,6 @@ export class ServerLoader extends System { async fetchArrayBuffer(url) { const isRemote = url.startsWith('http://') || url.startsWith('https://') - if (isRemote) { const response = await fetch(url) const arrayBuffer = await response.arrayBuffer()