diff --git a/deploy/dev/docker-compose.cn.yml b/deploy/dev/docker-compose.cn.yml index d1f6772a6..4dda36a6d 100644 --- a/deploy/dev/docker-compose.cn.yml +++ b/deploy/dev/docker-compose.cn.yml @@ -60,7 +60,7 @@ services: '--authenticationDatabase', 'admin', '--eval', - "db.adminCommand('ping')" + "db.adminCommand('ping')", ] interval: 10s timeout: 5s @@ -162,16 +162,19 @@ services: - fastgpt environment: - AUTH_TOKEN=token - - S3_EXTERNAL_BASE_URL=http://127.0.0.1:9000 # TODO: 改为你 Minio 的实际的 ip 地址 - - S3_ENDPOINT=fastgpt-minio - - S3_PORT=9000 - - S3_USE_SSL=false - - S3_ACCESS_KEY=minioadmin - - S3_SECRET_KEY=minioadmin - - S3_PUBLIC_BUCKET=fastgpt-public # 系统工具,创建的临时文件,存储的桶,要求公开读私有写。 - - S3_PRIVATE_BUCKET=fastgpt-private # 系统插件热安装文件的桶,私有读写。 - MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin&directConnection=true - REDIS_URL=redis://default:mypassword@redis:6379 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + - STORAGE_VENDOR=minio # minio | aws-s3 | cos | oss + - STORAGE_REGION=us-east-1 + - STORAGE_ACCESS_KEY_ID=minioadmin + - STORAGE_SECRET_ACCESS_KEY=minioadmin + - STORAGE_PUBLIC_BUCKET=fastgpt-public + - STORAGE_PRIVATE_BUCKET=fastgpt-private + - STORAGE_EXTERNAL_ENDPOINT=https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + - STORAGE_S3_ENDPOINT=http://fastgpt-minio:9000 # 协议://域名(IP):端口 + - STORAGE_S3_FORCE_PATH_STYLE=true + - STORAGE_S3_MAX_RETRIES=3 depends_on: fastgpt-minio: condition: service_healthy diff --git a/deploy/dev/docker-compose.yml b/deploy/dev/docker-compose.yml index 313dbe6a0..71a1e1b53 100644 --- a/deploy/dev/docker-compose.yml +++ b/deploy/dev/docker-compose.yml @@ -162,14 +162,17 @@ services: - fastgpt environment: - AUTH_TOKEN=token - - S3_EXTERNAL_BASE_URL=http://127.0.0.1:9000 # TODO: 改为你 Minio 的实际的 ip 地址 - - S3_ENDPOINT=fastgpt-minio - - S3_PORT=9000 - - S3_USE_SSL=false - - S3_ACCESS_KEY=minioadmin - - S3_SECRET_KEY=minioadmin - - S3_PUBLIC_BUCKET=fastgpt-public # 系统工具,创建的临时文件,存储的桶,要求公开读私有写。 - - S3_PRIVATE_BUCKET=fastgpt-private # 系统插件热安装文件的桶,私有读写。 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + - STORAGE_VENDOR=minio # minio | aws-s3 | cos | oss + - STORAGE_REGION=us-east-1 + - STORAGE_ACCESS_KEY_ID=minioadmin + - STORAGE_SECRET_ACCESS_KEY=minioadmin + - STORAGE_PUBLIC_BUCKET=fastgpt-public + - STORAGE_PRIVATE_BUCKET=fastgpt-private + - STORAGE_EXTERNAL_ENDPOINT=https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + - STORAGE_S3_ENDPOINT=http://fastgpt-minio:9000 # 协议://域名(IP):端口 + - STORAGE_S3_FORCE_PATH_STYLE=true + - STORAGE_S3_MAX_RETRIES=3 - MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin&directConnection=true - REDIS_URL=redis://default:mypassword@redis:6379 depends_on: diff --git a/deploy/docker/cn/docker-compose.milvus.yml b/deploy/docker/cn/docker-compose.milvus.yml index a0c925899..8312c0e21 100644 --- a/deploy/docker/cn/docker-compose.milvus.yml +++ b/deploy/docker/cn/docker-compose.milvus.yml @@ -12,20 +12,23 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config MILVUS_ADDRESS: http://milvusStandalone:19530 MILVUS_TOKEN: none - + version: '3.3' services: diff --git a/deploy/docker/cn/docker-compose.oceanbase.yml b/deploy/docker/cn/docker-compose.oceanbase.yml index 334e53433..f65107eea 100644 --- a/deploy/docker/cn/docker-compose.oceanbase.yml +++ b/deploy/docker/cn/docker-compose.oceanbase.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config @@ -290,4 +293,4 @@ configs: name: init_sql content: | ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; - + diff --git a/deploy/docker/cn/docker-compose.pg.yml b/deploy/docker/cn/docker-compose.pg.yml index 0647f55fc..1ef8c8f2d 100644 --- a/deploy/docker/cn/docker-compose.pg.yml +++ b/deploy/docker/cn/docker-compose.pg.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config @@ -47,7 +50,6 @@ services: timeout: 5s retries: 10 - mongo: image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # cpu 不支持 AVX 时候使用 4.4.29 container_name: mongo @@ -61,7 +63,19 @@ services: volumes: - ./mongo/data:/data/db healthcheck: - test: ['CMD', 'mongo', '-u', 'myusername', '-p', 'mypassword', '--authenticationDatabase', 'admin', '--eval', "db.adminCommand('ping')"] + test: + [ + 'CMD', + 'mongo', + '-u', + 'myusername', + '-p', + 'mypassword', + '--authenticationDatabase', + 'admin', + '--eval', + "db.adminCommand('ping')", + ] interval: 10s timeout: 5s retries: 5 @@ -266,4 +280,3 @@ networks: fastgpt: aiproxy: vector: - diff --git a/deploy/docker/cn/docker-compose.zilliz.yml b/deploy/docker/cn/docker-compose.zilliz.yml index 7df9ba95d..a346eab25 100644 --- a/deploy/docker/cn/docker-compose.zilliz.yml +++ b/deploy/docker/cn/docker-compose.zilliz.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/deploy/docker/global/docker-compose.milvus.yml b/deploy/docker/global/docker-compose.milvus.yml index 68a1e4e20..8ed22fff4 100644 --- a/deploy/docker/global/docker-compose.milvus.yml +++ b/deploy/docker/global/docker-compose.milvus.yml @@ -12,20 +12,23 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config MILVUS_ADDRESS: http://milvusStandalone:19530 MILVUS_TOKEN: none - + version: '3.3' services: diff --git a/deploy/docker/global/docker-compose.oceanbase.yml b/deploy/docker/global/docker-compose.oceanbase.yml index 53a17652b..af8258766 100644 --- a/deploy/docker/global/docker-compose.oceanbase.yml +++ b/deploy/docker/global/docker-compose.oceanbase.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config @@ -290,4 +293,4 @@ configs: name: init_sql content: | ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; - + diff --git a/deploy/docker/global/docker-compose.pg.yml b/deploy/docker/global/docker-compose.pg.yml index f13ef6826..fb1771568 100644 --- a/deploy/docker/global/docker-compose.pg.yml +++ b/deploy/docker/global/docker-compose.pg.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config @@ -47,7 +50,6 @@ services: timeout: 5s retries: 10 - mongo: image: mongo:5.0.18 # cpu 不支持 AVX 时候使用 4.4.29 container_name: mongo @@ -61,7 +63,19 @@ services: volumes: - ./mongo/data:/data/db healthcheck: - test: ['CMD', 'mongo', '-u', 'myusername', '-p', 'mypassword', '--authenticationDatabase', 'admin', '--eval', "db.adminCommand('ping')"] + test: + [ + 'CMD', + 'mongo', + '-u', + 'myusername', + '-p', + 'mypassword', + '--authenticationDatabase', + 'admin', + '--eval', + "db.adminCommand('ping')", + ] interval: 10s timeout: 5s retries: 5 @@ -266,4 +280,3 @@ networks: fastgpt: aiproxy: vector: - diff --git a/deploy/docker/global/docker-compose.ziliiz.yml b/deploy/docker/global/docker-compose.ziliiz.yml index 79cc14f83..a4a1c8c91 100644 --- a/deploy/docker/global/docker-compose.ziliiz.yml +++ b/deploy/docker/global/docker-compose.ziliiz.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/deploy/templates/docker-compose.dev.yml b/deploy/templates/docker-compose.dev.yml index bf2c54298..6c90a52c3 100644 --- a/deploy/templates/docker-compose.dev.yml +++ b/deploy/templates/docker-compose.dev.yml @@ -162,14 +162,17 @@ services: - fastgpt environment: - AUTH_TOKEN=token - - S3_EXTERNAL_BASE_URL=http://127.0.0.1:9000 # TODO: 改为你 Minio 的实际的 ip 地址 - - S3_ENDPOINT=fastgpt-minio - - S3_PORT=9000 - - S3_USE_SSL=false - - S3_ACCESS_KEY=minioadmin - - S3_SECRET_KEY=minioadmin - - S3_PUBLIC_BUCKET=fastgpt-public # 系统工具,创建的临时文件,存储的桶,要求公开读私有写。 - - S3_PRIVATE_BUCKET=fastgpt-private # 系统插件热安装文件的桶,私有读写。 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + - STORAGE_VENDOR=minio # minio | aws-s3 | cos | oss + - STORAGE_REGION=us-east-1 + - STORAGE_ACCESS_KEY_ID=minioadmin + - STORAGE_SECRET_ACCESS_KEY=minioadmin + - STORAGE_PUBLIC_BUCKET=fastgpt-public + - STORAGE_PRIVATE_BUCKET=fastgpt-private + - STORAGE_EXTERNAL_ENDPOINT=https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + - STORAGE_S3_ENDPOINT=http://fastgpt-minio:9000 # 协议://域名(IP):端口 + - STORAGE_S3_FORCE_PATH_STYLE=true + - STORAGE_S3_MAX_RETRIES=3 - MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin&directConnection=true - REDIS_URL=redis://default:mypassword@redis:6379 depends_on: diff --git a/deploy/templates/docker-compose.prod.yml b/deploy/templates/docker-compose.prod.yml index e9c59ae38..727612029 100644 --- a/deploy/templates/docker-compose.prod.yml +++ b/deploy/templates/docker-compose.prod.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/document/content/docs/introduction/development/meta.json b/document/content/docs/introduction/development/meta.json index 80d919814..cb5c945d5 100644 --- a/document/content/docs/introduction/development/meta.json +++ b/document/content/docs/introduction/development/meta.json @@ -8,6 +8,7 @@ "intro", "docker", "configuration", + "object-storage", "faq", "signoz", "modelConfig", diff --git a/document/content/docs/introduction/development/object-storage.mdx b/document/content/docs/introduction/development/object-storage.mdx new file mode 100644 index 000000000..a81af9abc --- /dev/null +++ b/document/content/docs/introduction/development/object-storage.mdx @@ -0,0 +1,51 @@ +--- +title: 对象存储配置及常见问题 +description: 如何通过环境变量配置并连接个各厂商的对象存储,以及常见的配置问题 +--- + +import { Alert } from '@/components/docs/Alert'; +import FastGPTLink from '@/components/docs/linkFastGPT'; + +## 对象存储服务配置介绍 + +这里提供了 FastGPT 目前支持的对象存储厂商,包括自部署的 MinIO、AWS S3、阿里云 OSS 和腾讯云 COS 的环境变量配置说明 + +### 通用且必需的环境变量 + +> - 不支持对临时身份校验密钥的支持,比如 STS 等,请自行保证服务的安全性。 +> - 不支持对私有桶的复用,如果把私有桶名和公开桶名设置为同一个的话,请保证存储桶的策略至少为【公开读私有写】 + +- `STORAGE_VENDOR` 该变量为一个枚举值,可选值为:`minio`、`aws-s3`、`oss` 和 `cos`。 +- `STORAGE_REGION` 提供的对象存储服务所在的地区如 `us-east-1` 等,具体请根据服务厂商所提供的地区来填写;如果是自部署的 MinIO 等服务这个值可随意填写。 +- `STORAGE_ACCESS_KEY_ID` 服务访问密钥的 Access Key ID +- `STORAGE_SECRET_ACCESS_KEY` 服务访问密钥的 Secret Access Key +- `STORAGE_PUBLIC_BUCKET` FastGPT 公开资源存储桶桶名 +- `STORAGE_PRIVATE_BUCKET` FastGPT 私有资源存储桶桶名 + +### 自部署的 MinIO 和 AWS S3 + +> MinIO 这类产品对 AWS S3 协议支持比较完整,因此使用 Minio 和AWS S3 配置几乎可以是相同的,只是因为服务商提供和自部署的区别,会有额外的配置。 +> 因此理论上任何对 AWS S3 协议的支持程度至少和 MinIO 相当的对象存储服务都可以使用,比如 SeaweedFS、RustFS 等。 + +- `STORAGE_S3_ENDPOINT` 连接端点,填写一个完整的、带协议和端口的主机名,比如 `http://localhost:9000` +- `STORAGE_EXTERNAL_ENDPOINT` 【可选】如果是自己部署的对象存储服务(如 Docker 部署 MinIO)且前端无法访问到你的对象存储服务时,请填写该变量,并保证是一个外部可访问的域名,比如 `https://your-domain.com` 或者 `http://{ip}:9000` 等 +- `STORAGE_S3_FORCE_PATH_STYLE` 【可选】虚拟主机风格路由或路径路由风格,其中如果厂商填写了 `minio` 的话,该值被固定为 `true` +- `STORAGE_S3_MAX_RETRIES` 【可选】请求最大尝试次数,默认为 3 次 + +### 阿里云 OSS + +> - [跨域配置](https://help.aliyun.com/zh/oss/user-guide/configure-cross-origin-resource-sharing/?spm=5176.8466032.console-base_help.dexternal.1bcd1450Wau6J6#b58400ec36rqf) + +- `STORAGE_OSS_ENDPOINT` 阿里云对象存储连接主机名,厂商提供的默认值一般都是 `{地区}.aliyuncs.com`,如 `oss-cn-hangzhou.aliyuncs.com`;注意,如果配置了自定义域名的话也填在这里,比如 `your-domain.com` +- `STORAGE_OSS_CNAME` 是否开启自定义域名 +- `STORAGE_OSS_SECURE` 是否开启了 TLS,如果域名没有认证证书的话,请关闭该选项 +- `STORAGE_OSS_INTERNAL` 【可选】是否开启内网访问,如果你的服务也在阿里云的话可以开启并节省流量,默认关闭 + +### 腾讯云 COS + +> - [跨域配置](https://cloud.tencent.com/document/product/436/13318) + +- `STORAGE_COS_PROTOCOL` 枚举可选值 `https:`、`http:`,注意不要忘记 `:`;如果自定义域名没有上传证书的话,请不要设置为 `https:` +- `STORAGE_COS_USE_ACCELERATE` 【可选】是否启用全球加速域名,默认为 false。若改为 true,需要存储桶开启全球加速功能 +- `STORAGE_COS_CNAME_DOMAIN` 【可选】自定义域名,如 `your-domain.com` +- `STORAGE_COS_PROXY` 【可选】代理服务器,如 `http://localhost:7897` diff --git a/document/content/docs/toc.mdx b/document/content/docs/toc.mdx index abbc28cf3..b133eb744 100644 --- a/document/content/docs/toc.mdx +++ b/document/content/docs/toc.mdx @@ -33,6 +33,7 @@ description: FastGPT 文档目录 - [/docs/introduction/development/modelConfig/one-api](/docs/introduction/development/modelConfig/one-api) - [/docs/introduction/development/modelConfig/ppio](/docs/introduction/development/modelConfig/ppio) - [/docs/introduction/development/modelConfig/siliconCloud](/docs/introduction/development/modelConfig/siliconCloud) +- [/docs/introduction/development/object-storage](/docs/introduction/development/object-storage) - [/docs/introduction/development/openapi/app](/docs/introduction/development/openapi/app) - [/docs/introduction/development/openapi/chat](/docs/introduction/development/openapi/chat) - [/docs/introduction/development/openapi/dataset](/docs/introduction/development/openapi/dataset) diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 330ea4e5b..eba3542d8 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -120,7 +120,7 @@ "document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00", "document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00", "document/content/docs/upgrading/4-14/4144.mdx": "2025-12-16T14:56:04+08:00", - "document/content/docs/upgrading/4-14/4145.mdx": "2025-12-21T19:15:10+08:00", + "document/content/docs/upgrading/4-14/4145.mdx": "2025-12-21T23:28:19+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00", diff --git a/document/package-lock.json b/document/package-lock.json index a8111b1fa..4ee46103e 100644 --- a/document/package-lock.json +++ b/document/package-lock.json @@ -2543,7 +2543,6 @@ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2554,7 +2553,6 @@ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -2576,7 +2574,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3244,7 +3241,6 @@ "resolved": "https://registry.npmjs.org/fumadocs-core/-/fumadocs-core-15.6.3.tgz", "integrity": "sha512-71IPC6Y0ZLPHlavYormnF1r2uX/lNrTFTYCEh6Akll8hWxRNbKG9Hk4xpFJDTkU83c8eLtHk2iow/ccQkcV6Hw==", "license": "MIT", - "peer": true, "dependencies": { "@formatjs/intl-localematcher": "^0.6.1", "@orama/orama": "^3.1.9", @@ -5177,7 +5173,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", @@ -5353,7 +5348,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5469,7 +5463,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5479,7 +5472,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6101,8 +6093,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.2.2", @@ -6198,7 +6189,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/document/public/deploy/docker/cn/docker-compose.milvus.yml b/document/public/deploy/docker/cn/docker-compose.milvus.yml index a0c925899..8312c0e21 100644 --- a/document/public/deploy/docker/cn/docker-compose.milvus.yml +++ b/document/public/deploy/docker/cn/docker-compose.milvus.yml @@ -12,20 +12,23 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config MILVUS_ADDRESS: http://milvusStandalone:19530 MILVUS_TOKEN: none - + version: '3.3' services: diff --git a/document/public/deploy/docker/cn/docker-compose.oceanbase.yml b/document/public/deploy/docker/cn/docker-compose.oceanbase.yml index 334e53433..f65107eea 100644 --- a/document/public/deploy/docker/cn/docker-compose.oceanbase.yml +++ b/document/public/deploy/docker/cn/docker-compose.oceanbase.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config @@ -290,4 +293,4 @@ configs: name: init_sql content: | ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; - + diff --git a/document/public/deploy/docker/cn/docker-compose.pg.yml b/document/public/deploy/docker/cn/docker-compose.pg.yml index 0647f55fc..932112af7 100644 --- a/document/public/deploy/docker/cn/docker-compose.pg.yml +++ b/document/public/deploy/docker/cn/docker-compose.pg.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/document/public/deploy/docker/cn/docker-compose.zilliz.yml b/document/public/deploy/docker/cn/docker-compose.zilliz.yml index 7df9ba95d..a346eab25 100644 --- a/document/public/deploy/docker/cn/docker-compose.zilliz.yml +++ b/document/public/deploy/docker/cn/docker-compose.zilliz.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/document/public/deploy/docker/global/docker-compose.milvus.yml b/document/public/deploy/docker/global/docker-compose.milvus.yml index 68a1e4e20..8ed22fff4 100644 --- a/document/public/deploy/docker/global/docker-compose.milvus.yml +++ b/document/public/deploy/docker/global/docker-compose.milvus.yml @@ -12,20 +12,23 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config MILVUS_ADDRESS: http://milvusStandalone:19530 MILVUS_TOKEN: none - + version: '3.3' services: diff --git a/document/public/deploy/docker/global/docker-compose.oceanbase.yml b/document/public/deploy/docker/global/docker-compose.oceanbase.yml index 53a17652b..af8258766 100644 --- a/document/public/deploy/docker/global/docker-compose.oceanbase.yml +++ b/document/public/deploy/docker/global/docker-compose.oceanbase.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config @@ -290,4 +293,4 @@ configs: name: init_sql content: | ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; - + diff --git a/document/public/deploy/docker/global/docker-compose.pg.yml b/document/public/deploy/docker/global/docker-compose.pg.yml index f13ef6826..22f95255d 100644 --- a/document/public/deploy/docker/global/docker-compose.pg.yml +++ b/document/public/deploy/docker/global/docker-compose.pg.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/document/public/deploy/docker/global/docker-compose.ziliiz.yml b/document/public/deploy/docker/global/docker-compose.ziliiz.yml index 79cc14f83..a4a1c8c91 100644 --- a/document/public/deploy/docker/global/docker-compose.ziliiz.yml +++ b/document/public/deploy/docker/global/docker-compose.ziliiz.yml @@ -12,14 +12,17 @@ x-share-db-config: &x-share-db-config MONGODB_URI: mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin DB_MAX_LINK: 100 REDIS_URL: redis://default:mypassword@redis:6379 - S3_EXTERNAL_BASE_URL: https://minio.com # S3 的公网访问地址 - S3_ENDPOINT: fastgpt-minio - S3_PORT: 9000 - S3_USE_SSL: false - S3_ACCESS_KEY: minioadmin - S3_SECRET_KEY: minioadmin - S3_PUBLIC_BUCKET: fastgpt-public # 公开读私有写桶 - S3_PRIVATE_BUCKET: fastgpt-private # 私有读写桶 + # @see https://fastgpt.cn/docs/introduction/development/object-storage + STORAGE_VENDOR: minio # minio | aws-s3 | cos | oss + STORAGE_REGION: us-east-1 + STORAGE_ACCESS_KEY_ID: minioadmin + STORAGE_SECRET_ACCESS_KEY: minioadmin + STORAGE_PUBLIC_BUCKET: fastgpt-public + STORAGE_PRIVATE_BUCKET: fastgpt-private + STORAGE_EXTERNAL_ENDPOINT: https://minio.com # 一个公开的、前端和用户可以直接访问的对象存储连接 + STORAGE_S3_ENDPOINT: http://fastgpt-minio:9000 # 协议://域名(IP):端口 + STORAGE_S3_FORCE_PATH_STYLE: true + STORAGE_S3_MAX_RETRIES: 3 # 向量库相关配置 x-vec-config: &x-vec-config diff --git a/packages/global/package.json b/packages/global/package.json index 9cec61e63..6c327f2e6 100644 --- a/packages/global/package.json +++ b/packages/global/package.json @@ -2,7 +2,7 @@ "name": "@fastgpt/global", "version": "1.0.0", "dependencies": { - "@fastgpt-sdk/plugin": "0.2.16", + "@fastgpt-sdk/plugin": "0.2.17", "@apidevtools/swagger-parser": "^10.1.0", "@bany/curl-to-json": "^1.2.8", "axios": "^1.12.1", diff --git a/packages/service/common/s3/buckets/base.ts b/packages/service/common/s3/buckets/base.ts index 2ab81c923..b2c8d0bde 100644 --- a/packages/service/common/s3/buckets/base.ts +++ b/packages/service/common/s3/buckets/base.ts @@ -10,20 +10,21 @@ import { type CreatePostPresignedUrlOptions, type CreatePostPresignedUrlParams, type CreatePostPresignedUrlResult, - type S3OptionsType, type createPreviewUrlParams, CreateGetPresignedUrlParamsSchema } from '../type'; -import { defaultS3Options, getSystemMaxFileSize, Mimes } from '../constants'; +import { getSystemMaxFileSize, Mimes } from '../constants'; import path from 'node:path'; import { MongoS3TTL } from '../schema'; -import { addHours, addMinutes } from 'date-fns'; +import { addHours, addMinutes, differenceInSeconds } from 'date-fns'; import { addLog } from '../../system/log'; import { addS3DelJob } from '../mq'; -import { type Readable } from 'node:stream'; import { type UploadFileByBufferParams, UploadFileByBufferSchema } from '../type'; +import type { createStorage } from '@fastgpt-sdk/storage'; import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools'; +type IStorage = ReturnType; + // Check if the error is a "file not found" type error, which should be treated as success export const isFileNotFoundError = (error: any): boolean => { if (error instanceof S3Error) { @@ -45,82 +46,26 @@ export const isFileNotFoundError = (error: any): boolean => { }; export class S3BaseBucket { - private _client: Client; - private _externalClient: Client | undefined; - - /** - * - * @param bucketName the bucket you want to operate - * @param options the options for the s3 client - */ constructor( - public readonly bucketName: string, - public options: Partial = defaultS3Options - ) { - options = { ...defaultS3Options, ...options }; - this.options = options; - this._client = new Client(options as S3OptionsType); + private readonly _client: IStorage, + private readonly _externalClient: IStorage | undefined + ) {} - if (this.options.externalBaseURL) { - const externalBaseURL = new URL(this.options.externalBaseURL); - const endpoint = externalBaseURL.hostname; - const useSSL = externalBaseURL.protocol === 'https:'; - - const externalPort = externalBaseURL.port - ? parseInt(externalBaseURL.port) - : useSSL - ? 443 - : undefined; // https 默认 443,其他情况让 MinIO 客户端使用默认端口 - - this._externalClient = new Client({ - useSSL: useSSL, - endPoint: endpoint, - port: externalPort, - accessKey: options.accessKey, - secretKey: options.secretKey, - pathStyle: options.pathStyle, - region: options.region - }); - } - - const init = async () => { - // Not exists bucket, create it - if (!(await this.client.bucketExists(this.bucketName))) { - await this.client.makeBucket(this.bucketName); - } - await this.options.afterInit?.(); - console.log(`S3 init success: ${this.bucketName}`); - }; - if (this.options.init) { - init(); - } - } - - get client(): Client { + get client(): IStorage { return this._client; } - get externalClient(): Client { + + get externalClient(): IStorage { return this._externalClient ?? this._client; } + get bucketName(): string { + return this.client.bucketName; + } + // TODO: 加到 MQ 里保障幂等 - async move({ - from, - to, - options - }: { - from: string; - to: string; - options?: CopyConditions; - }): Promise { - await this.copy({ - from, - to, - options: { - copyConditions: options, - temporary: false - } - }); + async move({ from, to }: { from: string; to: string }): Promise { + await this.copy({ from, to, options: { temporary: false } }); await this.removeObject(from); } @@ -133,10 +78,8 @@ export class S3BaseBucket { to: string; options?: { temporary?: boolean; - copyConditions?: CopyConditions; }; - }): ReturnType { - const bucket = this.bucketName; + }) { if (options?.temporary) { await MongoS3TTL.create({ minioKey: to, @@ -144,11 +87,11 @@ export class S3BaseBucket { expiredTime: addHours(new Date(), 24) }); } - return this.client.copyObject(bucket, to, `${bucket}/${from}`, options?.copyConditions); + return this.client.copyObjectInSelfBucket({ sourceKey: from, targetKey: to }); } - async removeObject(objectKey: string, options?: RemoveOptions): Promise { - return this.client.removeObject(this.bucketName, objectKey, options).catch((err) => { + async removeObject(objectKey: string): Promise { + this.client.deleteObject({ key: objectKey }).catch((err) => { if (isFileNotFoundError(err)) { return Promise.resolve(); } @@ -160,66 +103,17 @@ export class S3BaseBucket { }); } - // 列出文件 - listObjectsV2( - ...params: Parameters extends [string, ...infer R] ? R : never - ) { - return this.client.listObjectsV2(this.bucketName, ...params); - } - - // 上传文件 - putObject(...params: Parameters extends [string, ...infer R] ? R : never) { - return this.client.putObject(this.bucketName, ...params); - } - - // 获取文件流 - getFileStream( - ...params: Parameters extends [string, ...infer R] ? R : never - ) { - return this.client.getObject(this.bucketName, ...params); - } - - // 获取文件状态 - async statObject( - ...params: Parameters extends [string, ...infer R] ? R : never - ) { - try { - return await this.client.statObject(this.bucketName, ...params); - } catch (error) { - if (error instanceof S3Error && error.message === 'Not Found') { - return null; - } - return Promise.reject(error); - } - } - - // 判断文件是否存在 - async isObjectExists(key: string): Promise { - try { - await this.client.statObject(this.bucketName, key); - return true; - } catch (err) { - if (err instanceof S3Error && err.message === 'Not Found') { - return false; - } - return Promise.reject(err); - } - } - - // 将文件流转换为Buffer - async fileStreamToBuffer(stream: Readable): Promise { - const chunks: Buffer[] = []; - for await (const chunk of stream) { - chunks.push(chunk); - } - return Buffer.concat(chunks); - } - addDeleteJob(params: Omit[0], 'bucketName'>) { return addS3DelJob({ ...params, bucketName: this.bucketName }); } - async createPostPresignedUrl( + async isObjectExists(key: string) { + const { exists } = await this.client.checkObjectExists({ key }); + + return exists ?? false; + } + + async createPresignedPutUrl( params: CreatePostPresignedUrlParams, options: CreatePostPresignedUrlOptions = {} ): Promise { @@ -229,42 +123,39 @@ export class S3BaseBucket { const filename = params.filename; const ext = path.extname(filename).toLowerCase(); const contentType = Mimes[ext as keyof typeof Mimes] ?? 'application/octet-stream'; + const expiredSeconds = differenceInSeconds(addMinutes(new Date(), 10), new Date()); - const key = params.rawKey; - - const policy = this.externalClient.newPostPolicy(); - policy.setKey(key); - policy.setBucket(this.bucketName); - policy.setContentType(contentType); - if (formatMaxFileSize) { - policy.setContentLengthRange(1, formatMaxFileSize); - } - policy.setExpires(addMinutes(new Date(), 10)); - policy.setUserMetaData({ - 'content-disposition': `attachment; filename="${encodeURIComponent(filename)}"`, - 'origin-filename': encodeURIComponent(filename), - 'upload-time': new Date().toISOString(), - ...params.metadata + const { metadata, putUrl } = await this.externalClient.generatePresignedPutUrl({ + key: params.rawKey, + expiredSeconds, + contentType, + metadata: { + contentDisposition: `attachment; filename="${encodeURIComponent(filename)}"`, + originFilename: encodeURIComponent(filename), + uploadTime: new Date().toISOString(), + ...params.metadata + } }); - const { formData, postURL } = await this.externalClient.presignedPostPolicy(policy); - if (expiredHours) { await MongoS3TTL.create({ - minioKey: key, + minioKey: params.rawKey, bucketName: this.bucketName, expiredTime: addHours(new Date(), expiredHours) }); } return { - url: postURL, - fields: formData, + url: putUrl, + fields: { + ...metadata, + key: params.rawKey + }, maxSize: formatMaxFileSize }; } catch (error) { - addLog.error('Failed to create post presigned url', error); - return Promise.reject('Failed to create post presigned url'); + addLog.error('Failed to create presigned put url', error); + return Promise.reject('Failed to create presigned put url'); } } @@ -274,7 +165,7 @@ export class S3BaseBucket { const { key, expiredHours } = parsed; const expires = expiredHours ? expiredHours * 60 * 60 : 30 * 60; // expires 的单位是秒 默认 30 分钟 - return await this.externalClient.presignedGetObject(this.bucketName, key, expires); + return await this.externalClient.generatePresignedGetUrl({ key, expiredSeconds: expires }); } async createPreviewUrl(params: createPreviewUrlParams) { @@ -283,7 +174,7 @@ export class S3BaseBucket { const { key, expiredHours } = parsed; const expires = expiredHours ? expiredHours * 60 * 60 : 30 * 60; // expires 的单位是秒 默认 30 分钟 - return await this.client.presignedGetObject(this.bucketName, key, expires); + return await this.client.generatePresignedGetUrl({ key, expiredSeconds: expires }); } async uploadFileByBuffer(params: UploadFileByBufferParams) { @@ -294,8 +185,11 @@ export class S3BaseBucket { bucketName: this.bucketName, expiredTime: addHours(new Date(), 1) }); - await this.putObject(key, buffer, undefined, { - 'Content-Type': contentType || 'application/octet-stream' + + await this.client.uploadObject({ + key, + body: buffer, + contentType: contentType || 'application/octet-stream' }); return { @@ -307,16 +201,15 @@ export class S3BaseBucket { }; } - // 对外包装的方法 - // 获取文件元数据 async getFileMetadata(key: string) { - const stat = await this.statObject(key); - if (!stat) return; + const metadataResponse = await this.client.getObjectMetadata({ key }); + if (!metadataResponse) return; - const contentLength = stat.size; - const filename: string = decodeURIComponent(stat.metaData['origin-filename']); + const contentLength = metadataResponse.contentLength; + const filename: string = decodeURIComponent(metadataResponse.metadata.originFilename || ''); const extension = parseFileExtensionFromUrl(filename); - const contentType: string = stat.metaData['content-type']; + const contentType: string = metadataResponse.contentType || 'application/octet-stream'; + return { filename, extension, @@ -324,4 +217,11 @@ export class S3BaseBucket { contentLength }; } + + async getFileStream(key: string) { + const downloadResponse = await this.client.downloadObject({ key }); + if (!downloadResponse) return; + + return downloadResponse.body; + } } diff --git a/packages/service/common/s3/buckets/private.ts b/packages/service/common/s3/buckets/private.ts index 1d85712f7..65447abb6 100644 --- a/packages/service/common/s3/buckets/private.ts +++ b/packages/service/common/s3/buckets/private.ts @@ -1,9 +1,95 @@ import { S3BaseBucket } from './base'; -import { S3Buckets } from '../constants'; -import { type S3OptionsType } from '../type'; +import { createDefaultStorageOptions } from '../constants'; +import { + type IAwsS3CompatibleStorageOptions, + createStorage, + type ICosStorageOptions, + type IOssStorageOptions, + type IStorageOptions +} from '@fastgpt-sdk/storage'; export class S3PrivateBucket extends S3BaseBucket { - constructor(options?: Partial) { - super(S3Buckets.private, options); + constructor() { + const { vendor, privateBucket, externalBaseUrl, credentials, region, ...options } = + createDefaultStorageOptions(); + + const { config, externalConfig } = (() => { + if (vendor === 'minio') { + const config = { + region, + vendor, + credentials, + forcePathStyle: true, + endpoint: options.endpoint!, + maxRetries: options.maxRetries! + } as Omit; + return { + config, + externalConfig: { + ...config, + endpoint: externalBaseUrl + } + }; + } else if (vendor === 'aws-s3') { + const config = { + region, + vendor, + credentials, + endpoint: options.endpoint!, + maxRetries: options.maxRetries!, + forcePathStyle: options.forcePathStyle + } as Omit; + return { + config, + externalConfig: { + ...config, + endpoint: externalBaseUrl + } + }; + } else if (vendor === 'cos') { + return { + config: { + region, + vendor, + credentials, + proxy: options.proxy, + domain: options.domain, + protocol: options.protocol, + useAccelerate: options.useAccelerate + } as Omit + }; + } else if (vendor === 'oss') { + return { + config: { + region, + vendor, + credentials, + endpoint: options.endpoint!, + cname: options.cname, + internal: options.internal, + secure: options.secure, + enableProxy: options.enableProxy + } as Omit + }; + } + throw new Error(`Unsupported storage vendor: ${vendor}`); + })(); + + const client = createStorage({ bucket: privateBucket, ...config }); + + let externalClient: ReturnType | undefined = undefined; + if (externalBaseUrl) { + externalClient = createStorage({ + bucket: privateBucket, + ...externalConfig + } as IStorageOptions); + } + + super(client, externalClient); + + client.ensureBucket(); + if (externalClient) { + externalClient.ensureBucket(); + } } } diff --git a/packages/service/common/s3/buckets/public.ts b/packages/service/common/s3/buckets/public.ts index 331c79277..1d1d717b7 100644 --- a/packages/service/common/s3/buckets/public.ts +++ b/packages/service/common/s3/buckets/public.ts @@ -1,51 +1,109 @@ import { S3BaseBucket } from './base'; -import { S3Buckets } from '../constants'; -import { type S3OptionsType } from '../type'; +import { createDefaultStorageOptions } from '../constants'; +import { + type IAwsS3CompatibleStorageOptions, + type ICosStorageOptions, + type IOssStorageOptions, + createStorage, + MinioStorageAdapter, + type IStorageOptions +} from '@fastgpt-sdk/storage'; export class S3PublicBucket extends S3BaseBucket { - constructor(options?: Partial) { - super(S3Buckets.public, { - ...options, - afterInit: async () => { - const bucket = this.bucketName; - const policy = JSON.stringify({ - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: '*', - Action: 's3:GetObject', - Resource: `arn:aws:s3:::${bucket}/*` - } - ] - }); - try { - await this.client.setBucketPolicy(bucket, policy); - } catch (error) { - // NOTE: maybe it was a cloud S3 that doesn't allow us to set the policy, so that cause the error, - // maybe we can ignore the error, or we have other plan to handle this. - console.error('Failed to set bucket policy:', error); - } + constructor() { + const { vendor, publicBucket, externalBaseUrl, credentials, region, ...options } = + createDefaultStorageOptions(); + + const { config, externalConfig } = (() => { + if (vendor === 'minio') { + const config = { + region, + vendor, + credentials, + forcePathStyle: true, + endpoint: options.endpoint!, + maxRetries: options.maxRetries! + } as Omit; + return { + config, + externalConfig: { + ...config, + endpoint: externalBaseUrl + } + }; + } else if (vendor === 'aws-s3') { + const config = { + region, + vendor, + credentials, + endpoint: options.endpoint!, + maxRetries: options.maxRetries!, + forcePathStyle: options.forcePathStyle + } as Omit; + return { + config, + externalConfig: { + ...config, + endpoint: externalBaseUrl + } + }; + } else if (vendor === 'cos') { + return { + config: { + region, + vendor, + credentials, + proxy: options.proxy, + domain: options.domain, + protocol: options.protocol, + useAccelerate: options.useAccelerate + } as Omit + }; + } else if (vendor === 'oss') { + return { + config: { + region, + vendor, + credentials, + endpoint: options.endpoint!, + cname: options.cname, + internal: options.internal, + secure: options.secure, + enableProxy: options.enableProxy + } as Omit + }; + } + throw new Error(`Unsupported storage vendor: ${vendor}`); + })(); + + const client = createStorage({ bucket: publicBucket, ...config }); + + let externalClient: ReturnType | undefined = undefined; + if (externalBaseUrl) { + externalClient = createStorage({ + bucket: publicBucket, + ...externalConfig + } as IStorageOptions); + } + + super(client, externalClient); + + client.ensureBucket().then(() => { + if (client instanceof MinioStorageAdapter) { + client.ensurePublicBucketPolicy(); } }); + + if (externalClient) { + externalClient.ensureBucket().then(() => { + if (externalClient instanceof MinioStorageAdapter) { + externalClient.ensurePublicBucketPolicy(); + } + }); + } } createPublicUrl(objectKey: string): string { - const protocol = this.options.useSSL ? 'https' : 'http'; - const hostname = this.options.endPoint; - const port = this.options.port; - const bucket = this.bucketName; - - const url = new URL(`${protocol}://${hostname}:${port}/${bucket}/${objectKey}`); - - if (this.options.externalBaseURL) { - const externalBaseURL = new URL(this.options.externalBaseURL); - - url.port = externalBaseURL.port; - url.hostname = externalBaseURL.hostname; - url.protocol = externalBaseURL.protocol; - } - - return url.toString(); + return this.externalClient.generatePublicGetUrl({ key: objectKey }).publicGetUrl; } } diff --git a/packages/service/common/s3/constants.ts b/packages/service/common/s3/constants.ts index 03e1853e8..3773ecf56 100644 --- a/packages/service/common/s3/constants.ts +++ b/packages/service/common/s3/constants.ts @@ -1,6 +1,9 @@ -import { HttpProxyAgent } from 'http-proxy-agent'; -import { HttpsProxyAgent } from 'https-proxy-agent'; -import type { ClientOptions } from 'minio'; +import type { + IAwsS3CompatibleStorageOptions, + ICosStorageOptions, + IOssStorageOptions, + IStorageOptions +} from '@fastgpt-sdk/storage'; export const Mimes = { '.gif': 'image/gif', @@ -25,24 +28,9 @@ export const Mimes = { '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } as const; -export const defaultS3Options: { - externalBaseURL?: string; - afterInit?: () => Promise | void; - init?: boolean; -} & ClientOptions = { - useSSL: process.env.S3_USE_SSL === 'true', - endPoint: process.env.S3_ENDPOINT || 'localhost', - externalBaseURL: process.env.S3_EXTERNAL_BASE_URL, - accessKey: process.env.S3_ACCESS_KEY || 'minioadmin', - secretKey: process.env.S3_SECRET_KEY || 'minioadmin', - port: process.env.S3_PORT ? parseInt(process.env.S3_PORT) : 9000, - pathStyle: process.env.S3_PATH_STYLE === 'false' ? false : true, - region: process.env.S3_REGION || undefined -}; - export const S3Buckets = { - public: process.env.S3_PUBLIC_BUCKET || 'fastgpt-public', - private: process.env.S3_PRIVATE_BUCKET || 'fastgpt-private' + public: process.env.STORAGE_PUBLIC_BUCKET || 'fastgpt-public', + private: process.env.STORAGE_PRIVATE_BUCKET || 'fastgpt-private' } as const; export const getSystemMaxFileSize = () => { @@ -51,3 +39,104 @@ export const getSystemMaxFileSize = () => { }; export const S3_KEY_PATH_INVALID_CHARS = /[|\\/]/; + +export function createDefaultStorageOptions() { + const vendor = (process.env.STORAGE_VENDOR || 'minio') as IStorageOptions['vendor']; + + switch (vendor) { + case 'minio': { + return { + vendor: 'minio', + forcePathStyle: true, + externalBaseUrl: process.env.STORAGE_EXTERNAL_ENDPOINT || undefined, + endpoint: process.env.STORAGE_S3_ENDPOINT || 'http://localhost:9000', + region: process.env.STORAGE_REGION || 'us-east-1', + publicBucket: process.env.STORAGE_PUBLIC_BUCKET || 'fastgpt-public', + privateBucket: process.env.STORAGE_PRIVATE_BUCKET || 'fastgpt-private', + credentials: { + accessKeyId: process.env.STORAGE_ACCESS_KEY_ID || 'minioadmin', + secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY || 'minioadmin' + }, + maxRetries: process.env.STORAGE_S3_MAX_RETRIES + ? parseInt(process.env.STORAGE_S3_MAX_RETRIES) + : 3 + } satisfies Omit & { + publicBucket: string; + privateBucket: string; + externalBaseUrl?: string; + }; + } + + case 'aws-s3': { + return { + vendor: 'aws-s3', + forcePathStyle: process.env.STORAGE_S3_FORCE_PATH_STYLE === 'true' ? true : false, + externalBaseUrl: process.env.STORAGE_EXTERNAL_ENDPOINT || undefined, + endpoint: process.env.STORAGE_S3_ENDPOINT || '', + region: process.env.STORAGE_REGION || 'us-east-1', + publicBucket: process.env.STORAGE_PUBLIC_BUCKET || 'fastgpt-public', + privateBucket: process.env.STORAGE_PRIVATE_BUCKET || 'fastgpt-private', + credentials: { + accessKeyId: process.env.STORAGE_ACCESS_KEY_ID || '', + secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY || '' + }, + maxRetries: process.env.STORAGE_S3_MAX_RETRIES + ? parseInt(process.env.STORAGE_S3_MAX_RETRIES) + : 3 + } satisfies Omit & { + publicBucket: string; + privateBucket: string; + externalBaseUrl?: string; + }; + } + + case 'cos': { + return { + vendor: 'cos', + externalBaseUrl: process.env.STORAGE_EXTERNAL_ENDPOINT || undefined, + region: process.env.STORAGE_REGION || 'ap-shanghai', + publicBucket: process.env.STORAGE_PUBLIC_BUCKET || 'fastgpt-public', + privateBucket: process.env.STORAGE_PRIVATE_BUCKET || 'fastgpt-private', + credentials: { + accessKeyId: process.env.STORAGE_ACCESS_KEY_ID || '', + secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY || '' + }, + protocol: (process.env.STORAGE_COS_PROTOCOL as 'https:' | 'http:' | undefined) || 'https:', + useAccelerate: process.env.STORAGE_COS_USE_ACCELERATE === 'true' ? true : false, + domain: process.env.STORAGE_COS_CNAME_DOMAIN || undefined, + proxy: process.env.STORAGE_COS_PROXY || undefined + } satisfies Omit & { + publicBucket: string; + privateBucket: string; + externalBaseUrl?: string; + }; + } + + case 'oss': { + return { + vendor: 'oss', + externalBaseUrl: process.env.STORAGE_EXTERNAL_ENDPOINT || undefined, + endpoint: process.env.STORAGE_OSS_ENDPOINT || '', + region: process.env.STORAGE_REGION || 'oss-cn-hangzhou', + publicBucket: process.env.STORAGE_PUBLIC_BUCKET || 'fastgpt-public', + privateBucket: process.env.STORAGE_PRIVATE_BUCKET || 'fastgpt-private', + credentials: { + accessKeyId: process.env.STORAGE_ACCESS_KEY_ID || '', + secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY || '' + }, + cname: process.env.STORAGE_OSS_CNAME === 'true' ? true : false, + internal: process.env.STORAGE_OSS_INTERNAL === 'true' ? true : false, + secure: process.env.STORAGE_OSS_SECURE === 'true' ? true : false, + enableProxy: process.env.STORAGE_OSS_ENABLE_PROXY === 'false' ? false : true + } satisfies Omit & { + publicBucket: string; + privateBucket: string; + externalBaseUrl?: string; + }; + } + + default: { + throw new Error(`Unsupported storage vendor: ${vendor}`); + } + } +} diff --git a/packages/service/common/s3/index.ts b/packages/service/common/s3/index.ts index 72e4c3c92..821d3dcab 100644 --- a/packages/service/common/s3/index.ts +++ b/packages/service/common/s3/index.ts @@ -4,8 +4,8 @@ import { addLog } from '../system/log'; import { startS3DelWorker } from './mq'; export function initS3Buckets() { - const publicBucket = new S3PublicBucket({ init: true }); - const privateBucket = new S3PrivateBucket({ init: true }); + const publicBucket = new S3PublicBucket(); + const privateBucket = new S3PrivateBucket(); global.s3BucketMap = { [publicBucket.bucketName]: publicBucket, diff --git a/packages/service/common/s3/mq.ts b/packages/service/common/s3/mq.ts index 7025bcff0..26a51dc7a 100644 --- a/packages/service/common/s3/mq.ts +++ b/packages/service/common/s3/mq.ts @@ -1,8 +1,8 @@ import { getQueue, getWorker, QueueNames } from '../bullmq'; import { addLog } from '../system/log'; import path from 'path'; -import { batchRun } from '@fastgpt/global/common/system/utils'; -import { isFileNotFoundError, type S3BaseBucket } from './buckets/base'; +import { batchRun, retryFn } from '@fastgpt/global/common/system/utils'; +import pLimit from 'p-limit'; export type S3MQJobData = { key?: string; @@ -23,6 +23,7 @@ const jobOption = { type: 'exponential' } }; + export const addS3DelJob = async (data: S3MQJobData): Promise => { const queue = getQueue(QueueNames.s3FileDelete); const jobId = (() => { @@ -40,77 +41,15 @@ export const addS3DelJob = async (data: S3MQJobData): Promise => { await queue.add('delete-s3-files', data, { jobId, ...jobOption }); }; -export const prefixDel = async (bucket: S3BaseBucket, prefix: string) => { - addLog.debug(`[S3 delete] delete prefix: ${prefix}`); - let tasks: Promise[] = []; - return new Promise((resolve, reject) => { - let timer: NodeJS.Timeout; - const stream = bucket.listObjectsV2(prefix, true); - - let settled = false; - const finish = (error?: any) => { - if (settled) return; - settled = true; - - if (timer) { - clearTimeout(timer); - } - - stream?.removeAllListeners?.(); - stream?.destroy?.(); - - if (error) { - addLog.error(`[S3 delete] delete prefix failed`, error); - reject(error); - } else { - resolve(); - } - }; - - // stream 可能会中断,没有触发 end 和 error,导致 promise 不返回,需要增加定时器兜底。 - timer = setTimeout(() => { - addLog.error(`[S3 delete] delete prefix timeout: ${prefix}`); - finish('Timeout'); - }, 60000); - - stream.on('data', (file) => { - if (!file.name) return; - tasks.push(bucket.removeObject(file.name)); - }); - stream.on('end', async () => { - if (tasks.length === 0) { - return finish(); - } - - if (timer) { - clearTimeout(timer); - } - const results = await Promise.allSettled(tasks); - const failed = results.some((r) => r.status === 'rejected'); - if (failed) { - return finish('Some deletes failed'); - } - finish(); - }); - stream.on('error', (err) => { - if (isFileNotFoundError(err)) { - return finish(); - } - addLog.error(`[S3 delete] delete prefix: ${prefix} error`, err); - return finish(err); - }); - stream.on('pause', () => { - addLog.warn(`[S3 delete] delete prefix: ${prefix} paused`); - stream.resume(); - }); - }); -}; export const startS3DelWorker = async () => { + const limit = pLimit(50); + return getWorker( QueueNames.s3FileDelete, async (job) => { let { prefix, bucketName, key, keys } = job.data; const bucket = global.s3BucketMap[bucketName]; + if (!bucket) { addLog.error(`Bucket not found: ${bucketName}`); return; @@ -121,17 +60,20 @@ export const startS3DelWorker = async () => { } if (keys) { addLog.debug(`[S3 delete] delete keys: ${keys.length}`); + await bucket.client.deleteObjectsByMultiKeys({ keys }); await batchRun(keys, async (key) => { - await bucket.removeObject(key); - // Delete parsed - if (!key.includes('-parsed/')) { - const fileParsedPrefix = `${path.dirname(key)}/${path.basename(key, path.extname(key))}-parsed`; - await prefixDel(bucket, fileParsedPrefix); - } + if (key.includes('-parsed/')) return; + const fileParsedPrefix = `${path.dirname(key)}/${path.basename(key, path.extname(key))}-parsed`; + await bucket.client.deleteObjectsByPrefix({ prefix: fileParsedPrefix }); }); } if (prefix) { - await prefixDel(bucket, prefix); + addLog.info(`[S3 delete] delete prefix: ${prefix}`); + const tasks = []; + const p = limit(() => retryFn(() => bucket.client.deleteObjectsByPrefix({ prefix }))); + tasks.push(p); + await Promise.all(tasks); + addLog.info(`[S3 delete] delete prefix: ${prefix} success`); } }, { diff --git a/packages/service/common/s3/sources/avatar.ts b/packages/service/common/s3/sources/avatar.ts index 17469b6ab..e4b8b9d1b 100644 --- a/packages/service/common/s3/sources/avatar.ts +++ b/packages/service/common/s3/sources/avatar.ts @@ -25,7 +25,7 @@ class S3AvatarSource extends S3PublicBucket { }) { const { fileKey } = getFileS3Key.avatar({ teamId, filename }); - return this.createPostPresignedUrl( + return this.createPresignedPutUrl( { filename, rawKey: fileKey }, { expiredHours: autoExpired ? 1 : undefined, // 1 Hours diff --git a/packages/service/common/s3/sources/chat/index.ts b/packages/service/common/s3/sources/chat/index.ts index 087c8f3fd..9c4b0045d 100644 --- a/packages/service/common/s3/sources/chat/index.ts +++ b/packages/service/common/s3/sources/chat/index.ts @@ -60,21 +60,25 @@ export class S3ChatSource extends S3PrivateBucket { async createUploadChatFileURL(params: CheckChatFileKeys) { const { appId, chatId, uId, filename, expiredTime } = ChatFileUploadSchema.parse(params); const { fileKey } = getFileS3Key.chat({ appId, chatId, uId, filename }); - return await this.createPostPresignedUrl( + return await this.createPresignedPutUrl( { rawKey: fileKey, filename }, { expiredHours: expiredTime ? differenceInHours(expiredTime, new Date()) : 24 } ); } - deleteChatFilesByPrefix(params: DelChatFileByPrefixParams) { + async deleteChatFilesByPrefix(params: DelChatFileByPrefixParams) { const { appId, chatId, uId } = DelChatFileByPrefixSchema.parse(params); const prefix = [S3Sources.chat, appId, uId, chatId].filter(Boolean).join('/'); - return this.addDeleteJob({ prefix }); + await this.addDeleteJob({ prefix }); + // Delete public chat files which are generated by Plugins + global.s3BucketMap[S3Buckets.public]?.addDeleteJob({ prefix }); + return prefix; } deleteChatFileByKey(key: string) { - return this.addDeleteJob({ key }); + this.addDeleteJob({ key }); + return key; } async uploadChatFileByBuffer(params: UploadFileParams) { @@ -93,6 +97,11 @@ export class S3ChatSource extends S3PrivateBucket { contentType }); } + + getToolFilePrefix(params: { appId: string; chatId: string; uId: string }) { + const { appId, chatId, uId } = params; + return [S3Sources.chat, appId, uId, chatId].filter(Boolean).join('/'); + } } export function getS3ChatSource() { diff --git a/packages/service/common/s3/sources/dataset/index.ts b/packages/service/common/s3/sources/dataset/index.ts index c9ca9d6aa..82c433f32 100644 --- a/packages/service/common/s3/sources/dataset/index.ts +++ b/packages/service/common/s3/sources/dataset/index.ts @@ -1,6 +1,6 @@ import { S3Sources } from '../../type'; import { S3PrivateBucket } from '../../buckets/private'; -import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools'; +import streamConsumer from 'node:stream/consumers'; import { type CreateGetDatasetFileURLParams, CreateGetDatasetFileURLParamsSchema, @@ -46,7 +46,7 @@ export class S3DatasetSource extends S3PrivateBucket { async createUploadDatasetFileURL(params: CreateUploadDatasetFileParams) { const { filename, datasetId } = CreateUploadDatasetFileParamsSchema.parse(params); const { fileKey } = getFileS3Key.dataset({ datasetId, filename }); - return await this.createPostPresignedUrl({ rawKey: fileKey, filename }, { expiredHours: 3 }); + return await this.createPresignedPutUrl({ rawKey: fileKey, filename }, { expiredHours: 3 }); } // 单个键删除 @@ -71,13 +71,14 @@ export class S3DatasetSource extends S3PrivateBucket { } async getDatasetBase64Image(key: string): Promise { - const [stream, metadata] = await Promise.all([ - this.getFileStream(key), + const [downloadResponse, fileMetadata] = await Promise.all([ + this.client.downloadObject({ key }), this.getFileMetadata(key) ]); - const buffer = await this.fileStreamToBuffer(stream); + + const buffer = await streamConsumer.buffer(downloadResponse.body); const base64 = buffer.toString('base64'); - return `data:${metadata?.contentType || 'image/jpeg'};base64,${base64}`; + return `data:${fileMetadata?.contentType || 'image/jpeg'};base64,${base64}`; } async getDatasetFileRawText(params: GetDatasetFileContentParams) { @@ -95,16 +96,16 @@ export class S3DatasetSource extends S3PrivateBucket { }; } - const [metadata, stream] = await Promise.all([ + const [fileMetadata, downloadResponse] = await Promise.all([ this.getFileMetadata(fileId), - this.getFileStream(fileId) + this.client.downloadObject({ key: fileId }) ]); - const extension = metadata?.extension || ''; - const filename: string = decodeURIComponent(metadata?.filename || ''); + const filename = fileMetadata?.filename || ''; + const extension = fileMetadata?.extension || ''; const start = Date.now(); - const buffer = await this.fileStreamToBuffer(stream); + const buffer = await streamConsumer.buffer(downloadResponse.body); addLog.debug('get dataset file buffer', { time: Date.now() - start }); const encoding = detectFileEncoding(buffer); @@ -144,29 +145,20 @@ export class S3DatasetSource extends S3PrivateBucket { const truncatedFilename = truncateFilename(filename); const { fileKey: key } = getFileS3Key.dataset({ datasetId, filename: truncatedFilename }); - const { stream, size } = (() => { - if ('buffer' in file) { - return { - stream: file.buffer, - size: file.buffer.length - }; - } - return { - stream: file.stream, - size: file.size - }; - })(); - await MongoS3TTL.create({ minioKey: key, bucketName: this.bucketName, expiredTime: addHours(new Date(), 3) }); - await this.putObject(key, stream, size, { - 'content-type': Mimes[path.extname(truncatedFilename) as keyof typeof Mimes], - 'upload-time': new Date().toISOString(), - 'origin-filename': encodeURIComponent(truncatedFilename) + await this.client.uploadObject({ + key, + body: 'buffer' in file ? file.buffer : file.stream, + contentType: Mimes[path.extname(truncatedFilename) as keyof typeof Mimes], + metadata: { + uploadTime: new Date().toISOString(), + originFilename: encodeURIComponent(truncatedFilename) + } }); return key; diff --git a/packages/service/common/s3/sources/rawText/index.ts b/packages/service/common/s3/sources/rawText/index.ts index 5f09e0675..6e2756953 100644 --- a/packages/service/common/s3/sources/rawText/index.ts +++ b/packages/service/common/s3/sources/rawText/index.ts @@ -8,6 +8,7 @@ import { MongoS3TTL } from '../../schema'; import { addMinutes } from 'date-fns'; import { getFileS3Key } from '../../utils'; import { createHash } from 'node:crypto'; +import streamConsumer from 'node:stream/consumers'; export class S3RawTextSource extends S3PrivateBucket { constructor() { @@ -16,10 +17,10 @@ export class S3RawTextSource extends S3PrivateBucket { // 获取文件元数据 async getFilename(key: string) { - const stat = await this.statObject(key); - if (!stat) return ''; + const metadataResponse = await this.client.getObjectMetadata({ key }); + if (!metadataResponse) return ''; - const filename: string = decodeURIComponent(stat.metaData['origin-filename']); + const filename: string = decodeURIComponent(metadataResponse.metadata.originFilename || ''); return filename; } @@ -38,10 +39,14 @@ export class S3RawTextSource extends S3PrivateBucket { expiredTime: addMinutes(new Date(), 20) }); - await this.putObject(key, buffer, buffer.length, { - 'content-type': 'text/plain', - 'origin-filename': encodeURIComponent(sourceName), - 'upload-time': new Date().toISOString() + await this.client.uploadObject({ + key, + body: buffer, + contentType: 'text/plain', + metadata: { + originFilename: encodeURIComponent(sourceName), + uploadTime: new Date().toISOString() + } }); return key; @@ -55,13 +60,16 @@ export class S3RawTextSource extends S3PrivateBucket { if (!(await this.isObjectExists(key))) return null; - const [stream, filename] = await Promise.all([this.getFileStream(key), this.getFilename(key)]); + const [downloadResponse, fileMetadata] = await Promise.all([ + this.client.downloadObject({ key }), + this.getFileMetadata(key) + ]); - const buffer = await this.fileStreamToBuffer(stream); + const buffer = await streamConsumer.buffer(downloadResponse.body); return { text: buffer.toString('utf-8'), - filename + filename: fileMetadata?.filename || '' }; } } diff --git a/packages/service/common/s3/type.ts b/packages/service/common/s3/type.ts index ac638ad77..507b55160 100644 --- a/packages/service/common/s3/type.ts +++ b/packages/service/common/s3/type.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import type { defaultS3Options, Mimes } from './constants'; +import type { Mimes } from './constants'; import type { S3BaseBucket } from './buckets/base'; export const S3MetadataSchema = z.object({ @@ -15,8 +15,6 @@ export type S3Metadata = z.infer; export type ContentType = (typeof Mimes)[keyof typeof Mimes]; export type ExtensionType = keyof typeof Mimes; -export type S3OptionsType = typeof defaultS3Options; - export const S3SourcesSchema = z.enum(['avatar', 'chat', 'dataset', 'temp', 'rawText']); export const S3Sources = S3SourcesSchema.enum; export type S3SourceType = z.infer; diff --git a/packages/service/common/s3/utils.ts b/packages/service/common/s3/utils.ts index 4819199bc..64fb5b102 100644 --- a/packages/service/common/s3/utils.ts +++ b/packages/service/common/s3/utils.ts @@ -121,10 +121,14 @@ export async function uploadImage2S3Bucket( const base64Data = base64Img.split(',')[1] || base64Img; const buffer = Buffer.from(base64Data, 'base64'); - await bucket.putObject(uploadKey, buffer, buffer.length, { - 'content-type': mimetype, - 'upload-time': new Date().toISOString(), - 'origin-filename': encodeURIComponent(filename) + await bucket.client.uploadObject({ + key: uploadKey, + body: buffer, + contentType: mimetype, + metadata: { + uploadTime: new Date().toISOString(), + originFilename: encodeURIComponent(filename) + } }); const now = new Date(); @@ -194,7 +198,7 @@ export const getFileS3Key = { chatId: string; uId: string; appId: string; - filename: string; + filename?: string; }) => { const { formatedFilename, extension } = getFormatedFilename(filename); const basePrefix = [S3Sources.chat, appId, uId, chatId].filter(Boolean).join('/'); diff --git a/packages/service/common/security/fileUrlValidator.ts b/packages/service/common/security/fileUrlValidator.ts index 295c6d288..496e6853c 100644 --- a/packages/service/common/security/fileUrlValidator.ts +++ b/packages/service/common/security/fileUrlValidator.ts @@ -1,11 +1,11 @@ const systemWhiteList = (() => { const list: string[] = []; - if (process.env.S3_ENDPOINT) { - list.push(process.env.S3_ENDPOINT); + if (process.env.STORAGE_S3_ENDPOINT) { + list.push(process.env.STORAGE_S3_ENDPOINT); } - if (process.env.S3_EXTERNAL_BASE_URL) { + if (process.env.STORAGE_EXTERNAL_ENDPOINT) { try { - const urlData = new URL(process.env.S3_EXTERNAL_BASE_URL); + const urlData = new URL(process.env.STORAGE_EXTERNAL_ENDPOINT); list.push(urlData.hostname); } catch (error) {} } diff --git a/packages/service/core/ai/llm/utils.ts b/packages/service/core/ai/llm/utils.ts index 9af77f773..31bc2667a 100644 --- a/packages/service/core/ai/llm/utils.ts +++ b/packages/service/core/ai/llm/utils.ts @@ -171,10 +171,12 @@ export const loadRequestMessages = async ({ const url = await (async () => { if (item.key) { try { - return await getS3ChatSource().createGetChatFileURL({ - key: item.key, - external: false - }); + return ( + await getS3ChatSource().createGetChatFileURL({ + key: item.key, + external: false + }) + ).getUrl; } catch (error) {} } return imgUrl; diff --git a/packages/service/core/chat/utils.ts b/packages/service/core/chat/utils.ts index a68c1faae..d011c44e8 100644 --- a/packages/service/core/chat/utils.ts +++ b/packages/service/core/chat/utils.ts @@ -14,13 +14,15 @@ export const addPreviewUrlToChatItems = async ( async function addToChatflow(item: ChatItemType) { for await (const value of item.value) { if (value.type === ChatItemValueTypeEnum.file && value.file && value.file.key) { - value.file.url = await s3ChatSource.createGetChatFileURL({ + const { getUrl: url } = await s3ChatSource.createGetChatFileURL({ key: value.file.key, external: true }); + value.file.url = url; } } } + async function addToWorkflowTool(item: ChatItemType) { if (item.obj !== ChatRoleEnum.Human || !Array.isArray(item.value)) return; @@ -39,7 +41,7 @@ export const addPreviewUrlToChatItems = async ( for (const file of input.value) { if (!file.key) continue; - const url = await getS3ChatSource().createGetChatFileURL({ + const { getUrl: url } = await getS3ChatSource().createGetChatFileURL({ key: file.key, external: true }); @@ -85,7 +87,7 @@ export const presignVariablesFileUrls = async ({ val.map(async (item) => { if (!item.key) return item; - const url = await getS3ChatSource().createGetChatFileURL({ + const { getUrl: url } = await getS3ChatSource().createGetChatFileURL({ key: item.key, external: true }); diff --git a/packages/service/core/workflow/dispatch/child/runTool.ts b/packages/service/core/workflow/dispatch/child/runTool.ts index ec805a77e..945ca33b7 100644 --- a/packages/service/core/workflow/dispatch/child/runTool.ts +++ b/packages/service/core/workflow/dispatch/child/runTool.ts @@ -22,6 +22,7 @@ import { getNodeErrResponse } from '../utils'; import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils'; import { getAppVersionById } from '../../../../core/app/version/controller'; import { runHTTPTool } from '../../../app/http'; +import { getS3ChatSource } from '../../../../common/s3/sources/chat'; type SystemInputConfigType = { type: SystemToolSecretInputTypeEnum; @@ -53,6 +54,12 @@ export const dispatchRunTool = async (props: RunToolProps): Promise = {}; @@ -109,7 +116,8 @@ export const dispatchRunTool = async (props: RunToolProps): Promise { if (item.type !== ChatItemValueTypeEnum.file || !item.file?.key) return; - item.file.url = await getS3ChatSource().createGetChatFileURL({ + const { getUrl: url } = await getS3ChatSource().createGetChatFileURL({ key: item.file.key, external: true }); + item.file.url = url; }), // Remove stopping sign delAgentRuntimeStopSign({ diff --git a/packages/service/core/workflow/dispatch/plugin/runInput.ts b/packages/service/core/workflow/dispatch/plugin/runInput.ts index d5f693742..9835b4407 100644 --- a/packages/service/core/workflow/dispatch/plugin/runInput.ts +++ b/packages/service/core/workflow/dispatch/plugin/runInput.ts @@ -42,11 +42,12 @@ export const dispatchPluginInput = async ( for (let i = 0; i < val.length; i++) { const fileItem = val[i]; if (fileItem.key && !fileItem.url) { - val[i].url = await getS3ChatSource().createGetChatFileURL({ + const { getUrl: url } = await getS3ChatSource().createGetChatFileURL({ key: fileItem.key, external: true, expiredHours: 1 }); + val[i].url = url; } } params[key] = val.map((item) => item.url); diff --git a/packages/service/package.json b/packages/service/package.json index aad8018d2..51ffa508e 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "dependencies": { + "@fastgpt-sdk/storage": "0.5.4", "@fastgpt/global": "workspace:*", "@maxmind/geoip2-node": "^6.3.4", "@modelcontextprotocol/sdk": "^1.24.0", @@ -38,6 +39,7 @@ "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "mammoth": "^1.11.0", + "mime": "^4.1.0", "minio": "^8.0.5", "mongoose": "^8.10.1", "multer": "2.0.2", @@ -52,6 +54,7 @@ "pg": "^8.10.0", "pino": "^9.7.0", "pino-opentelemetry-transport": "^1.0.1", + "proxy-agent": "^6.5.0", "request-ip": "^3.3.0", "tiktoken": "1.0.17", "tunnel": "^0.0.6", diff --git a/packages/service/support/permission/dataset/auth.ts b/packages/service/support/permission/dataset/auth.ts index b1891c76a..15759414e 100644 --- a/packages/service/support/permission/dataset/auth.ts +++ b/packages/service/support/permission/dataset/auth.ts @@ -202,11 +202,13 @@ export async function authDatasetData({ imageId: datasetData.imageId, imagePreivewUrl: datasetData.imageId && isS3ObjectKey(datasetData.imageId, 'dataset') - ? await getS3DatasetSource().createGetDatasetFileURL({ - key: datasetData.imageId, - expiredHours: 1, - external: true - }) + ? ( + await getS3DatasetSource().createGetDatasetFileURL({ + key: datasetData.imageId, + expiredHours: 1, + external: true + }) + ).getUrl : undefined, chunkIndex: datasetData.chunkIndex, indexes: datasetData.indexes, diff --git a/packages/web/common/file/hooks/useUploadAvatar.tsx b/packages/web/common/file/hooks/useUploadAvatar.tsx index b6df4c008..079540598 100644 --- a/packages/web/common/file/hooks/useUploadAvatar.tsx +++ b/packages/web/common/file/hooks/useUploadAvatar.tsx @@ -1,4 +1,4 @@ -import { base64ToFile, fileToBase64 } from '../utils'; +import { base64ToFile, fileToBase64, putFileToS3 } from '../utils'; import { compressBase64Img } from '../img'; import { useToast } from '../../../hooks/useToast'; import { useCallback, useRef, useTransition } from 'react'; @@ -37,14 +37,20 @@ export const useUploadAvatar = ( }), file.name ); - const { url, fields } = await api({ filename: file.name }); - const formData = new FormData(); - Object.entries(fields).forEach(([k, v]) => formData.set(k, v)); - formData.set('file', compressed); - const res = await fetch(url, { method: 'POST', body: formData }); // 204 - if (res.ok && res.status === 204) { - onSuccess?.(`${imageBaseUrl}${fields.key}`); - } + const { + url, + fields: { key, ...headers } + } = await api({ filename: file.name }); + + await putFileToS3({ + url, + file: compressed, + headers, + onSuccess() { + onSuccess?.(`${imageBaseUrl}${key}`); + }, + t + }); }); }, [t, toast, api, onSuccess] diff --git a/packages/web/common/file/utils.ts b/packages/web/common/file/utils.ts index f4107301d..e6311a143 100644 --- a/packages/web/common/file/utils.ts +++ b/packages/web/common/file/utils.ts @@ -1,5 +1,7 @@ import { getErrText } from '@fastgpt/global/common/error/utils'; import Papa from 'papaparse'; +import axios, { type AxiosProgressEvent } from 'axios'; +import { parseS3UploadError } from '@fastgpt/global/common/error/s3'; export const loadFile2Buffer = ({ file, onError }: { file: File; onError?: (err: any) => void }) => new Promise((resolve, reject) => { @@ -120,3 +122,36 @@ export const base64ToFile = (base64: string, filename: string) => { } return new File([u8arr], filename, { type: mime }); }; + +export const putFileToS3 = async ({ + headers, + url, + file, + onSuccess, + onUploadProgress, + maxSize, + t +}: { + headers?: Record; + url: string; + file: File; + onSuccess?: () => void; + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; + maxSize?: number; + t: any; +}) => { + try { + const res = await axios.put(url, file, { + headers: { + ...headers + }, + onUploadProgress, + timeout: 5 * 60 * 1000 + }); + if (res.status === 200) { + onSuccess?.(); + } + } catch (error) { + Promise.reject(parseS3UploadError({ t, error, maxSize })); + } +}; diff --git a/packages/web/package.json b/packages/web/package.json index d78979557..550871c9f 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -23,13 +23,14 @@ "@monaco-editor/react": "^4.6.0", "@tanstack/react-query": "^4.24.10", "ahooks": "^3.9.5", + "axios": "^1.12.1", "date-fns": "2.30.0", "dayjs": "^1.11.7", - "next": "14.2.35", "i18next": "23.16.8", "js-cookie": "^3.0.5", "lexical": "0.12.6", "lodash": "^4.17.21", + "next": "14.2.35", "next-i18next": "15.4.2", "papaparse": "^5.4.1", "react": "18.3.1", @@ -38,13 +39,13 @@ "react-dom": "18.3.1", "react-hook-form": "7.43.1", "react-i18next": "14.1.2", + "react-markdown": "^9.0.1", "react-photo-view": "^1.2.6", "recharts": "^2.15.0", - "use-context-selector": "^1.4.4", - "zustand": "^4.3.5", - "react-markdown": "^9.0.1", "rehype-external-links": "^3.0.0", - "remark-gfm": "^4.0.1" + "remark-gfm": "^4.0.1", + "use-context-selector": "^1.4.4", + "zustand": "^4.3.5" }, "devDependencies": { "@types/js-cookie": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e47c035b..fac507199 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,8 @@ importers: specifier: ^1.2.8 version: 1.2.8 '@fastgpt-sdk/plugin': - specifier: 0.2.16 - version: 0.2.16(@types/node@20.14.0) + specifier: 0.2.17 + version: 0.2.17(@types/node@20.14.0) axios: specifier: ^1.12.1 version: 1.12.1 @@ -132,6 +132,9 @@ importers: packages/service: dependencies: + '@fastgpt-sdk/storage': + specifier: 0.5.4 + version: 0.5.4(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) '@fastgpt/global': specifier: workspace:* version: link:../global @@ -237,6 +240,9 @@ importers: mammoth: specifier: ^1.11.0 version: 1.11.0 + mime: + specifier: ^4.1.0 + version: 4.1.0 minio: specifier: ^8.0.5 version: 8.0.5 @@ -279,6 +285,9 @@ importers: pino-opentelemetry-transport: specifier: ^1.0.1 version: 1.0.1(@opentelemetry/api@1.9.0)(pino@9.7.0) + proxy-agent: + specifier: ^6.5.0 + version: 6.5.0 request-ip: specifier: ^3.3.0 version: 3.3.0 @@ -345,7 +354,7 @@ importers: version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1) '@chakra-ui/next-js': specifier: 2.4.2 - version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1) + version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.35(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1) '@chakra-ui/react': specifier: 2.10.7 version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -400,6 +409,9 @@ importers: ahooks: specifier: ^3.9.5 version: 3.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + axios: + specifier: ^1.12.1 + version: 1.12.1 date-fns: specifier: 2.30.0 version: 2.30.0 @@ -423,7 +435,7 @@ importers: version: 14.2.35(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) next-i18next: specifier: 15.4.2 - version: 15.4.2(i18next@23.16.8)(next@14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.4.2(i18next@23.16.8)(next@14.2.35(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) papaparse: specifier: ^5.4.1 version: 5.4.1 @@ -515,6 +527,9 @@ importers: '@emotion/styled': specifier: 11.11.0 version: 11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1) + '@fastgpt-sdk/storage': + specifier: 0.5.4 + version: 0.5.4(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) '@fastgpt/global': specifier: workspace:* version: link:../../packages/global @@ -981,6 +996,179 @@ packages: peerDependencies: openapi-types: '>=7' + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.948.0': + resolution: {integrity: sha512-uvEjds8aYA9SzhBS8RKDtsDUhNV9VhqKiHTcmvhM7gJO92q0WTn8/QeFTdNyLc6RxpiDyz+uBxS7PcdNiZzqfA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/client-sso@3.948.0': + resolution: {integrity: sha512-iWjchXy8bIAVBUsKnbfKYXRwhLgRg3EqCQ5FTr3JbR+QR75rZm4ZOYXlvHGztVTmtAZ+PQVA1Y4zO7v7N87C0A==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/core@3.947.0': + resolution: {integrity: sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-env@3.947.0': + resolution: {integrity: sha512-VR2V6dRELmzwAsCpK4GqxUi6UW5WNhAXS9F9AzWi5jvijwJo3nH92YNJUP4quMpgFZxJHEWyXLWgPjh9u0zYOA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-http@3.947.0': + resolution: {integrity: sha512-inF09lh9SlHj63Vmr5d+LmwPXZc2IbK8lAruhOr3KLsZAIHEgHgGPXWDC2ukTEMzg0pkexQ6FOhXXad6klK4RA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-ini@3.948.0': + resolution: {integrity: sha512-Cl//Qh88e8HBL7yYkJNpF5eq76IO6rq8GsatKcfVBm7RFVxCqYEPSSBtkHdbtNwQdRQqAMXc6E/lEB/CZUDxnA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-login@3.948.0': + resolution: {integrity: sha512-gcKO2b6eeTuZGp3Vvgr/9OxajMrD3W+FZ2FCyJox363ZgMoYJsyNid1vuZrEuAGkx0jvveLXfwiVS0UXyPkgtw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-node@3.948.0': + resolution: {integrity: sha512-ep5vRLnrRdcsP17Ef31sNN4g8Nqk/4JBydcUJuFRbGuyQtrZZrVT81UeH2xhz6d0BK6ejafDB9+ZpBjXuWT5/Q==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-process@3.947.0': + resolution: {integrity: sha512-WpanFbHe08SP1hAJNeDdBDVz9SGgMu/gc0XJ9u3uNpW99nKZjDpvPRAdW7WLA4K6essMjxWkguIGNOpij6Do2Q==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-sso@3.948.0': + resolution: {integrity: sha512-gqLhX1L+zb/ZDnnYbILQqJ46j735StfWV5PbDjxRzBKS7GzsiYoaf6MyHseEopmWrez5zl5l6aWzig7UpzSeQQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.948.0': + resolution: {integrity: sha512-MvYQlXVoJyfF3/SmnNzOVEtANRAiJIObEUYYyjTqKZTmcRIVVky0tPuG26XnB8LmTYgtESwJIZJj/Eyyc9WURQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/lib-storage@3.948.0': + resolution: {integrity: sha512-dY7wISfWgEqSHGps0DkQiDjHhCqR7bc0mMrBHZ810/j12uzhTakAcb9FlF7mFWkX6zEvz2kjxF4r91lBwNqt5w==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.948.0 + + '@aws-sdk/middleware-bucket-endpoint@3.936.0': + resolution: {integrity: sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-expect-continue@3.936.0': + resolution: {integrity: sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.947.0': + resolution: {integrity: sha512-kXXxS2raNESNO+zR0L4YInVjhcGGNI2Mx0AE1ThRhDkAt2se3a+rGf9equ9YvOqA1m8Jl/GSI8cXYvSxXmS9Ag==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-host-header@3.936.0': + resolution: {integrity: sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-location-constraint@3.936.0': + resolution: {integrity: sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-logger@3.936.0': + resolution: {integrity: sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.948.0': + resolution: {integrity: sha512-Qa8Zj+EAqA0VlAVvxpRnpBpIWJI9KUwaioY1vkeNVwXPlNaz9y9zCKVM9iU9OZ5HXpoUg6TnhATAHXHAE8+QsQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.947.0': + resolution: {integrity: sha512-DS2tm5YBKhPW2PthrRBDr6eufChbwXe0NjtTZcYDfUCXf0OR+W6cIqyKguwHMJ+IyYdey30AfVw9/Lb5KB8U8A==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-ssec@3.936.0': + resolution: {integrity: sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-user-agent@3.947.0': + resolution: {integrity: sha512-7rpKV8YNgCP2R4F9RjWZFcD2R+SO/0R4VHIbY9iZJdH2MzzJ8ZG7h8dZ2m8QkQd1fjx4wrFJGGPJUTYXPV3baA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/nested-clients@3.948.0': + resolution: {integrity: sha512-zcbJfBsB6h254o3NuoEkf0+UY1GpE9ioiQdENWv7odo69s8iaGBEQ4BDpsIMqcuiiUXw1uKIVNxCB1gUGYz8lw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/region-config-resolver@3.936.0': + resolution: {integrity: sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/s3-request-presigner@3.952.0': + resolution: {integrity: sha512-K/rJxP3O6TKTzDsBoVpExCZZlKbfY3SWNaR7ilm+mwBS/EXqY7sObYZU4Yhl+8aQlRTqDHgOOkR2+Qws0qD54Q==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.947.0': + resolution: {integrity: sha512-UaYmzoxf9q3mabIA2hc4T6x5YSFUG2BpNjAZ207EA1bnQMiK+d6vZvb83t7dIWL/U1de1sGV19c1C81Jf14rrA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/token-providers@3.948.0': + resolution: {integrity: sha512-V487/kM4Teq5dcr1t5K6eoUKuqlGr9FRWL3MIMukMERJXHZvio6kox60FZ/YtciRHRI75u14YUqm2Dzddcu3+A==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/types@3.936.0': + resolution: {integrity: sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-endpoints@3.936.0': + resolution: {integrity: sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-format-url@3.936.0': + resolution: {integrity: sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-locate-window@3.893.0': + resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-user-agent-browser@3.936.0': + resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} + + '@aws-sdk/util-user-agent-node@3.947.0': + resolution: {integrity: sha512-+vhHoDrdbb+zerV4noQk1DHaUMNzWFWPpPYjVTwW2186k5BEJIecAMChYkghRrBVJ3KPWP1+JnZwOd72F3d4rQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.930.0': + resolution: {integrity: sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==} + engines: {node: '>=18.0.0'} + + '@aws/lambda-invoke-store@0.2.2': + resolution: {integrity: sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -1072,8 +1260,8 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.25.9': @@ -1098,6 +1286,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} @@ -1612,8 +1805,8 @@ packages: resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@bany/curl-to-json@1.2.8': @@ -2347,8 +2540,12 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@fastgpt-sdk/plugin@0.2.16': - resolution: {integrity: sha512-tEpwtSLqPJCvvEk+KpdlgCl2N7n3m5ew2C9/hFKt+ahBY+eaU4WVrblJ6idSIImZ9lqOjOW4Mq7U1lO1mq06mQ==} + '@fastgpt-sdk/plugin@0.2.17': + resolution: {integrity: sha512-TU93FD9JIeAV+isoLVVbW+yX14J27Kgd5Sn8LPvYWkrorUEtWeVfd8rOzh/KXPd43hCgD3bSDZ1W3hC06Spnog==} + + '@fastgpt-sdk/storage@0.5.4': + resolution: {integrity: sha512-vjzwm3vSCcr44GUl7K+tqKVsEzx+LZoH2csr+J6eZE5yyTyouQYDxcZswseNJcriw0orSGJYtK/iFq26Ix+cuw==} + engines: {node: '>=20'} '@fastify/accept-negotiator@1.1.0': resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} @@ -2681,6 +2878,9 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -2705,6 +2905,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -3925,6 +4128,225 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@smithy/abort-controller@4.2.5': + resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader-native@4.2.1': + resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.0': + resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.3': + resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.18.7': + resolution: {integrity: sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.5': + resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.5': + resolution: {integrity: sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.5': + resolution: {integrity: sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.5': + resolution: {integrity: sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.5': + resolution: {integrity: sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.5': + resolution: {integrity: sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.6': + resolution: {integrity: sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.6': + resolution: {integrity: sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.5': + resolution: {integrity: sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.5': + resolution: {integrity: sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.5': + resolution: {integrity: sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.5': + resolution: {integrity: sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.5': + resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.3.14': + resolution: {integrity: sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.14': + resolution: {integrity: sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.6': + resolution: {integrity: sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.5': + resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.5': + resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.5': + resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.5': + resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.5': + resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.5': + resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.5': + resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.5': + resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.0': + resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.5': + resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.9.10': + resolution: {integrity: sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.9.0': + resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.5': + resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.13': + resolution: {integrity: sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.16': + resolution: {integrity: sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.2.5': + resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.5': + resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.5': + resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.6': + resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.5': + resolution: {integrity: sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} + engines: {node: '>=18.0.0'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@svgr/babel-plugin-add-jsx-attribute@6.5.1': resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -4042,6 +4464,9 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -4090,6 +4515,9 @@ packages: '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -4198,6 +4626,9 @@ packages: '@types/decompress@4.2.7': resolution: {integrity: sha512-9z+8yjKr5Wn73Pt17/ldnmQToaFHZxK0N1GHysuk/JIPT8RIdQeoInM01wWPgypRcvb6VH1drjuFpQ4zmY437g==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -4522,6 +4953,9 @@ packages: '@vitest/expect@3.1.1': resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==} + '@vitest/expect@4.0.16': + resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + '@vitest/mocker@3.1.1': resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==} peerDependencies: @@ -4533,33 +4967,59 @@ packages: vite: optional: true + '@vitest/mocker@4.0.16': + resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@3.1.1': resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==} + '@vitest/pretty-format@4.0.16': + resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} + '@vitest/runner@1.6.1': resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} '@vitest/runner@3.1.1': resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==} + '@vitest/runner@4.0.16': + resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} + '@vitest/snapshot@1.6.1': resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} '@vitest/snapshot@3.1.1': resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==} + '@vitest/snapshot@4.0.16': + resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} + '@vitest/spy@1.6.1': resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} '@vitest/spy@3.1.1': resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==} + '@vitest/spy@4.0.16': + resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} + '@vitest/utils@1.6.1': resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} '@vitest/utils@3.1.1': resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} + '@vitest/utils@4.0.16': + resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} @@ -4798,10 +5258,18 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + agentkeepalive@3.5.3: + resolution: {integrity: sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw==} + engines: {node: '>= 4.0.0'} + agentkeepalive@4.6.0: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} @@ -4825,6 +5293,14 @@ packages: ajv: optional: true + ajv-formats@1.6.1: + resolution: {integrity: sha512-4CjkH20If1lhR5CGtqkrVg3bbOtFEG80X9v6jDOIUhbzzbB+UzPBGy8GQhUNVZ0yvMHdMpawCOcy5ydGMsagGQ==} + peerDependencies: + ajv: ^7.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -4854,12 +5330,19 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@7.2.4: + resolution: {integrity: sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==} + ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ali-oss@6.23.0: + resolution: {integrity: sha512-FipRmyd16Pr/tEey/YaaQ/24Pc3HEpLM9S1DRakEuXlSLXNIJnu1oJtHM53eVYpvW3dXapSjrip3xylZUTIZVQ==} + engines: {node: '>=8'} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -4974,6 +5457,13 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -4984,6 +5474,10 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -5001,6 +5495,10 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + atomically@1.7.0: + resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} + engines: {node: '>=10.12.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -5008,10 +5506,16 @@ packages: avvio@8.4.0: resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==} + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + aws-ssl-profiles@1.1.2: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + axe-core@4.10.3: resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} engines: {node: '>=4'} @@ -5085,6 +5589,13 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -5115,6 +5626,12 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@1.9.4: + resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==} + + bowser@2.13.1: + resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} + boxen@7.1.1: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} @@ -5170,12 +5687,18 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + bullmq@5.52.2: resolution: {integrity: sha512-fK/dKIv8ymyys4K+zeNEPA+yuYWzRPmBWUmwIMz8DvYekadl8VG19yUx94Na0n0cLAi+spdn3a/+ufkYK7CBUg==} @@ -5250,6 +5773,9 @@ packages: caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -5261,6 +5787,10 @@ packages: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -5506,6 +6036,10 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} + conf@9.0.2: + resolution: {integrity: sha512-rLSiilO85qHgaTBIIHQpsv8z+NnVfZq3cKuYNCXN1AOqPzced0GWZEe/A517VldRLyQYXUMyV+vszavE2jSAqw==} + engines: {node: '>=10'} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -5558,12 +6092,18 @@ packages: copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + copy-to@2.0.1: + resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + core-js-compat@3.41.0: resolution: {integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==} core-js@3.41.0: resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==} + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -5571,6 +6111,10 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cos-nodejs-sdk-v5@2.15.4: + resolution: {integrity: sha512-TP/iYTvKKKhRK89on9SRfSMGEw/9SFAAU8EC1kdT5Fmpx7dAwaCNM2+R2H1TSYoQt+03rwOs8QEfNkX8GOHjHQ==} + engines: {node: '>= 6'} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -5801,6 +6345,14 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -5817,9 +6369,16 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + dateformat@2.2.0: + resolution: {integrity: sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==} + dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debounce-fn@4.0.0: + resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} + engines: {node: '>=10'} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -5940,6 +6499,10 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-user-agent@1.0.0: + resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==} + engines: {node: '>= 0.10.0'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -5958,6 +6521,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -6022,6 +6589,10 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} + digest-header@1.1.0: + resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==} + engines: {node: '>= 8.0.0'} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -6100,6 +6671,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -6155,6 +6729,10 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-or-error@1.0.1: + resolution: {integrity: sha512-OclLMSug+k2A0JKuf494im25ANRBVW8qsjmwbgX7lQ8P82H21PQ1PWkoYwb9y5yMBS69BPlwtzdIFClo3+7kOQ==} + engines: {node: '>= 0.11.14'} + enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -6195,6 +6773,9 @@ packages: es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -6211,6 +6792,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.43.0: + resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -6253,6 +6837,11 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + eslint-config-next@14.2.26: resolution: {integrity: sha512-KZNh1xvWG1ZDFD2f2WkvvnMpp7Sjsl6xJXCsvfEe8GH1FLXn6GtXo7lY9S8xDcn6oBWmKA0hSrlrp1DNQ9QDnQ==} peerDependencies: @@ -6448,6 +7037,10 @@ packages: resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} engines: {node: '>=12.0.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6469,6 +7062,10 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -6476,6 +7073,10 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} @@ -6521,10 +7122,18 @@ packages: fast-uri@3.0.6: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + fast-xml-parser@4.5.3: resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} hasBin: true + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + fastify-plugin@4.5.1: resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} @@ -6554,6 +7163,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -6611,6 +7229,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -6653,6 +7275,9 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + fork-ts-checker-webpack-plugin@9.0.2: resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} engines: {node: '>=12.13.0', yarn: '>=1.0.0'} @@ -6667,6 +7292,10 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} + form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -6683,6 +7312,9 @@ packages: resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} deprecated: 'ACTION REQUIRED: SWITCH TO v3 - v1 and v2 are VULNERABLE! v1 is DEPRECATED FOR OVER 2 YEARS! Use formidable@latest or try formidable-mini for fresh projects' + formstream@1.5.2: + resolution: {integrity: sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -6782,6 +7414,9 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-ready@1.0.0: + resolution: {integrity: sha512-mFXCZPJIlcYcth+N8267+mghfYN9h3EhsDa6JSnbA3Wrhh/XFpuowviFcsDeYZtKspQyWyJqfs4O6P8CHeTwzw==} + get-stream@2.3.1: resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} engines: {node: '>=0.10.0'} @@ -6805,6 +7440,16 @@ packages: get-tsconfig@4.10.0: resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -6876,6 +7521,15 @@ packages: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} + har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + + har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -7037,6 +7691,10 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} @@ -7246,6 +7904,9 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true + is-class-hotfix@0.0.6: + resolution: {integrity: sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -7264,6 +7925,10 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -7402,6 +8067,9 @@ packages: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} + is-type-of@1.4.0: + resolution: {integrity: sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==} + is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -7459,6 +8127,9 @@ packages: isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -7654,6 +8325,9 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-base64@2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -7679,6 +8353,9 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} @@ -7715,6 +8392,12 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@7.0.3: + resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -7722,6 +8405,9 @@ packages: resolution: {integrity: sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==} engines: {node: '>= 4'} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -7758,6 +8444,13 @@ packages: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} + jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + + jstoxml@2.2.9: + resolution: {integrity: sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -7955,6 +8648,10 @@ packages: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -8081,8 +8778,8 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} @@ -8423,10 +9120,19 @@ packages: engines: {node: '>=10.0.0'} hasBin: true + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-fn@3.1.0: + resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} + engines: {node: '>=8'} + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -8657,6 +9363,10 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + new-find-package-json@2.0.0: resolution: {integrity: sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==} engines: {node: '>=12.22.0'} @@ -8673,6 +9383,7 @@ packages: next@14.2.33: resolution: {integrity: sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==} engines: {node: '>=18.17.0'} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -8777,6 +9488,10 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} hasBin: true + node-hex@1.0.1: + resolution: {integrity: sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==} + engines: {node: '>=8.0.0'} + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -8818,6 +9533,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -8860,6 +9578,9 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -8925,10 +9646,20 @@ packages: resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} engines: {node: '>=16'} + os-name@1.0.3: + resolution: {integrity: sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==} + engines: {node: '>=0.10.0'} + hasBin: true + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + osx-release@1.1.0: + resolution: {integrity: sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==} + engines: {node: '>=0.10.0'} + hasBin: true + otlp-logger@1.1.10: resolution: {integrity: sha512-/8sCaoUJQ9Cqqz2bVTC5bfYeRs9SLIvr0BgPj4XXhD+1YuLhCDsBqVT8I9H8I4dnirSwgHcXjgQUNoAo889GqA==} @@ -8959,6 +9690,10 @@ packages: resolution: {integrity: sha512-ATHLtwoTNDloHRFFxFJdHnG6n2WUeFjaR8XQMFdKIv0xkXjrER8/iG9iu265jOM95zXHAfv9oTkqhrfbIzosrQ==} engines: {node: '>=20'} + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -8975,6 +9710,14 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -9022,6 +9765,10 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -9074,6 +9821,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + pdfjs-dist@4.10.38: resolution: {integrity: sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==} engines: {node: '>=20'} @@ -9085,6 +9835,9 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} @@ -9142,6 +9895,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -9193,6 +9950,13 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -9372,9 +10136,16 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -9398,6 +10169,10 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + qs@6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + query-string@7.1.3: resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} engines: {node: '>=6'} @@ -9745,6 +10520,11 @@ packages: request-ip@3.3.0: resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==} + request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -9921,6 +10701,9 @@ packages: resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} engines: {node: '>=0.10.0'} + sdk-base@2.0.1: + resolution: {integrity: sha512-eeG26wRwhtwYuKGCDM3LixCaxY27Pa/5lK4rLKhQa7HBjJ3U3Y+f81MMZQRsDw/8SC2Dao/83yJTXJ8aULuN8Q==} + secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -9932,6 +10715,10 @@ packages: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} engines: {node: '>=12'} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -10137,6 +10924,11 @@ packages: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + ssri@10.0.6: resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -10167,6 +10959,10 @@ packages: state-toggle@1.0.3: resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==} + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -10175,6 +10971,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@3.8.1: resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} @@ -10182,12 +10981,22 @@ packages: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + stream-chain@2.2.5: resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + stream-http@2.8.2: + resolution: {integrity: sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==} + stream-json@1.9.1: resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + stream-wormhole@1.1.0: + resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} + engines: {node: '>=4.0.0'} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -10296,6 +11105,9 @@ packages: strnum@1.1.2: resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + strnum@2.1.2: + resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} + strtok3@9.1.1: resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==} engines: {node: '>=16'} @@ -10500,10 +11312,18 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.12: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} @@ -10516,6 +11336,10 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.1: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} @@ -10531,6 +11355,9 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-arraybuffer@1.0.1: + resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} + to-buffer@1.1.1: resolution: {integrity: sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==} @@ -10557,6 +11384,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -10685,6 +11516,9 @@ packages: turndown@7.2.0: resolution: {integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==} + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -10790,6 +11624,10 @@ packages: undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + unescape@1.0.1: + resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==} + engines: {node: '>=0.10.0'} + unhead@1.11.20: resolution: {integrity: sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==} @@ -10890,6 +11728,15 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + urllib@2.44.0: + resolution: {integrity: sha512-zRCJqdfYllRDA9bXUtx+vccyRqtJPKsw85f44zH7zPD28PIvjMqIgw9VwoTLV7xTBWZsbebUFVHU5ghQcWku2A==} + engines: {node: '>= 0.10.0'} + peerDependencies: + proxy-agent: ^5.0.0 + peerDependenciesMeta: + proxy-agent: + optional: true + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -10966,10 +11813,19 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + utility@1.18.0: + resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==} + engines: {node: '>= 0.12.0'} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -10994,6 +11850,10 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + vfile-location@2.0.6: resolution: {integrity: sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==} @@ -11149,6 +12009,40 @@ packages: jsdom: optional: true + vitest@4.0.16: + resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.16 + '@vitest/browser-preview': 4.0.16 + '@vitest/browser-webdriverio': 4.0.16 + '@vitest/ui': 4.0.16 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -11296,6 +12190,10 @@ packages: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} + win-release@1.1.1: + resolution: {integrity: sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==} + engines: {node: '>=0.10.0'} + winston-transport@4.9.0: resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} engines: {node: '>= 12.0.0'} @@ -11469,9 +12367,6 @@ packages: zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} - zod@3.25.51: - resolution: {integrity: sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -11567,6 +12462,514 @@ snapshots: call-me-maybe: 1.0.2 openapi-types: 12.1.3 + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + 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.936.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.948.0': + 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.947.0 + '@aws-sdk/credential-provider-node': 3.948.0 + '@aws-sdk/middleware-bucket-endpoint': 3.936.0 + '@aws-sdk/middleware-expect-continue': 3.936.0 + '@aws-sdk/middleware-flexible-checksums': 3.947.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-location-constraint': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.948.0 + '@aws-sdk/middleware-sdk-s3': 3.947.0 + '@aws-sdk/middleware-ssec': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.947.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/signature-v4-multi-region': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.947.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.7 + '@smithy/eventstream-serde-browser': 4.2.5 + '@smithy/eventstream-serde-config-resolver': 4.3.5 + '@smithy/eventstream-serde-node': 4.2.5 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-blob-browser': 4.2.6 + '@smithy/hash-node': 4.2.5 + '@smithy/hash-stream-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/md5-js': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/middleware-retry': 4.4.14 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.13 + '@smithy/util-defaults-mode-node': 4.2.16 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + '@smithy/util-waiter': 4.2.5 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.948.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.947.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.948.0 + '@aws-sdk/middleware-user-agent': 3.947.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.947.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.7 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/middleware-retry': 4.4.14 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.13 + '@smithy/util-defaults-mode-node': 4.2.16 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.947.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws-sdk/xml-builder': 3.930.0 + '@smithy/core': 3.18.7 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.947.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.947.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.948.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/credential-provider-env': 3.947.0 + '@aws-sdk/credential-provider-http': 3.947.0 + '@aws-sdk/credential-provider-login': 3.948.0 + '@aws-sdk/credential-provider-process': 3.947.0 + '@aws-sdk/credential-provider-sso': 3.948.0 + '@aws-sdk/credential-provider-web-identity': 3.948.0 + '@aws-sdk/nested-clients': 3.948.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.948.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/nested-clients': 3.948.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.948.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.947.0 + '@aws-sdk/credential-provider-http': 3.947.0 + '@aws-sdk/credential-provider-ini': 3.948.0 + '@aws-sdk/credential-provider-process': 3.947.0 + '@aws-sdk/credential-provider-sso': 3.948.0 + '@aws-sdk/credential-provider-web-identity': 3.948.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.947.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.948.0': + dependencies: + '@aws-sdk/client-sso': 3.948.0 + '@aws-sdk/core': 3.947.0 + '@aws-sdk/token-providers': 3.948.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.948.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/nested-clients': 3.948.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/lib-storage@3.948.0(@aws-sdk/client-s3@3.948.0)': + dependencies: + '@aws-sdk/client-s3': 3.948.0 + '@smithy/abort-controller': 4.2.5 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/smithy-client': 4.9.10 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.947.0': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.948.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws/lambda-invoke-store': 0.2.2 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.947.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.18.7 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.947.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@smithy/core': 3.18.7 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.948.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.947.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.948.0 + '@aws-sdk/middleware-user-agent': 3.947.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.947.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.7 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/middleware-retry': 4.4.14 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.13 + '@smithy/util-defaults-mode-node': 4.2.16 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.952.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-format-url': 3.936.0 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.947.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.948.0': + dependencies: + '@aws-sdk/core': 3.947.0 + '@aws-sdk/nested-clients': 3.948.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.936.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-endpoints': 3.2.5 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + bowser: 2.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.947.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.947.0 + '@aws-sdk/types': 3.936.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.930.0': + dependencies: + '@smithy/types': 4.9.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.2': {} + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -11588,7 +12991,7 @@ snapshots: '@babel/traverse': 7.26.10 '@babel/types': 7.26.10 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -11649,7 +13052,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.25.9': dependencies: '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -11671,7 +13074,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 '@babel/helper-plugin-utils@7.26.5': {} @@ -11706,7 +13109,7 @@ snapshots: '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.25.9': {} @@ -11714,7 +13117,7 @@ snapshots: dependencies: '@babel/template': 7.26.9 '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -11729,7 +13132,11 @@ snapshots: '@babel/parser@7.28.4': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': dependencies: @@ -12362,10 +13769,10 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bany/curl-to-json@1.2.8': dependencies: @@ -12436,14 +13843,6 @@ snapshots: next: 14.2.35(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) react: 18.3.1 - '@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)': - dependencies: - '@chakra-ui/react': 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@emotion/cache': 11.14.0 - '@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1) - next: 14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) - react: 18.3.1 - '@chakra-ui/object-utils@2.1.0': {} '@chakra-ui/react-use-safe-layout-effect@2.1.0(react@18.3.1)': @@ -13046,7 +14445,7 @@ snapshots: '@eslint/js@8.57.1': {} - '@fastgpt-sdk/plugin@0.2.16(@types/node@20.14.0)': + '@fastgpt-sdk/plugin@0.2.17(@types/node@20.14.0)': dependencies: '@fortaine/fetch-event-source': 3.0.6 '@ts-rest/core': 3.52.1(@types/node@20.14.0)(zod@3.25.76) @@ -13054,6 +14453,74 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@fastgpt-sdk/storage@0.5.4(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)': + dependencies: + '@aws-sdk/client-s3': 3.948.0 + '@aws-sdk/lib-storage': 3.948.0(@aws-sdk/client-s3@3.948.0) + '@aws-sdk/s3-request-presigner': 3.952.0 + ali-oss: 6.23.0(proxy-agent@6.5.0) + cos-nodejs-sdk-v5: 2.15.4 + es-toolkit: 1.43.0 + vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitest/browser-playwright' + - '@vitest/browser-preview' + - '@vitest/browser-webdriverio' + - '@vitest/ui' + - aws-crt + - happy-dom + - jiti + - jsdom + - less + - lightningcss + - msw + - proxy-agent + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@fastgpt-sdk/storage@0.5.4(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)': + dependencies: + '@aws-sdk/client-s3': 3.948.0 + '@aws-sdk/lib-storage': 3.948.0(@aws-sdk/client-s3@3.948.0) + '@aws-sdk/s3-request-presigner': 3.952.0 + ali-oss: 6.23.0(proxy-agent@6.5.0) + cos-nodejs-sdk-v5: 2.15.4 + es-toolkit: 1.43.0 + vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitest/browser-playwright' + - '@vitest/browser-preview' + - '@vitest/browser-webdriverio' + - '@vitest/ui' + - aws-crt + - happy-dom + - jiti + - jsdom + - less + - lightningcss + - msw + - proxy-agent + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@fastify/accept-negotiator@1.1.0': {} '@fastify/ajv-compiler@3.6.0': @@ -13485,6 +14952,11 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -13497,8 +14969,8 @@ snapshots: '@jridgewell/source-map@0.3.6': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/sourcemap-codec@1.5.0': {} @@ -13509,6 +14981,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -14097,7 +15574,7 @@ snapshots: '@npmcli/fs@3.1.1': dependencies: - semver: 7.7.2 + semver: 7.7.3 '@nuxtjs/opencollective@0.3.2(encoding@0.1.13)': dependencies: @@ -14902,6 +16379,346 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@smithy/abort-controller@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@4.2.1': + dependencies: + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.3': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/core@3.18.7': + dependencies: + '@smithy/middleware-serde': 4.2.6 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.5': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.9.0 + '@smithy/util-hex-encoding': 4.2.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.5': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.5': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.5': + dependencies: + '@smithy/eventstream-codec': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.2.6': + dependencies: + '@smithy/chunked-blob-reader': 5.2.0 + '@smithy/chunked-blob-reader-native': 4.2.1 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.5': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.3.14': + dependencies: + '@smithy/core': 3.18.7 + '@smithy/middleware-serde': 4.2.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.14': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/service-error-classification': 4.2.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.5': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.4.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + + '@smithy/shared-ini-file-loader@4.4.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.5': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/smithy-client@4.9.10': + dependencies: + '@smithy/core': 3.18.7 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/middleware-stack': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@smithy/types@4.9.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.5': + dependencies: + '@smithy/querystring-parser': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.13': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.16': + dependencies: + '@smithy/config-resolver': 4.4.3 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.5': + dependencies: + '@smithy/service-error-classification': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.6': + dependencies: + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.0': + dependencies: + tslib: 2.8.1 + + '@standard-schema/spec@1.1.0': {} + '@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -15025,6 +16842,8 @@ snapshots: '@tokenizer/token@0.3.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@trysound/sax@0.2.0': {} '@ts-rest/core@3.52.1(@types/node@20.14.0)(zod@3.25.76)': @@ -15051,30 +16870,35 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 '@types/node': 20.17.24 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect@3.4.38': dependencies: '@types/node': 20.17.24 @@ -15208,6 +17032,8 @@ snapshots: dependencies: '@types/node': 20.17.24 + '@types/deep-eql@4.0.2': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -15677,6 +17503,15 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 + '@vitest/expect@4.0.16': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + chai: 6.2.2 + tinyrainbow: 3.0.3 + '@vitest/mocker@3.1.1(vite@6.2.2(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.1.1 @@ -15693,10 +17528,30 @@ snapshots: optionalDependencies: vite: 6.2.2(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + '@vitest/mocker@4.0.16(vite@6.2.2(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 4.0.16 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.2.2(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + + '@vitest/mocker@4.0.16(vite@6.2.2(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 4.0.16 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.2.2(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + '@vitest/pretty-format@3.1.1': dependencies: tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.0.16': + dependencies: + tinyrainbow: 3.0.3 + '@vitest/runner@1.6.1': dependencies: '@vitest/utils': 1.6.1 @@ -15708,6 +17563,11 @@ snapshots: '@vitest/utils': 3.1.1 pathe: 2.0.3 + '@vitest/runner@4.0.16': + dependencies: + '@vitest/utils': 4.0.16 + pathe: 2.0.3 + '@vitest/snapshot@1.6.1': dependencies: magic-string: 0.30.17 @@ -15720,6 +17580,12 @@ snapshots: magic-string: 0.30.17 pathe: 2.0.3 + '@vitest/snapshot@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@1.6.1': dependencies: tinyspy: 2.2.1 @@ -15728,6 +17594,8 @@ snapshots: dependencies: tinyspy: 3.0.2 + '@vitest/spy@4.0.16': {} + '@vitest/utils@1.6.1': dependencies: diff-sequences: 29.6.3 @@ -15741,9 +17609,14 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 + '@vitest/utils@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + tinyrainbow: 3.0.3 + '@vue/compiler-core@3.5.13': dependencies: - '@babel/parser': 7.26.10 + '@babel/parser': 7.28.5 '@vue/shared': 3.5.13 entities: 4.5.0 estree-walker: 2.0.2 @@ -15751,7 +17624,7 @@ snapshots: '@vue/compiler-core@3.5.22': dependencies: - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@vue/shared': 3.5.22 entities: 4.5.0 estree-walker: 2.0.2 @@ -15787,7 +17660,7 @@ snapshots: '@vue/compiler-ssr': 3.5.22 '@vue/shared': 3.5.22 estree-walker: 2.0.2 - magic-string: 0.30.19 + magic-string: 0.30.21 postcss: 8.5.6 source-map-js: 1.2.1 @@ -16033,8 +17906,14 @@ snapshots: acorn@8.15.0: {} + address@1.2.2: {} + agent-base@7.1.3: {} + agentkeepalive@3.5.3: + dependencies: + humanize-ms: 1.2.1 + agentkeepalive@4.6.0: dependencies: humanize-ms: 1.2.1 @@ -16063,6 +17942,10 @@ snapshots: optionalDependencies: ajv: 8.17.1 + ajv-formats@1.6.1(ajv@7.2.4): + optionalDependencies: + ajv: 7.2.4 + ajv-formats@2.1.1(ajv@8.12.0): optionalDependencies: ajv: 8.12.0 @@ -16091,6 +17974,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@7.2.4: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + ajv@8.12.0: dependencies: fast-deep-equal: 3.1.3 @@ -16105,6 +17995,37 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ali-oss@6.23.0(proxy-agent@6.5.0): + dependencies: + address: 1.2.2 + agentkeepalive: 3.5.3 + bowser: 1.9.4 + copy-to: 2.0.1 + dateformat: 2.2.0 + debug: 4.4.3 + destroy: 1.2.0 + end-or-error: 1.0.1 + get-ready: 1.0.0 + humanize-ms: 1.2.1 + is-type-of: 1.4.0 + js-base64: 2.6.4 + jstoxml: 2.2.9 + lodash: 4.17.21 + merge-descriptors: 1.0.3 + mime: 2.6.0 + platform: 1.3.6 + pump: 3.0.2 + qs: 6.14.0 + sdk-base: 2.0.1 + stream-http: 2.8.2 + stream-wormhole: 1.1.0 + urllib: 2.44.0(proxy-agent@6.5.0) + utility: 1.18.0 + xml2js: 0.6.2 + transitivePeerDependencies: + - proxy-agent + - supports-color + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -16233,12 +18154,22 @@ snapshots: asap@2.0.6: {} + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + assertion-error@1.1.0: {} assertion-error@2.0.1: {} ast-types-flow@0.0.8: {} + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + async-function@1.0.0: {} async-mutex@0.5.0: @@ -16251,6 +18182,8 @@ snapshots: atomic-sleep@1.0.0: {} + atomically@1.7.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -16260,8 +18193,12 @@ snapshots: '@fastify/error': 3.4.1 fastq: 1.19.1 + aws-sign2@0.7.0: {} + aws-ssl-profiles@1.1.2: {} + aws4@1.13.2: {} + axe-core@4.10.3: {} axios@1.12.1: @@ -16302,7 +18239,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.26.9 - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 @@ -16372,6 +18309,12 @@ snapshots: base64-js@1.5.1: {} + basic-ftp@5.0.5: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + binary-extensions@2.3.0: {} bl@1.2.3: @@ -16430,6 +18373,10 @@ snapshots: boolbase@1.0.0: {} + bowser@1.9.4: {} + + bowser@2.13.1: {} + boxen@7.1.1: dependencies: ansi-align: 3.0.1 @@ -16490,6 +18437,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -16500,6 +18452,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + builtin-status-codes@3.0.0: {} + bullmq@5.52.2: dependencies: cron-parser: 4.9.0 @@ -16587,6 +18541,8 @@ snapshots: caniuse-lite@1.0.30001757: {} + caseless@0.12.0: {} + ccount@2.0.1: {} chai@4.5.0: @@ -16607,6 +18563,8 @@ snapshots: loupe: 3.1.3 pathval: 2.0.0 + chai@6.2.2: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -16849,6 +18807,20 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 + conf@9.0.2: + dependencies: + ajv: 7.2.4 + ajv-formats: 1.6.1(ajv@7.2.4) + atomically: 1.7.0 + debounce-fn: 4.0.0 + dot-prop: 6.0.1 + env-paths: 2.2.1 + json-schema-typed: 7.0.3 + make-dir: 3.1.0 + onetime: 5.1.2 + pkg-up: 3.1.0 + semver: 7.7.3 + confbox@0.1.8: {} config-chain@1.1.13: @@ -16892,12 +18864,16 @@ snapshots: dependencies: toggle-selection: 1.0.6 + copy-to@2.0.1: {} + core-js-compat@3.41.0: dependencies: browserslist: 4.24.4 core-js@3.41.0: {} + core-util-is@1.0.2: {} + core-util-is@1.0.3: {} cors@2.8.5: @@ -16905,6 +18881,13 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cos-nodejs-sdk-v5@2.15.4: + dependencies: + conf: 9.0.2 + fast-xml-parser: 4.2.5 + mime-types: 2.1.35 + request: 2.88.2 + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -17181,6 +19164,12 @@ snapshots: damerau-levenshtein@1.0.8: {} + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-uri-to-buffer@6.0.2: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -17203,8 +19192,14 @@ snapshots: dependencies: '@babel/runtime': 7.26.10 + dateformat@2.2.0: {} + dayjs@1.11.13: {} + debounce-fn@4.0.0: + dependencies: + mimic-fn: 3.1.0 + debounce@1.2.1: {} debug@2.6.9: @@ -17299,6 +19294,10 @@ snapshots: deepmerge@4.3.1: {} + default-user-agent@1.0.0: + dependencies: + os-name: 1.0.3 + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -17319,6 +19318,12 @@ snapshots: defu@6.1.4: {} + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + delaunator@5.0.1: dependencies: robust-predicates: 3.0.2 @@ -17362,6 +19367,8 @@ snapshots: diff@5.2.0: {} + digest-header@1.1.0: {} + dijkstrajs@1.0.3: {} dingbat-to-unicode@1.0.1: {} @@ -17445,6 +19452,11 @@ snapshots: eastasianwidth@0.2.0: {} + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -17492,6 +19504,8 @@ snapshots: dependencies: once: 1.4.0 + end-or-error@1.0.1: {} + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -17588,6 +19602,8 @@ snapshots: es-module-lexer@1.6.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -17609,6 +19625,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.43.0: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -17706,6 +19724,14 @@ snapshots: escape-string-regexp@5.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + eslint-config-next@14.2.26(eslint@8.56.0)(typescript@5.8.2): dependencies: '@next/eslint-plugin-next': 14.2.26 @@ -17754,8 +19780,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.9.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.4(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -17774,21 +19800,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0 - eslint: 8.57.1 - get-tsconfig: 4.10.0 - is-bun-module: 1.3.0 - oxc-resolver: 5.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.12 - optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -17830,17 +19841,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1): dependencies: debug: 3.2.7 @@ -17881,35 +19881,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 @@ -18215,6 +20186,8 @@ snapshots: expect-type@1.2.0: {} + expect-type@1.3.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -18298,6 +20271,10 @@ snapshots: transitivePeerDependencies: - supports-color + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + extend@3.0.2: {} external-editor@3.1.0: @@ -18306,6 +20283,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + extsprintf@1.3.0: {} + fast-content-type-parse@1.1.0: {} fast-decode-uri-component@1.0.1: {} @@ -18350,10 +20329,18 @@ snapshots: fast-uri@3.0.6: {} + fast-xml-parser@4.2.5: + dependencies: + strnum: 1.1.2 + fast-xml-parser@4.5.3: dependencies: strnum: 1.1.2 + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.2 + fastify-plugin@4.5.1: {} fastify@4.28.1: @@ -18414,6 +20401,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fecha@4.2.3: {} figures@3.2.0: @@ -18484,6 +20475,10 @@ snapshots: find-root@1.1.0: {} + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -18525,6 +20520,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + forever-agent@0.6.1: {} + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1): dependencies: '@babel/code-frame': 7.26.2 @@ -18537,7 +20534,7 @@ snapshots: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.2 + semver: 7.7.3 tapable: 2.2.1 typescript: 5.7.2 webpack: 5.97.1 @@ -18546,6 +20543,12 @@ snapshots: form-data-encoder@2.1.4: {} + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -18568,6 +20571,13 @@ snapshots: once: 1.4.0 qs: 6.14.0 + formstream@1.5.2: + dependencies: + destroy: 1.2.0 + mime: 2.6.0 + node-hex: 1.0.1 + pause-stream: 0.0.11 + forwarded@0.2.0: {} framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -18660,6 +20670,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-ready@1.0.0: {} + get-stream@2.3.1: dependencies: object-assign: 4.1.1 @@ -18684,6 +20696,22 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + github-from-package@0.0.0: {} github-slugger@2.0.0: {} @@ -18774,6 +20802,13 @@ snapshots: dependencies: duplexer: 0.1.2 + har-schema@2.0.0: {} + + har-validator@5.1.5: + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -19033,10 +21068,16 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 @@ -19045,7 +21086,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -19264,6 +21305,8 @@ snapshots: dependencies: ci-info: 3.9.0 + is-class-hotfix@0.0.6: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -19283,6 +21326,8 @@ snapshots: is-decimal@2.0.1: {} + is-extendable@0.1.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -19384,6 +21429,12 @@ snapshots: has-symbols: 1.1.0 safe-regex-test: 1.1.0 + is-type-of@1.4.0: + dependencies: + core-util-is: 1.0.3 + is-class-hotfix: 0.0.6 + isstream: 0.1.2 + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.19 @@ -19425,12 +21476,14 @@ snapshots: isomorphic.js@0.2.5: {} + isstream@0.1.2: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.26.10 - '@babel/parser': 7.26.10 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -19440,10 +21493,10 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: '@babel/core': 7.26.10 - '@babel/parser': 7.26.10 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -19761,7 +21814,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -19828,6 +21881,8 @@ snapshots: jose@6.1.3: {} + js-base64@2.6.4: {} + js-base64@3.7.8: {} js-cookie@3.0.5: {} @@ -19849,6 +21904,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@0.1.1: {} + jsbn@1.1.0: {} jschardet@3.1.1: {} @@ -19871,10 +21928,16 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@7.0.3: {} + + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-deterministic@1.0.12: {} + json-stringify-safe@5.0.1: {} + json5@1.0.2: dependencies: minimist: 1.2.8 @@ -19916,6 +21979,15 @@ snapshots: ms: 2.1.3 semver: 7.7.1 + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + jstoxml@2.2.9: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -19970,9 +22042,9 @@ snapshots: langbase@1.1.44(encoding@0.1.13)(react@19.1.1): dependencies: dotenv: 16.4.7 - openai: 4.87.3(encoding@0.1.13)(zod@3.25.51) - zod: 3.25.51 - zod-validation-error: 3.4.0(zod@3.25.51) + openai: 4.87.3(encoding@0.1.13)(zod@3.25.76) + zod: 3.25.76 + zod-validation-error: 3.4.0(zod@3.25.76) optionalDependencies: react: 19.1.1 transitivePeerDependencies: @@ -20106,6 +22178,11 @@ snapshots: mlly: 1.7.4 pkg-types: 1.3.1 + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -20224,7 +22301,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -20248,7 +22325,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 make-error@1.3.6: {} @@ -20901,8 +22978,12 @@ snapshots: mime@3.0.0: {} + mime@4.1.0: {} + mimic-fn@2.1.0: {} + mimic-fn@3.1.0: {} + mimic-fn@4.0.0: {} mimic-response@3.1.0: {} @@ -21080,7 +23161,7 @@ snapshots: mquery@5.0.0: dependencies: - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -21164,6 +23245,8 @@ snapshots: neo-async@2.6.2: {} + netmask@2.0.2: {} + new-find-package-json@2.0.0: dependencies: debug: 4.4.3 @@ -21194,18 +23277,6 @@ snapshots: react: 18.3.1 react-i18next: 14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-i18next@15.4.2(i18next@23.16.8)(next@14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.26.10 - '@types/hoist-non-react-statics': 3.3.6 - core-js: 3.41.0 - hoist-non-react-statics: 3.3.2 - i18next: 23.16.8 - i18next-fs-backend: 2.6.0 - next: 14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) - react: 18.3.1 - react-i18next: 14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-i18next@15.4.2(i18next@23.16.8)(next@15.3.5(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1): dependencies: '@babel/runtime': 7.26.10 @@ -21272,33 +23343,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.35(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1): - dependencies: - '@next/env': 14.2.35 - '@swc/helpers': 0.5.5 - busboy: 1.6.0 - caniuse-lite: 1.0.30001757 - graceful-fs: 4.2.11 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.10)(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 14.2.33 - '@next/swc-darwin-x64': 14.2.33 - '@next/swc-linux-arm64-gnu': 14.2.33 - '@next/swc-linux-arm64-musl': 14.2.33 - '@next/swc-linux-x64-gnu': 14.2.33 - '@next/swc-linux-x64-musl': 14.2.33 - '@next/swc-win32-arm64-msvc': 14.2.33 - '@next/swc-win32-ia32-msvc': 14.2.33 - '@next/swc-win32-x64-msvc': 14.2.33 - '@opentelemetry/api': 1.9.0 - sass: 1.85.1 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - next@14.2.35(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.85.1): dependencies: '@next/env': 14.2.35 @@ -21407,6 +23451,8 @@ snapshots: transitivePeerDependencies: - supports-color + node-hex@1.0.1: {} + node-int64@0.4.0: {} node-releases@2.0.19: {} @@ -21439,6 +23485,8 @@ snapshots: dependencies: boolbase: 1.0.0 + oauth-sign@0.9.0: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -21487,6 +23535,8 @@ snapshots: obuf@1.1.2: {} + obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: @@ -21525,7 +23575,7 @@ snapshots: transitivePeerDependencies: - encoding - openai@4.87.3(encoding@0.1.13)(zod@3.25.51): + openai@4.87.3(encoding@0.1.13)(zod@3.25.76): dependencies: '@types/node': 18.19.80 '@types/node-fetch': 2.6.12 @@ -21535,7 +23585,7 @@ snapshots: formdata-node: 4.4.1 node-fetch: 2.7.0(encoding@0.1.13) optionalDependencies: - zod: 3.25.51 + zod: 3.25.76 transitivePeerDependencies: - encoding @@ -21578,8 +23628,17 @@ snapshots: string-width: 6.1.0 strip-ansi: 7.1.0 + os-name@1.0.3: + dependencies: + osx-release: 1.1.0 + win-release: 1.1.1 + os-tmpdir@1.0.2: {} + osx-release@1.1.0: + dependencies: + minimist: 1.2.8 + otlp-logger@1.1.10(@opentelemetry/api@1.9.0): dependencies: '@opentelemetry/api-logs': 0.202.0 @@ -21629,6 +23688,10 @@ snapshots: dependencies: yocto-queue: 1.2.1 + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -21643,6 +23706,24 @@ snapshots: p-try@2.2.0: {} + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.4.3 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + package-json-from-dist@1.0.1: {} package-json@8.1.1: @@ -21650,7 +23731,7 @@ snapshots: got: 12.6.1 registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.7.2 + semver: 7.7.3 packrup@0.1.2: {} @@ -21710,6 +23791,8 @@ snapshots: parseurl@1.3.3: {} + path-exists@3.0.0: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -21743,6 +23826,10 @@ snapshots: pathval@2.0.0: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + pdfjs-dist@4.10.38: optionalDependencies: '@napi-rs/canvas': 0.1.69 @@ -21751,6 +23838,8 @@ snapshots: pend@1.2.0: {} + performance-now@2.1.0: {} + pg-cloudflare@1.1.1: optional: true @@ -21806,6 +23895,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + pidtree@0.6.0: {} pify@2.3.0: {} @@ -21860,6 +23951,12 @@ snapshots: mlly: 1.7.4 pathe: 2.0.3 + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + platform@1.3.6: {} + pluralize@8.0.0: {} pngjs@5.0.0: {} @@ -22024,8 +24121,25 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.3 + debug: 4.4.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + proxy-from-env@1.1.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -22049,6 +24163,8 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.5.3: {} + query-string@7.1.3: dependencies: decode-uri-component: 0.2.2 @@ -22549,6 +24665,29 @@ snapshots: request-ip@3.3.0: {} + request@2.88.2: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -22744,6 +24883,10 @@ snapshots: screenfull@5.2.0: {} + sdk-base@2.0.1: + dependencies: + get-ready: 1.0.0 + secure-json-parse@2.7.0: {} seek-bzip@1.0.6: @@ -22752,7 +24895,9 @@ snapshots: semver-diff@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 + + semver@5.7.2: {} semver@6.3.1: {} @@ -23028,6 +25173,18 @@ snapshots: sqlstring@2.3.3: {} + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + ssri@10.0.6: dependencies: minipass: 7.1.2 @@ -23050,22 +25207,41 @@ snapshots: state-toggle@1.0.3: {} + statuses@1.5.0: {} + statuses@2.0.1: {} statuses@2.0.2: {} + std-env@3.10.0: {} + std-env@3.8.1: {} stdin-discarder@0.1.0: dependencies: bl: 5.1.0 + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-chain@2.2.5: {} + stream-http@2.8.2: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 2.3.8 + to-arraybuffer: 1.0.1 + xtend: 4.0.2 + stream-json@1.9.1: dependencies: stream-chain: 2.2.5 + stream-wormhole@1.1.0: {} + streamsearch@1.1.0: {} streamx@2.22.0: @@ -23201,6 +25377,8 @@ snapshots: strnum@1.1.2: {} + strnum@2.1.2: {} + strtok3@9.1.1: dependencies: '@tokenizer/token': 0.3.0 @@ -23443,17 +25621,26 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.12: dependencies: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@0.8.4: {} tinypool@1.0.2: {} tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} + tinyspy@2.2.1: {} tinyspy@3.0.2: {} @@ -23464,6 +25651,8 @@ snapshots: tmpl@1.0.5: {} + to-arraybuffer@1.0.1: {} + to-buffer@1.1.1: {} to-regex-range@5.0.1: @@ -23483,6 +25672,11 @@ snapshots: totalist@3.0.1: {} + tough-cookie@2.5.0: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + tr46@0.0.3: {} tr46@5.1.0: @@ -23589,7 +25783,7 @@ snapshots: tsx@4.20.6: dependencies: esbuild: 0.25.11 - get-tsconfig: 4.10.0 + get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 @@ -23603,6 +25797,8 @@ snapshots: dependencies: '@mixmark-io/domino': 2.2.0 + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -23708,6 +25904,10 @@ snapshots: undici-types@7.8.0: optional: true + unescape@1.0.1: + dependencies: + extend-shallow: 2.0.1 + unhead@1.11.20: dependencies: '@unhead/dom': 1.11.20 @@ -23848,6 +26048,23 @@ snapshots: dependencies: punycode: 2.3.1 + urllib@2.44.0(proxy-agent@6.5.0): + dependencies: + any-promise: 1.3.0 + content-type: 1.0.5 + default-user-agent: 1.0.0 + digest-header: 1.1.0 + ee-first: 1.1.1 + formstream: 1.5.2 + humanize-ms: 1.2.1 + iconv-lite: 0.6.3 + pump: 3.0.2 + qs: 6.14.0 + statuses: 1.5.0 + utility: 1.18.0 + optionalDependencies: + proxy-agent: 6.5.0 + use-callback-ref@1.3.3(@types/react@18.3.1)(react@18.3.1): dependencies: react: 18.3.1 @@ -23907,8 +26124,18 @@ snapshots: is-typed-array: 1.1.15 which-typed-array: 1.1.19 + utility@1.18.0: + dependencies: + copy-to: 2.0.1 + escape-html: 1.0.3 + mkdirp: 0.5.6 + mz: 2.7.0 + unescape: 1.0.1 + utils-merge@1.0.1: {} + uuid@3.4.0: {} + uuid@8.3.2: {} uuid@9.0.1: {} @@ -23930,6 +26157,12 @@ snapshots: vary@1.1.2: {} + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + vfile-location@2.0.6: {} vfile-location@5.0.3: @@ -24190,6 +26423,82 @@ snapshots: - tsx - yaml + vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(vite@6.2.2(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 6.2.2(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 20.17.24 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(vite@6.2.2(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 6.2.2(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 24.0.13 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + void-elements@3.1.0: {} vue-component-type-helpers@3.1.1: {} @@ -24381,6 +26690,10 @@ snapshots: dependencies: string-width: 5.1.2 + win-release@1.1.1: + dependencies: + semver: 5.7.2 + winston-transport@4.9.0: dependencies: logform: 2.7.0 @@ -24560,14 +26873,12 @@ snapshots: dependencies: zod: 4.1.12 - zod-validation-error@3.4.0(zod@3.25.51): + zod-validation-error@3.4.0(zod@3.25.76): dependencies: - zod: 3.25.51 + zod: 3.25.76 zod@3.24.1: {} - zod@3.25.51: {} - zod@3.25.76: {} zod@4.1.11: {} diff --git a/projects/app/.env.template b/projects/app/.env.template index d50a573cb..af72966e4 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -34,18 +34,6 @@ AIPROXY_API_TOKEN=aiproxy # OPENAI_BASE_URL=https://api.openai.com/v1 # CHAT_API_KEY=sk-xxxx -# S3 Config -S3_EXTERNAL_BASE_URL= -S3_ENDPOINT=localhost -S3_PORT=9000 -S3_USE_SSL=false -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -S3_PUBLIC_BUCKET=fastgpt-public # 插件文件存储公开桶 -S3_PRIVATE_BUCKET=fastgpt-private # 插件文件存储公开桶 -S3_PATH_STYLE=true # forcePathStyle 默认为 true, 当且仅当设置为 false 时关闭, 其他值都为 true -S3_REGION= # 如果是本地部署的 MinIO等服务就不需要;如果是云服务就需要,比如 aws 的或者国内 oss 厂商 - # Redis URL REDIS_URL=redis://default:mypassword@127.0.0.1:6379 # mongo 数据库连接参数,本地开发连接远程数据库时,可能需要增加 directConnection=true 参数,才能连接上。 @@ -121,3 +109,62 @@ CONFIG_JSON_PATH= # CHAT_LOG_INTERVAL=10000 # # 日志来源ID前缀 # CHAT_LOG_SOURCE_ID_PREFIX=fastgpt- + +# ✅ 对象存储供应商 +# - minio: MinIO / 或者其他兼容 S3 协议的自部署对象存储服务 +# - aws-s3: AWS S3 +# - oss: 阿里云 OSS +# - cos: 腾讯云 COS +STORAGE_VENDOR=minio +# 地区 +# - minio: 通常本地部署的对象存储服务的地区设置没什么影响 比如设置为 "us-east-1" 就可以了 +# - aws-s3: 根据云服务商提供的设置 比如 "ap-northeast-1" +# - oss: 根据云服务商提供的设置 比如 "oss-cn-hangzhou" +# - cos: 根据云服务商提供的设置 比如 "ap-shanghai" +STORAGE_REGION=us-east-1 +# 身份验证凭证 +STORAGE_ACCESS_KEY_ID=minioadmin +STORAGE_SECRET_ACCESS_KEY=minioadmin +# 存储桶名称 +# - 公开桶 +# - 私有桶 +STORAGE_PUBLIC_BUCKET=fastgpt-public +STORAGE_PRIVATE_BUCKET=fastgpt-private +# 一个公开的、前端和用户可以直接访问的对象存储连接 +# - 比如 MinIO 的反向代理链接或者一个 CDN: https://s3.example.com +STORAGE_EXTERNAL_ENDPOINT= +# ️⭕ 兼容 S3 协议的对象存储需要额外填的 +# S3 端点连接 URL 为了避免歧义 填写完整的、包含协议与端口的 URL +# - 本地 MinIO: http://127.0.0.1:9000 +# - docker-compose 中的 MinIO: http://fastgpt-minio:9000 +STORAGE_S3_ENDPOINT=http://127.0.0.1:9000 +# 路径风格配置 (virtual-host style | path style) +# - true => http(s)://endpoint/{bucket}/{key} +# - false => http(s)://{bucket}.endpoint/{key} +STORAGE_S3_FORCE_PATH_STYLE=true +# 【可选】最多请求重试次数 +STORAGE_S3_MAX_RETRIES=3 +# ️⭕ 阿里云 OSS 需要额外填的 参考: https://github.com/ali-sdk/ali-oss?tab=readme-ov-file#ossoptions +# 阿里云连接端点 URL +# - 比如 oss-cn-hangzhou.aliyuncs.com +# - 如果配置了 CName 记得更换为映射的域名 比如 http(s)://example.com +STORAGE_OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com +# 【可选】自定义域名 CNAME 参考: https://help.aliyun.com/zh/oss/developer-reference/initialization-10?spm=a2c4g.11186623.help-menu-31815.d_1_1_10_1.34ec79cfj3YO6w&scm=20140722.H_111256._.OR_help-T_cn~zh-V_1#9635d0c28f3p6 +STORAGE_OSS_CNAME=false +# 【可选】是否开启 TLS +# - true +# - false +STORAGE_OSS_SECURE=false +# 【可选】Whether to use internal endpoint (intra-cloud) +STORAGE_OSS_INTERNAL=false +# ️⭕ 腾讯云 COS 需要额外填的 参考: https://cloud.tencent.com/document/product/436/8629#.E9.85.8D.E7.BD.AE.E9.A1.B9 +# 【可选】发请求时用的协议,可选项 https:、http: 默认判断当前页面是 http: 时使用 http: 否则使用 https: +# - http: +# - https: +STORAGE_COS_PROTOCOL=http: +# 【可选】是否启用全球加速域名 默认为 false 若改为 true 需要存储桶开启全球加速功能 +STORAGE_COS_USE_ACCELERATE=false +# 【可选】CNAME 的自定义域名 +STORAGE_COS_CNAME_DOMAIN= +# 【可选】请求时使用 HTTP 代理,例如:http://127.0.0.1:8080 +STORAGE_COS_PROXY= diff --git a/projects/app/package.json b/projects/app/package.json index a5cde6dd7..034cf9587 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -23,6 +23,7 @@ "@fastgpt/global": "workspace:*", "@fastgpt/service": "workspace:*", "@fastgpt/web": "workspace:*", + "@fastgpt-sdk/storage": "0.5.4", "@fortaine/fetch-event-source": "^3.0.6", "@modelcontextprotocol/sdk": "^1.24.0", "@node-rs/jieba": "2.0.1", diff --git a/projects/app/src/components/core/app/FileSelector/index.tsx b/projects/app/src/components/core/app/FileSelector/index.tsx index 5e73e7569..4a58cdd9d 100644 --- a/projects/app/src/components/core/app/FileSelector/index.tsx +++ b/projects/app/src/components/core/app/FileSelector/index.tsx @@ -26,11 +26,11 @@ import MyAvatar from '@fastgpt/web/components/common/Avatar'; import { z } from 'zod'; import { getPresignedChatFileGetUrl, getUploadChatFilePresignedUrl } from '@/web/common/file/api'; import { useContextSelector } from 'use-context-selector'; -import { POST } from '@/web/common/api/request'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { formatFileSize } from '@fastgpt/global/common/file/tools'; import { WorkflowRuntimeContext } from '@/components/core/chat/ChatContainer/context/workflowRuntimeContext'; import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation'; +import { putFileToS3 } from '@fastgpt/web/common/file/utils'; const FileSelector = ({ value, @@ -111,18 +111,20 @@ const FileSelector = ({ try { // Get Upload Post Presigned URL - const { url, fields } = await getUploadChatFilePresignedUrl({ + const { + url, + fields: { key, ...headers } + } = await getUploadChatFilePresignedUrl({ filename: file.rawFile.name, appId, chatId, outLinkAuthData }); - // Upload File to S3 - const formData = new FormData(); - Object.entries(fields).forEach(([k, v]) => formData.set(k, v)); - formData.set('file', file.rawFile); - await POST(url, formData, { + await putFileToS3({ + url, + file: file.rawFile, + headers, onUploadProgress: (e) => { if (!e.total) return; const percent = Math.round((e.loaded / e.total) * 100); @@ -133,10 +135,12 @@ const FileSelector = ({ }); handleChangeFiles(files); }, - timeout: 5 * 60 * 1000 // 5 minutes + t, + maxSize }); + const previewUrl = await getPresignedChatFileGetUrl({ - key: fields.key, + key: key, appId, outLinkAuthData }); @@ -145,7 +149,7 @@ const FileSelector = ({ files.forEach((item) => { if (item.id === file.id) { item.url = previewUrl; - item.key = fields.key; + item.key = key; item.process = 100; } }); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx index b60071a91..6dd541505 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx @@ -14,9 +14,8 @@ import { type AppFileSelectConfigType } from '@fastgpt/global/core/app/type'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import { getPresignedChatFileGetUrl, getUploadChatFilePresignedUrl } from '@/web/common/file/api'; -import { POST } from '@/web/common/api/request'; import { getUploadFileType } from '@fastgpt/global/core/app/constants'; -import { parseS3UploadError } from '@fastgpt/global/common/error/s3'; +import { putFileToS3 } from '@fastgpt/web/common/file/utils'; type UseFileUploadOptions = { fileSelectConfig: AppFileSelectConfigType; @@ -176,7 +175,11 @@ export const useFileUpload = (props: UseFileUploadOptions) => { const fileIndex = fileList.findIndex((item) => item.id === file.id)!; // Get Upload Post Presigned URL - const { url, fields, maxSize } = await getUploadChatFilePresignedUrl({ + const { + url, + fields: { key, ...headers }, + maxSize + } = await getUploadChatFilePresignedUrl({ filename: copyFile.rawFile.name, appId, chatId, @@ -184,28 +187,29 @@ export const useFileUpload = (props: UseFileUploadOptions) => { }); // Upload File to S3 - const formData = new FormData(); - Object.entries(fields).forEach(([k, v]) => formData.set(k, v)); - formData.set('file', copyFile.rawFile); - await POST(url, formData, { + await putFileToS3({ + url, + file: copyFile.rawFile, + headers, onUploadProgress: (e) => { if (!e.total) return; const percent = Math.round((e.loaded / e.total) * 100); copyFile.process = percent; updateFiles(fileIndex, copyFile); }, - timeout: 5 * 60 * 1000 // 5 minutes - }).catch((error) => Promise.reject(parseS3UploadError({ t, error, maxSize }))); + t, + maxSize + }); const previewUrl = await getPresignedChatFileGetUrl({ - key: fields.key, + key: key, appId, outLinkAuthData }); // Update file url and key copyFile.url = previewUrl; - copyFile.key = fields.key; + copyFile.key = key; updateFiles(fileIndex, copyFile); } catch (error) { errorFileIndex.push(fileList.findIndex((item) => item.id === file.id)!); diff --git a/projects/app/src/pageComponents/app/detail/components/QuickCreateDatasetModal.tsx b/projects/app/src/pageComponents/app/detail/components/QuickCreateDatasetModal.tsx index e55103fd4..ac6d659eb 100644 --- a/projects/app/src/pageComponents/app/detail/components/QuickCreateDatasetModal.tsx +++ b/projects/app/src/pageComponents/app/detail/components/QuickCreateDatasetModal.tsx @@ -18,21 +18,20 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useUploadAvatar } from '@fastgpt/web/common/file/hooks/useUploadAvatar'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { postCreateDatasetWithFiles, getDatasetById } from '@/web/core/dataset/api'; +import { postCreateDatasetWithFiles } from '@/web/core/dataset/api'; import { getUploadAvatarPresignedUrl, getUploadTempFilePresignedUrl } from '@/web/common/file/api'; -import { POST } from '@/web/common/api/request'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { getWebDefaultEmbeddingModel, getWebDefaultLLMModel } from '@/web/common/system/utils'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { formatFileSize } from '@fastgpt/global/common/file/tools'; import { getFileIcon } from '@fastgpt/global/common/file/icon'; -import { parseS3UploadError } from '@fastgpt/global/common/error/s3'; import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/type/io'; import type { ImportSourceItemType } from '@/web/core/dataset/type'; import FileSelector, { type SelectFileItemType } from '@/pageComponents/dataset/detail/Import/components/FileSelector'; import { useRouter } from 'next/router'; +import { putFileToS3 } from '@fastgpt/web/common/file/utils'; const QuickCreateDatasetModal = ({ onClose, @@ -82,15 +81,18 @@ const QuickCreateDatasetModal = ({ await Promise.all( files.map(async ({ fileId, file }) => { try { - const { url, fields, maxSize } = await getUploadTempFilePresignedUrl({ + const { + url, + fields: { key, ...headers }, + maxSize + } = await getUploadTempFilePresignedUrl({ filename: file.name }); - const formData = new FormData(); - Object.entries(fields).forEach(([k, v]) => formData.set(k, v)); - formData.set('file', file); - - await POST(url, formData, { + await putFileToS3({ + url, + file, + headers, onUploadProgress: (e) => { if (!e.total) return; const percent = Math.round((e.loaded / e.total) * 100); @@ -107,23 +109,23 @@ const QuickCreateDatasetModal = ({ ) ); }, - timeout: 5 * 60 * 1000 // 5 minutes - }) - .then(() => { + t, + maxSize, + onSuccess: () => { setSelectFiles((state) => state.map((item) => item.id === fileId ? { ...item, - dbFileId: fields.key, + dbFileId: key, isUploading: false, uploadedFileRate: 100 } : item ) ); - }) - .catch((error) => Promise.reject(parseS3UploadError({ t, error, maxSize }))); + } + }); } catch (error) { setSelectFiles((state) => state.map((item) => diff --git a/projects/app/src/pageComponents/config/ImportPluginModal.tsx b/projects/app/src/pageComponents/config/ImportPluginModal.tsx index ecbdbc8e2..b0c046416 100644 --- a/projects/app/src/pageComponents/config/ImportPluginModal.tsx +++ b/projects/app/src/pageComponents/config/ImportPluginModal.tsx @@ -55,21 +55,17 @@ const ImportPluginModal = ({ ) ); - const presignedData = await getPkgPluginUploadURL({ filename: file.name }); - - const formData = new FormData(); - Object.entries(presignedData.formData).forEach(([key, value]) => { - formData.append(key, value); + const { formData, objectName, postURL } = await getPkgPluginUploadURL({ + filename: file.name }); - formData.append('file', file.file); - await postS3UploadFile(presignedData.postURL, formData); + await postS3UploadFile(postURL, file.file, { ...formData }); setUploadedFiles((prev) => prev.map((f) => (f.name === file.name ? { ...f, status: 'parsing' } : f)) ); - const parseResult = await parseUploadedPkgPlugin({ objectName: presignedData.objectName }); + const parseResult = await parseUploadedPkgPlugin({ objectName }); const parentId = parseResult.find((item) => !item.parentId)?.toolId; if (!parentId) { diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx index e0a93f8af..1f5dc7d50 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx @@ -14,8 +14,7 @@ import { formatFileSize } from '@fastgpt/global/common/file/tools'; import { getFileIcon } from '@fastgpt/global/common/file/icon'; import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; import { getUploadDatasetFilePresignedUrl } from '@/web/common/file/api'; -import { POST } from '@/web/common/api/request'; -import { parseS3UploadError } from '@fastgpt/global/common/error/s3'; +import { putFileToS3 } from '@fastgpt/web/common/file/utils'; const DataProcess = dynamic(() => import('../commonProgress/DataProcess')); const PreviewData = dynamic(() => import('../commonProgress/PreviewData')); @@ -68,16 +67,20 @@ const SelectFile = React.memo(function SelectFile() { await Promise.all( files.map(async ({ fileId, file }) => { try { - const { url, fields, maxSize } = await getUploadDatasetFilePresignedUrl({ + const { + url, + fields: { key, ...headers }, + maxSize + } = await getUploadDatasetFilePresignedUrl({ filename: file.name, datasetId }); // Upload File to S3 - const formData = new FormData(); - Object.entries(fields).forEach(([k, v]) => formData.set(k, v)); - formData.set('file', file); - await POST(url, formData, { + await putFileToS3({ + url, + file, + headers, onUploadProgress: (e) => { if (!e.total) return; const percent = Math.round((e.loaded / e.total) * 100); @@ -94,23 +97,22 @@ const SelectFile = React.memo(function SelectFile() { ) ); }, - timeout: 5 * 60 * 1000 // 5 minutes - }) - .then(() => { + t, + onSuccess: () => { setSelectFiles((state) => state.map((item) => item.id === fileId ? { ...item, - dbFileId: fields.key, + dbFileId: key, isUploading: false, uploadedFileRate: 100 } : item ) ); - }) - .catch((error) => Promise.reject(parseS3UploadError({ t, error, maxSize }))); + } + }); } catch (error) { setSelectFiles((state) => state.map((item) => diff --git a/projects/app/src/pages/api/admin/initv4132.ts b/projects/app/src/pages/api/admin/initv4132.ts index 995887f3e..ba21c6200 100644 --- a/projects/app/src/pages/api/admin/initv4132.ts +++ b/projects/app/src/pages/api/admin/initv4132.ts @@ -2,6 +2,7 @@ import { NextAPI } from '@/service/middleware/entry'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { type NextApiRequest, type NextApiResponse } from 'next'; import { S3Buckets } from '@fastgpt/service/common/s3/constants'; +import { MinioStorageAdapter } from '@fastgpt-sdk/storage'; // 将 S3 原先的 circleLife 策略全部去掉 async function handler(req: NextApiRequest, _res: NextApiResponse) { @@ -11,8 +12,16 @@ async function handler(req: NextApiRequest, _res: NextApiResponse) { return Promise.reject('S3 not initialized'); } - await global.s3BucketMap[S3Buckets.public].client.removeBucketLifecycle(S3Buckets.public); - await global.s3BucketMap[S3Buckets.private].client.removeBucketLifecycle(S3Buckets.private); + const publicClient = global.s3BucketMap[S3Buckets.public].client; + const privateClient = global.s3BucketMap[S3Buckets.private].client; + + if (publicClient instanceof MinioStorageAdapter) { + await publicClient.removeBucketLifecycle(); + } + if (privateClient instanceof MinioStorageAdapter) { + await privateClient.removeBucketLifecycle(); + } + return {}; } diff --git a/projects/app/src/pages/api/common/file/presignTempFilePostUrl.ts b/projects/app/src/pages/api/common/file/presignTempFilePostUrl.ts index 243fa5087..11bceb7c6 100644 --- a/projects/app/src/pages/api/common/file/presignTempFilePostUrl.ts +++ b/projects/app/src/pages/api/common/file/presignTempFilePostUrl.ts @@ -38,7 +38,7 @@ async function handler( const bucket = new S3PrivateBucket(); const { fileKey } = getFileS3Key.temp({ teamId, filename }); - return await bucket.createPostPresignedUrl({ rawKey: fileKey, filename }, { expiredHours: 1 }); + return await bucket.createPresignedPutUrl({ rawKey: fileKey, filename }, { expiredHours: 1 }); } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/common/file/read/[filename].ts b/projects/app/src/pages/api/common/file/read/[filename].ts index 8b26df479..5f0f7f02a 100644 --- a/projects/app/src/pages/api/common/file/read/[filename].ts +++ b/projects/app/src/pages/api/common/file/read/[filename].ts @@ -34,7 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< getS3DatasetSource().getFileStream(fileId) ]); - if (!file) { + if (!file || !fileStream) { return Promise.reject(CommonErrEnum.fileNotFound); } @@ -49,7 +49,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< 'Content-Disposition', `${disposition}; filename="${encodeURIComponent(filename)}"` ); - res.setHeader('Content-Length', file.contentLength); + if (file.contentLength) { + res.setHeader('Content-Length', file.contentLength); + } stream.pipe(res); diff --git a/projects/app/src/pages/api/core/chat/presignChatFileGetUrl.ts b/projects/app/src/pages/api/core/chat/presignChatFileGetUrl.ts index 5658d1dca..a4b881014 100644 --- a/projects/app/src/pages/api/core/chat/presignChatFileGetUrl.ts +++ b/projects/app/src/pages/api/core/chat/presignChatFileGetUrl.ts @@ -15,7 +15,9 @@ async function handler(req: ApiRequestProps): Promi ...outLinkAuthData }); - return await getS3ChatSource().createGetChatFileURL({ key, external: true }); + const { getUrl: url } = await getS3ChatSource().createGetChatFileURL({ key, external: true }); + + return url; } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/collection/read.ts b/projects/app/src/pages/api/core/dataset/collection/read.ts index 9c9e03577..a4c77983e 100644 --- a/projects/app/src/pages/api/core/dataset/collection/read.ts +++ b/projects/app/src/pages/api/core/dataset/collection/read.ts @@ -79,11 +79,13 @@ async function handler( collection.fileId && isS3ObjectKey(collection.fileId, 'dataset') ) { - return getS3DatasetSource().createGetDatasetFileURL({ - key: collection.fileId, - expiredHours: 1, - external: true - }); + return ( + await getS3DatasetSource().createGetDatasetFileURL({ + key: collection.fileId, + expiredHours: 1, + external: true + }) + ).getUrl; } if (collection.type === DatasetCollectionTypeEnum.link && collection.rawLink) { return collection.rawLink; diff --git a/projects/app/src/pages/api/core/dataset/data/v2/list.ts b/projects/app/src/pages/api/core/dataset/data/v2/list.ts index 59f29b175..223dc6e1b 100644 --- a/projects/app/src/pages/api/core/dataset/data/v2/list.ts +++ b/projects/app/src/pages/api/core/dataset/data/v2/list.ts @@ -83,7 +83,7 @@ async function handler( const s3ImageIds = imageIds.filter((id) => isS3ObjectKey(id, 'dataset')); for (const id of s3ImageIds) { const metadata = await getS3DatasetSource().getFileMetadata(id); - if (metadata) { + if (metadata?.contentLength) { imageSizeMap.set(id, metadata.contentLength); } } diff --git a/projects/app/src/pages/api/system/file/[jwt].ts b/projects/app/src/pages/api/system/file/[jwt].ts index 35740129a..74d700a16 100644 --- a/projects/app/src/pages/api/system/file/[jwt].ts +++ b/projects/app/src/pages/api/system/file/[jwt].ts @@ -32,8 +32,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) })() ); + if (!stream) { + return jsonRes(res, { + code: 404, + error: 'File not found' + }); + } + if (metadata) { res.setHeader('Content-Type', metadata.contentType); + } + if (metadata?.contentLength) { res.setHeader('Content-Length', metadata.contentLength); } res.setHeader('Cache-Control', 'public, max-age=31536000'); diff --git a/projects/app/src/pages/openapi.tsx b/projects/app/src/pages/openapi.tsx index dc74fad7e..09cb105d5 100644 --- a/projects/app/src/pages/openapi.tsx +++ b/projects/app/src/pages/openapi.tsx @@ -23,9 +23,7 @@ function OpenAPIPage() { hideDarkModeToggle: true, hideClientButton: true, theme: 'default', - spec: { - url: '/api/openapi.json' - } + url: '/api/openapi.json' }} /> diff --git a/projects/app/src/web/common/file/api.ts b/projects/app/src/web/common/file/api.ts index 5800b9e68..a92b89376 100644 --- a/projects/app/src/web/common/file/api.ts +++ b/projects/app/src/web/common/file/api.ts @@ -1,16 +1,18 @@ -import { POST } from '@/web/common/api/request'; +import { POST, PUT } from '@/web/common/api/request'; import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import type { CreatePostPresignedUrlResult } from '@fastgpt/service/common/s3/type'; import { type AxiosProgressEvent } from 'axios'; export const postS3UploadFile = ( postURL: string, - form: FormData, + file: File, + headers?: Record, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void ) => - POST(postURL, form, { + PUT(postURL, file, { timeout: 600000, - onUploadProgress + onUploadProgress, + ...(headers ? { headers } : {}) }); export const getUploadAvatarPresignedUrl = (params: { diff --git a/projects/app/src/web/core/app/api/tool.ts b/projects/app/src/web/core/app/api/tool.ts index 295f3076f..6d9c2c85f 100644 --- a/projects/app/src/web/core/app/api/tool.ts +++ b/projects/app/src/web/core/app/api/tool.ts @@ -1,4 +1,4 @@ -import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; +import { GET, POST } from '@/web/common/api/request'; import type { createHttpToolsBody } from '@/pages/api/core/app/httpTools/create'; import type { UpdateHttpPluginBody } from '@/pages/api/core/app/httpTools/update'; import type { diff --git a/sdk/storage/.node-version b/sdk/storage/.node-version new file mode 100644 index 000000000..9de225682 --- /dev/null +++ b/sdk/storage/.node-version @@ -0,0 +1 @@ +lts/iron diff --git a/sdk/storage/README.md b/sdk/storage/README.md new file mode 100644 index 000000000..d5b912d4b --- /dev/null +++ b/sdk/storage/README.md @@ -0,0 +1,200 @@ +# @fastgpt-sdk/storage + +FastGPT 的对象存储 SDK,提供 **统一的、与厂商无关的**存储接口(S3/MinIO/OSS/COS 等),用于上传、下载、删除、列举对象以及获取元数据。 + +> 本包为 ESM(`"type": "module"`),并要求 Node.js **>= 20**。 + +## 安装 + +```bash +pnpm add @fastgpt-sdk/storage +``` + +## 快速开始 + +```ts +import { createStorage } from '@fastgpt-sdk/storage'; +import { createWriteStream } from 'node:fs'; + +const storage = createStorage({ + vendor: 'minio', + bucket: 'my-bucket', + region: 'us-east-1', + endpoint: 'http://127.0.0.1:9000', + credentials: { + accessKeyId: process.env.MINIO_ACCESS_KEY ?? '', + secretAccessKey: process.env.MINIO_SECRET_KEY ?? '' + }, + // minio 常见配置:若你的服务不支持 virtual-host 访问方式,可打开它 + forcePathStyle: true +}); + +// 1) 确保 bucket 存在(不存在则尝试创建) +await storage.ensureBucket(); + +// 2) 上传 +await storage.uploadObject({ + key: 'demo/hello.txt', + body: 'hello fastgpt', + contentType: 'text/plain; charset=utf-8', + metadata: { + app: 'fastgpt', + purpose: 'readme-demo' + } +}); + +// 3) 下载(流式) +const { body } = await storage.downloadObject({ key: 'demo/hello.txt' }); +body.pipe(createWriteStream('/tmp/hello.txt')); + +// 4) 删除 +await storage.deleteObject({ key: 'demo/hello.txt' }); + +// 5) 释放资源(部分 adapter 可能是空实现) +await storage.destroy(); +``` + +## 配置(IStorageOptions) + +通过 `vendor` 字段选择适配器(判别联合),不同厂商的配置项在 `IStorageOptions` 上有清晰的类型约束与中文 JSDoc。 + +### AWS S3 + +```ts +import { createStorage } from '@fastgpt-sdk/storage'; + +const storage = createStorage({ + vendor: 'aws-s3', + bucket: 'my-bucket', + region: 'ap-northeast-1', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '', + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '' + } +}); +``` + +### MinIO / 其他 S3 兼容 + +```ts +import { createStorage } from '@fastgpt-sdk/storage'; + +const storage = createStorage({ + vendor: 'minio', + bucket: 'my-bucket', + region: 'us-east-1', + endpoint: 'http://127.0.0.1:9000', + credentials: { + accessKeyId: process.env.MINIO_ACCESS_KEY ?? '', + secretAccessKey: process.env.MINIO_SECRET_KEY ?? '' + }, + forcePathStyle: true +}); +``` + +### 阿里云 OSS + +```ts +import { createStorage } from '@fastgpt-sdk/storage'; + +const storage = createStorage({ + vendor: 'oss', + bucket: 'my-bucket', + region: 'oss-cn-hangzhou', + endpoint: process.env.OSS_ENDPOINT, // 视你的部署与 SDK 配置而定 + credentials: { + accessKeyId: process.env.OSS_ACCESS_KEY_ID ?? '', + secretAccessKey: process.env.OSS_ACCESS_KEY_SECRET ?? '' + }, + cname: false, + internal: false +}); +``` + +### 腾讯云 COS + +```ts +import { createStorage } from '@fastgpt-sdk/storage'; + +const storage = createStorage({ + vendor: 'cos', + bucket: 'my-bucket', + region: 'ap-guangzhou', + credentials: { + accessKeyId: process.env.COS_SECRET_ID ?? '', + secretAccessKey: process.env.COS_SECRET_KEY ?? '' + }, + protocol: 'https:', + useAccelerate: false +}); +``` + +## API(IStorage) + +`createStorage(options)` 返回一个实现了 `IStorage` 的实例: + +- **`ensureBucket()`**: 确保 bucket 存在(不存在时**可能**尝试创建,取决于 vendor 与权限;部分厂商仅做存在性校验并直接抛错)。 +- **`checkObjectExists({ key })`**: 判断对象是否存在。 +- **`uploadObject({ key, body, contentType?, contentLength?, contentDisposition?, metadata? })`**: 上传对象。 +- **`downloadObject({ key })`**: 下载对象(返回 `Readable`)。 +- **`deleteObject({ key })`**: 删除单个对象。 +- **`deleteObjectsByMultiKeys({ keys })`**: 按 key 列表批量删除(返回失败 key 列表)。 +- **`deleteObjectsByPrefix({ prefix })`**: 按前缀批量删除(高危,务必使用非空 prefix;返回失败 key 列表)。 +- **`generatePresignedPutUrl({ key, expiredSeconds?, metadata? })`**: 生成 **PUT** 预签名 URL(用于前端直传)。 +- **`generatePresignedGetUrl({ key, expiredSeconds? })`**: 生成 **GET** 预签名 URL(用于临时授权下载)。 +- **`listObjects({ prefix? })`**: 列出对象 key(可按前缀过滤;不传则列出整个 bucket 内对象)。 +- **`getObjectMetadata({ key })`**: 获取对象元数据。 +- **`destroy()`**: 资源清理/连接释放。 + +> 重要:当前实现状态(以代码为准): +> - `generatePresignedPutUrl`:**AWS S3 / MinIO / COS / OSS 已实现**。 +> - `generatePresignedGetUrl`:目前各 adapter 仍为 **未实现**(会抛 `Error('Method not implemented.')`)。 + +### 预签名 PUT 直传示例(浏览器 / 前端) + +`generatePresignedPutUrl` 返回的 `metadata` 字段语义更接近“需要带上的 headers”(不同厂商前缀不同,如 `x-oss-meta-*` / `x-cos-meta-*`)。 + +```ts +const { putUrl, metadata } = await storage.generatePresignedPutUrl({ + key: 'demo/hello.txt', + expiredSeconds: 600, + metadata: { app: 'fastgpt', purpose: 'direct-upload' } +}); + +await fetch(putUrl, { + method: 'PUT', + headers: { + // 将 adapter 返回的 headers 带上(若为空对象也没关系) + ...metadata, + 'content-type': 'text/plain; charset=utf-8' + }, + body: 'hello fastgpt' +}); +``` + +## 错误与异常 + +导出的错误类型: + +- **`NoSuchBucketError`**: bucket 不存在(部分 adapter 会用它包装底层错误)。 +- **`NoBucketReadPermissionError`**: bucket 无读取权限(部分 adapter 会用它包装底层错误)。 +- **`EmptyObjectError`**: 下载时对象为空(例如底层 SDK 返回 `Body` 为空)。 + +建议你在业务层做分层处理:可恢复错误(重试/提示权限)与不可恢复错误(配置错误/接口未实现)。 + +## 注意事项 + +- **按前缀删除是高危操作**:`prefix` 必须是非空字符串;强烈建议使用业务隔离前缀(例如 `team/{teamId}/`),避免误删整桶。 +- **metadata 厂商差异**:不同厂商对元数据 key 前缀/大小写/可用字符/大小限制不同,建议使用简单 ASCII key,并控制总体大小。 +- **流式下载/上传**:大文件建议使用 `Readable`,减少内存峰值。 + +## 开发与构建 + +```bash +pnpm -C FastGPT/packages/storage dev +pnpm -C FastGPT/packages/storage build +``` + +发布前会执行 `prepublishOnly` 自动构建产物到 `dist/`。 + + diff --git a/sdk/storage/package.json b/sdk/storage/package.json new file mode 100644 index 000000000..076b399ac --- /dev/null +++ b/sdk/storage/package.json @@ -0,0 +1,65 @@ +{ + "name": "@fastgpt-sdk/storage", + "private": false, + "version": "0.5.4", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "packageManager": "pnpm@9.15.9", + "description": "FastGPT SDK for object storage", + "author": "FastGPT", + "repository": { + "type": "git", + "url": "https://github.com/labring/FastGPT.git", + "directory": "FastGPT/packages/storage" + }, + "homepage": "https://github.com/labring/FastGPT", + "bugs": { + "url": "https://github.com/labring/FastGPT/issues" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=20" + }, + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "license": "MIT", + "keywords": [ + "object-storage", + "storage", + "aws-s3", + "cos", + "minio", + "oss" + ], + "scripts": { + "build": "tsdown", + "dev": "tsdown --watch", + "prepublishOnly": "pnpm build" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.948.0", + "@aws-sdk/lib-storage": "^3.948.0", + "@aws-sdk/s3-request-presigner": "^3.952.0", + "ali-oss": "^6.23.0", + "cos-nodejs-sdk-v5": "^2.15.4", + "es-toolkit": "^1.43.0", + "vitest": "^4.0.16" + }, + "devDependencies": { + "@types/ali-oss": "^6.16.13", + "@types/node": "^20", + "tsdown": "^0.18.2", + "typescript": "^5.9.3" + } +} diff --git a/sdk/storage/src/adapters/aws-s3.adapter.ts b/sdk/storage/src/adapters/aws-s3.adapter.ts new file mode 100644 index 000000000..f8366d9a9 --- /dev/null +++ b/sdk/storage/src/adapters/aws-s3.adapter.ts @@ -0,0 +1,426 @@ +import { + CopyObjectCommand, + DeleteObjectCommand, + DeleteObjectsCommand, + GetObjectCommand, + HeadBucketCommand, + HeadObjectCommand, + ListObjectsV2Command, + NotFound, + PutObjectCommand, + S3Client +} from '@aws-sdk/client-s3'; +import type { IAwsS3CompatibleStorageOptions, IStorage } from '../interface'; +import type { + UploadObjectParams, + UploadObjectResult, + DownloadObjectParams, + DownloadObjectResult, + DeleteObjectParams, + DeleteObjectsParams, + DeleteObjectsResult, + PresignedPutUrlParams, + PresignedPutUrlResult, + ListObjectsParams, + ListObjectsResult, + DeleteObjectResult, + GetObjectMetadataParams, + GetObjectMetadataResult, + EnsureBucketResult, + DeleteObjectsByPrefixParams, + StorageObjectKey, + ExistsObjectParams, + ExistsObjectResult, + StorageObjectMetadata, + PresignedGetUrlParams, + PresignedGetUrlResult, + CopyObjectParams, + CopyObjectResult, + GeneratePublicGetUrlParams, + GeneratePublicGetUrlResult +} from '../types'; +import { Upload } from '@aws-sdk/lib-storage'; +import { EmptyObjectError } from '../errors'; +import type { Readable } from 'node:stream'; +import { camelCase, chunk, isNotNil, kebabCase } from 'es-toolkit'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS } from '../constants'; + +export class AwsS3StorageAdapter implements IStorage { + protected readonly client: S3Client; + + get bucketName(): string { + return this.options.bucket; + } + + constructor(protected readonly options: IAwsS3CompatibleStorageOptions) { + if (options.vendor !== 'aws-s3' && options.vendor !== 'minio') { + throw new Error('Invalid storage vendor'); + } + + this.client = new S3Client({ + region: options.region, + credentials: options.credentials, + endpoint: options.endpoint, + forcePathStyle: options.forcePathStyle, + maxAttempts: options.maxRetries + }); + } + + async checkObjectExists(params: ExistsObjectParams): Promise { + const { key } = params; + + let exists = false; + + try { + await this.client.send( + new HeadObjectCommand({ + Bucket: this.options.bucket, + Key: key + }) + ); + exists = true; + } catch (error) { + if (error instanceof NotFound) { + exists = false; + } else { + throw error; + } + } + + return { + key, + exists, + bucket: this.options.bucket + }; + } + + async getObjectMetadata(params: GetObjectMetadataParams): Promise { + const { key } = params; + + const result = await this.client.send( + new HeadObjectCommand({ + Bucket: this.options.bucket, + Key: key + }) + ); + + let metadata: StorageObjectMetadata = {}; + if (result.Metadata) { + for (const [k, v] of Object.entries(result.Metadata)) { + if (!k) continue; + metadata[camelCase(k)] = String(v); + } + } + + return { + key, + metadata, + etag: result.ETag, + bucket: this.options.bucket, + contentType: result.ContentType, + contentLength: result.ContentLength + }; + } + + async ensureBucket(): Promise { + await this.client.send(new HeadBucketCommand({ Bucket: this.options.bucket })); + + return { + exists: true, + created: false, + bucket: this.options.bucket + }; + } + + async uploadObject(params: UploadObjectParams): Promise { + const { key, body, contentType, contentLength, contentDisposition, metadata } = params; + + let meta: StorageObjectMetadata = {}; + if (metadata) { + for (const [k, v] of Object.entries(metadata)) { + if (!k) continue; + meta[kebabCase(k)] = String(v); + } + } + + const upload = new Upload({ + client: this.client, + params: { + Bucket: this.options.bucket, + Key: key, + Body: body, + ContentType: contentType, + ContentLength: contentLength, + ContentDisposition: contentDisposition, + Metadata: meta + } + }); + + await upload.done(); + + return { + key, + bucket: this.options.bucket + }; + } + + async downloadObject(params: DownloadObjectParams): Promise { + const { key } = params; + + const result = await this.client.send( + new GetObjectCommand({ + Bucket: this.options.bucket, + Key: key + }) + ); + + if (!result.Body) { + throw new EmptyObjectError('Object is undefined'); + } + + return { + key, + bucket: this.options.bucket, + body: result.Body as Readable + }; + } + + async deleteObject(params: DeleteObjectParams): Promise { + const { key } = params; + + await this.client.send( + new DeleteObjectCommand({ + Key: key, + Bucket: this.options.bucket + }) + ); + + return { + key, + bucket: this.options.bucket + }; + } + + async deleteObjectsByMultiKeys(params: DeleteObjectsParams): Promise { + const { keys } = params; + + if (keys.length === 0) { + return { + bucket: this.options.bucket, + keys: [] + }; + } + + const chunks = chunk(keys, 1000); + const fails: StorageObjectKey[] = []; + + for (const chunk of chunks) { + const result = await this.client.send( + new DeleteObjectsCommand({ + Bucket: this.options.bucket, + Delete: { + Objects: chunk.map((key) => ({ Key: key })), + Quiet: true + } + }) + ); + fails.push(...(result.Errors?.map((error) => error.Key).filter(isNotNil) ?? [])); + } + + return { + bucket: this.options.bucket, + keys: fails + }; + } + + async deleteObjectsByPrefix(params: DeleteObjectsByPrefixParams): Promise { + const { prefix } = params; + if (!prefix) { + throw new Error('Prefix is required'); + } + + let fails: StorageObjectKey[] = []; + let isTruncated = false; + let continuationToken: string | undefined = undefined; + + do { + const listResponse = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.options.bucket, + Prefix: prefix, + ContinuationToken: continuationToken, + MaxKeys: 1000 + }) + ); + + if (!listResponse.Contents || listResponse.Contents.length === 0) { + return { + bucket: this.options.bucket, + keys: [] + }; + } + + const objectsToDelete = listResponse.Contents.map((content) => ({ Key: content.Key })); + const deleteResponse = await this.client.send( + new DeleteObjectsCommand({ + Bucket: this.options.bucket, + Delete: { + Objects: objectsToDelete, + Quiet: true + } + }) + ); + + fails.push(...(deleteResponse.Errors?.map((error) => error.Key).filter(isNotNil) ?? [])); + + isTruncated = listResponse.IsTruncated ?? false; + continuationToken = listResponse.NextContinuationToken as string | undefined; + } while (isTruncated); + + return { + bucket: this.options.bucket, + keys: fails + }; + } + + async generatePresignedPutUrl(params: PresignedPutUrlParams): Promise { + const { key, expiredSeconds, metadata, contentType } = params; + + const expiresIn = expiredSeconds ? expiredSeconds : DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS; + + // For S3-compatible vendors, metadata is carried by `x-amz-meta-*` headers. + // We return the expected header map so callers can do browser direct-upload with the same metadata. + const meta: Record = {}; + if (metadata) { + for (const [k, v] of Object.entries(metadata)) { + if (!k) continue; + meta[kebabCase(k)] = String(v); + } + } + + if (contentType) { + meta['Content-Type'] = contentType; + } + + const url = await getSignedUrl( + this.client, + new PutObjectCommand({ + Bucket: this.options.bucket, + Key: key, + Metadata: meta + }), + { + expiresIn + } + ); + + return { + key, + putUrl: url, + bucket: this.options.bucket, + metadata: { + 'Content-Type': meta['Content-Type'] + } + }; + } + + async generatePresignedGetUrl(params: PresignedGetUrlParams): Promise { + const { key, expiredSeconds } = params; + + const expiresIn = expiredSeconds ? expiredSeconds : DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS; + + const url = await getSignedUrl( + this.client, + new GetObjectCommand({ + Bucket: this.options.bucket, + Key: key + }), + { + expiresIn + } + ); + + return { + key, + getUrl: url, + bucket: this.options.bucket + }; + } + + generatePublicGetUrl(params: GeneratePublicGetUrlParams): GeneratePublicGetUrlResult { + const { key } = params; + + let url: string; + if (this.options.forcePathStyle) { + url = `${this.options.endpoint}/${this.options.bucket}/${key}`; + } else { + const endpoint = new URL(this.options.endpoint); + url = `${endpoint.protocol}//${this.options.bucket}.${endpoint.host}/${key}`; + } + + return { + key, + publicGetUrl: url, + bucket: this.options.bucket + }; + } + + async listObjects(params: ListObjectsParams): Promise { + const { prefix } = params; + + let keys: StorageObjectKey[] = []; + let isTruncated = false; + let continuationToken: string | undefined = undefined; + + do { + const result = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.options.bucket, + Prefix: prefix, + ContinuationToken: continuationToken, + MaxKeys: 1000 + }) + ); + + if (!result.Contents || result.Contents.length === 0) { + return { + bucket: this.options.bucket, + keys + }; + } + + keys = keys.concat(result.Contents.map((content) => content.Key).filter(isNotNil)); + + isTruncated = result.IsTruncated ?? false; + continuationToken = result.NextContinuationToken as string | undefined; + } while (isTruncated); + + return { + bucket: this.options.bucket, + keys + }; + } + + async copyObjectInSelfBucket(params: CopyObjectParams): Promise { + const { sourceKey, targetKey } = params; + + await this.client.send( + new CopyObjectCommand({ + Bucket: this.options.bucket, + CopySource: `${this.options.bucket}/${sourceKey}`, + Key: targetKey + }) + ); + + return { + bucket: this.options.bucket, + sourceKey, + targetKey + }; + } + + async destroy(): Promise { + this.client.destroy(); + } +} diff --git a/sdk/storage/src/adapters/cos.adapter.ts b/sdk/storage/src/adapters/cos.adapter.ts new file mode 100644 index 000000000..08936c2ac --- /dev/null +++ b/sdk/storage/src/adapters/cos.adapter.ts @@ -0,0 +1,501 @@ +import COS from 'cos-nodejs-sdk-v5'; +import type { ICosStorageOptions, IStorage } from '../interface'; +import type { + UploadObjectParams, + UploadObjectResult, + DownloadObjectParams, + DownloadObjectResult, + DeleteObjectParams, + DeleteObjectsParams, + DeleteObjectsResult, + PresignedPutUrlParams, + PresignedPutUrlResult, + ListObjectsParams, + ListObjectsResult, + DeleteObjectResult, + GetObjectMetadataParams, + GetObjectMetadataResult, + EnsureBucketResult, + DeleteObjectsByPrefixParams, + StorageObjectKey, + ExistsObjectParams, + ExistsObjectResult, + StorageObjectMetadata, + PresignedGetUrlParams, + PresignedGetUrlResult, + CopyObjectParams, + CopyObjectResult, + GeneratePublicGetUrlParams, + GeneratePublicGetUrlResult +} from '../types'; +import { PassThrough } from 'node:stream'; +import { camelCase, isError, isNotNil, kebabCase } from 'es-toolkit'; +import { DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS } from '../constants'; + +export class CosStorageAdapter implements IStorage { + protected readonly client: COS; + + get bucketName(): string { + return this.options.bucket; + } + + constructor(protected readonly options: ICosStorageOptions) { + if (options.vendor !== 'cos') { + throw new Error('Invalid storage vendor'); + } + + this.client = new COS({ + SecretId: options.credentials.accessKeyId, + SecretKey: options.credentials.secretAccessKey, + UseAccelerate: options.useAccelerate, + Protocol: options.protocol, + Domain: options.domain + }); + } + + async checkObjectExists(params: ExistsObjectParams): Promise { + const { key } = params; + + let exists = false; + await new Promise((resolve, reject) => { + this.client.headObject( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: key + }, + function (err, _data) { + if (err && err.statusCode === 404) { + exists = false; + return resolve(); + } + + if (err) { + return reject(err); + } + + exists = true; + resolve(); + } + ); + }); + + return { + key, + exists, + bucket: this.options.bucket + }; + } + + async getObjectMetadata(params: GetObjectMetadataParams): Promise { + const { key } = params; + + const result = await new Promise((resolve, reject) => { + this.client.headObject( + { + Key: key, + Bucket: this.options.bucket, + Region: this.options.region + }, + function (err, data) { + if (err) { + return reject(err); + } + + resolve(data); + } + ); + }); + + let metadata: StorageObjectMetadata = {}; + if (result.headers) { + Object.entries(result.headers).forEach(([key, val]) => { + if (key.startsWith('x-cos-meta-')) { + metadata[camelCase(key.replace('x-cos-meta-', ''))] = String(val); + } + }); + } + + return { + metadata, + key, + etag: result.ETag, + bucket: this.options.bucket, + contentType: result.headers?.['content-type'], + contentLength: result.headers?.['content-length'] + ? Number(result.headers['content-length']) + : undefined + }; + } + + async ensureBucket(): Promise { + await new Promise((resolve, reject) => { + this.client.headBucket( + { + Bucket: this.options.bucket, + Region: this.options.region + }, + function (err, data) { + if (err) { + return reject(err); + } + + resolve(data); + } + ); + }); + + return { + exists: true, + created: false, + bucket: this.options.bucket + }; + } + + async uploadObject(params: UploadObjectParams): Promise { + const { key, body, contentType, contentLength, contentDisposition, metadata } = params; + + const headers: Record = {}; + if (contentDisposition) headers['Content-Disposition'] = contentDisposition; + + if (metadata) { + for (const [k, v] of Object.entries(metadata)) { + if (!k) continue; + headers[`x-cos-meta-${kebabCase(k)}`] = String(v); + } + } + + await new Promise((resolve, reject) => { + this.client.putObject( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: key, + Body: body, + ContentType: contentType, + ContentLength: contentLength, + Headers: Object.keys(headers).length ? headers : undefined + }, + function (err, data) { + if (err) { + return reject(err); + } + resolve(data); + } + ); + }); + + return { + key, + bucket: this.options.bucket + }; + } + + async downloadObject(params: DownloadObjectParams): Promise { + const passThrough = new PassThrough(); + + this.client.getObject( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: params.key, + Output: passThrough + }, + function (err, _data) { + if (err) { + passThrough.destroy(isError(err.error) ? err.error : new Error(err.message)); + } + } + ); + + return { + bucket: this.options.bucket, + key: params.key, + body: passThrough + }; + } + + async deleteObject(params: DeleteObjectParams): Promise { + const { key } = params; + + await new Promise((resolve, reject) => { + this.client.deleteObject( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: key + }, + function (err, data) { + if (err) { + return reject(err); + } + resolve(data); + } + ); + }); + + return { + key, + bucket: this.options.bucket + }; + } + + async deleteObjectsByMultiKeys(params: DeleteObjectsParams): Promise { + const { keys } = params; + + const result = await new Promise((resolve, reject) => { + this.client.deleteMultipleObject( + { + Bucket: this.options.bucket, + Region: this.options.region, + Objects: keys.map((key) => ({ Key: key })) + }, + function (err, data) { + if (err) { + return reject(err); + } + resolve(data); + } + ); + }); + + return { + keys: result.Error.map((e) => e.Key).filter(isNotNil), + bucket: this.options.bucket + }; + } + + async deleteObjectsByPrefix(params: DeleteObjectsByPrefixParams): Promise { + const { prefix } = params; + if (!prefix) { + throw new Error('Prefix is required'); + } + + const fails: StorageObjectKey[] = []; + let marker: string | undefined = undefined; + + await new Promise((resolve, reject) => { + const handler = () => { + this.client.getBucket( + { + Bucket: this.options.bucket, + Region: this.options.region, + Prefix: prefix, + MaxKeys: 1000, + Marker: marker + }, + (listErr, listData) => { + if (listErr) { + return reject(listErr); + } + + if (!listData.Contents || listData.Contents.length === 0) { + return resolve(); + } + + const objectsToDelete = listData.Contents.map((content) => ({ Key: content.Key })); + + this.client.deleteMultipleObject( + { + Bucket: this.options.bucket, + Region: this.options.region, + Objects: objectsToDelete + }, + function (deleteErr, deleteData) { + if (deleteErr) { + fails.push(...objectsToDelete.map((content) => content.Key)); + if (listData.IsTruncated === 'true') { + marker = listData.NextMarker; + return handler(); + } + + return resolve(); + } + + fails.push(...deleteData.Error.map((e) => e.Key).filter(isNotNil)); + + if (listData.IsTruncated === 'true') { + marker = listData.NextMarker; + return handler(); + } + + resolve(); + } + ); + } + ); + }; + + handler(); + }); + + return { + bucket: this.options.bucket, + keys: fails + }; + } + + async generatePresignedPutUrl(params: PresignedPutUrlParams): Promise { + const { key, expiredSeconds, metadata, contentType } = params; + + const expiresIn = expiredSeconds ? expiredSeconds : DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS; + + const meta: Record = {}; + if (metadata) { + for (const [k, v] of Object.entries(metadata)) { + if (!k) continue; + meta[`x-cos-meta-${kebabCase(k)}`] = String(v); + } + } + + if (contentType) { + meta['Content-Type'] = contentType; + } + + const url = await new Promise((resolve, reject) => { + this.client.getObjectUrl( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: key, + Expires: expiresIn, + Sign: true, + Method: 'PUT' + }, + function (err, data) { + if (err) { + return reject(err); + } + resolve(data.Url); + } + ); + }); + + return { + key, + putUrl: url, + bucket: this.options.bucket, + metadata: meta + }; + } + + async generatePresignedGetUrl(params: PresignedGetUrlParams): Promise { + const { key, expiredSeconds } = params; + const expiresIn = expiredSeconds ? expiredSeconds : DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS; + + const url = await new Promise((resolve, reject) => { + this.client.getObjectUrl( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: key, + Expires: expiresIn, + Sign: true, + Method: 'GET' + }, + function (err, data) { + if (err) { + return reject(err); + } + resolve(data.Url); + } + ); + }); + + return { + key, + getUrl: url, + bucket: this.options.bucket + }; + } + + generatePublicGetUrl(params: GeneratePublicGetUrlParams): GeneratePublicGetUrlResult { + const { key } = params; + + let url: string; + if (this.options.domain) { + url = `${this.options.protocol}//${this.options.domain}/${key}`; + } else { + url = `${this.options.protocol}//${this.options.bucket}.cos.${this.options.region}.myqcloud.com/${key}`; + } + + return { + key, + publicGetUrl: url, + bucket: this.options.bucket + }; + } + + async listObjects(params: ListObjectsParams): Promise { + const { prefix } = params; + + let keys: StorageObjectKey[] = []; + let marker: string | undefined = undefined; + + await new Promise((resolve, reject) => { + const handler = () => { + this.client.getBucket( + { + Bucket: this.options.bucket, + Region: this.options.region, + Prefix: prefix, + Marker: marker, + MaxKeys: 1000 + }, + function (err, data) { + if (err) { + return reject(err); + } + + keys = keys.concat(data.Contents?.map((content) => content.Key).filter(isNotNil) ?? []); + + if (data.IsTruncated === 'true') { + marker = data.NextMarker; + return handler(); + } + + resolve(); + } + ); + }; + + handler(); + }); + + return { + keys, + bucket: this.options.bucket + }; + } + + async copyObjectInSelfBucket(params: CopyObjectParams): Promise { + const { sourceKey, targetKey } = params; + + await new Promise((resolve, reject) => { + const copySource = `${this.options.bucket}.cos.${this.options.region}.myqcloud.com/${sourceKey}`; + + this.client.sliceCopyFile( + { + Bucket: this.options.bucket, + Region: this.options.region, + Key: targetKey, + CopySource: copySource + }, + function (err, data) { + if (err) { + return reject(err); + } + resolve(data); + } + ); + }); + + return { + bucket: this.options.bucket, + sourceKey, + targetKey + }; + } + + async destroy(): Promise {} +} diff --git a/sdk/storage/src/adapters/minio.adapter.ts b/sdk/storage/src/adapters/minio.adapter.ts new file mode 100644 index 000000000..6dcf4d4b0 --- /dev/null +++ b/sdk/storage/src/adapters/minio.adapter.ts @@ -0,0 +1,69 @@ +import { AwsS3StorageAdapter } from './aws-s3.adapter'; +import type { IAwsS3CompatibleStorageOptions, IStorage } from '../interface'; +import type { EnsureBucketResult } from '../types'; +import { + CreateBucketCommand, + DeleteBucketLifecycleCommand, + NotFound, + PutBucketPolicyCommand +} from '@aws-sdk/client-s3'; + +/** + * 注意: + * - 无论传入的 forcePathStyle 是什么,都强制使用 path style URLs + * - 只有 MinIO 这类 self-hosted 的存储服务会在存储桶不存在时 自动创建两个类型的存储桶 + */ +export class MinioStorageAdapter extends AwsS3StorageAdapter implements IStorage { + constructor(protected readonly options: IAwsS3CompatibleStorageOptions) { + if (options.vendor !== 'minio') { + throw new Error('Invalid storage vendor'); + } + + options.forcePathStyle = true; + + super(options); + } + + async ensureBucket(): Promise { + try { + return await super.ensureBucket(); + } catch (error) { + if (!(error instanceof NotFound)) { + throw error; + } + + await this.client.send(new CreateBucketCommand({ Bucket: this.options.bucket })); + + return { + exists: false, + created: true, + bucket: this.options.bucket + }; + } + } + + async ensurePublicBucketPolicy(): Promise { + const policy = { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: '*', + Action: ['s3:GetObject'], + Resource: [`arn:aws:s3:::${this.options.bucket}/*`] + } + ] + }; + + await this.client.send( + new PutBucketPolicyCommand({ + Bucket: this.options.bucket, + Policy: JSON.stringify(policy) + }) + ); + } + + async removeBucketLifecycle(): Promise { + await this.client.send(new DeleteBucketLifecycleCommand({ Bucket: this.options.bucket })); + } +} diff --git a/sdk/storage/src/adapters/oss.adapter.ts b/sdk/storage/src/adapters/oss.adapter.ts new file mode 100644 index 000000000..1193b7301 --- /dev/null +++ b/sdk/storage/src/adapters/oss.adapter.ts @@ -0,0 +1,357 @@ +import OSS from 'ali-oss'; +import type { IOssStorageOptions, IStorage } from '../interface'; +import type { + UploadObjectParams, + UploadObjectResult, + DownloadObjectParams, + DownloadObjectResult, + DeleteObjectParams, + DeleteObjectsParams, + DeleteObjectsResult, + PresignedPutUrlParams, + PresignedPutUrlResult, + ListObjectsParams, + ListObjectsResult, + DeleteObjectResult, + GetObjectMetadataParams, + GetObjectMetadataResult, + EnsureBucketResult, + DeleteObjectsByPrefixParams, + StorageObjectKey, + ExistsObjectParams, + ExistsObjectResult, + StorageObjectMetadata, + PresignedGetUrlParams, + PresignedGetUrlResult, + CopyObjectParams, + CopyObjectResult, + GeneratePublicGetUrlParams, + GeneratePublicGetUrlResult +} from '../types'; +import type { Readable } from 'node:stream'; +import { camelCase, difference, kebabCase } from 'es-toolkit'; +import { DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS } from '../constants'; + +export class OosStorageAdapter implements IStorage { + protected readonly client: OSS; + + constructor(protected readonly options: IOssStorageOptions) { + if (options.vendor !== 'oss') { + throw new Error('Invalid storage vendor'); + } + + this.client = new OSS({ + accessKeyId: options.credentials.accessKeyId, + accessKeySecret: options.credentials.secretAccessKey, + region: options.region, + endpoint: options.endpoint, + bucket: options.bucket, + cname: options.cname, + internal: options.internal, + secure: options.secure, + + // @ts-expect-error ali-oss SDK 类型未定义但存在此属性 + enableProxy: options.proxy ? true : false + }); + } + + get bucketName(): string { + return this.options.bucket; + } + + async checkObjectExists(params: ExistsObjectParams): Promise { + const { key } = params; + + let exists = false; + try { + await this.client.head(key); + exists = true; + } catch (error: any) { + if (error?.code === 'NoSuchKey') { + exists = false; + } else { + throw error; + } + } + + return { + key, + exists, + bucket: this.options.bucket + }; + } + + async getObjectMetadata(params: GetObjectMetadataParams): Promise { + const { key } = params; + + const result = await this.client.head(key); + + let metadata: StorageObjectMetadata = {}; + if (result.meta) { + for (const [k, v] of Object.entries(result.meta)) { + if (!k) continue; + metadata[camelCase(k)] = String(v); + } + } + + const headers = result.res.headers as Record; + + return { + key, + metadata, + etag: result.meta?.etag as string, + bucket: this.options.bucket, + contentType: headers['content-type'], + contentLength: headers['content-length'] ? Number(headers['content-length']) : undefined + }; + } + + async ensureBucket(): Promise { + await this.client.getBucketInfo(this.options.bucket); + + return { + exists: true, + created: false, + bucket: this.options.bucket + }; + } + + async uploadObject(params: UploadObjectParams): Promise { + const { key, body, contentType, contentLength, contentDisposition, metadata } = params; + + const headers: Record = { + 'x-oss-storage-class': 'Standard', + 'x-oss-forbid-overwrite': 'false' + }; + if (contentType) headers['Content-Type'] = contentType; + if (contentLength !== undefined) headers['Content-Length'] = String(contentLength); + if (contentDisposition) headers['Content-Disposition'] = contentDisposition; + + let meta = {} as StorageObjectMetadata & OSS.UserMeta; + if (metadata) { + for (const [k, v] of Object.entries(metadata)) { + if (!k) continue; + meta[kebabCase(k)] = String(v); + } + } + + await this.client.put(key, body, { + headers, + mime: contentType, + meta + }); + + return { + key, + bucket: this.options.bucket + }; + } + + async downloadObject(params: DownloadObjectParams): Promise { + const { key } = params; + + const result = await this.client.getStream(key); + + return { + key, + bucket: this.options.bucket, + body: result.stream as Readable + }; + } + + async deleteObject(params: DeleteObjectParams): Promise { + const { key } = params; + + await this.client.delete(key); + + return { + bucket: this.options.bucket, + key + }; + } + + async deleteObjectsByMultiKeys(params: DeleteObjectsParams): Promise { + const { keys } = params; + + const result = await this.client.deleteMulti(keys, { quiet: true }); + + return { + bucket: this.options.bucket, + keys: difference(keys, result.deleted ?? []) + }; + } + + async deleteObjectsByPrefix(params: DeleteObjectsByPrefixParams): Promise { + const { prefix } = params; + if (!prefix) { + throw new Error('Prefix is required'); + } + + const fails: StorageObjectKey[] = []; + let marker: string | undefined = undefined; + let isTruncated = false; + + do { + const listResponse = await this.client.list( + { + prefix, + 'max-keys': 1000, + marker + }, + { + timeout: 60000 + } + ); + + if (!listResponse.objects || listResponse.objects.length === 0) { + return { + bucket: this.options.bucket, + keys: [] + }; + } + + const objectsToDelete = listResponse.objects.map((object) => object.name); + const deleteResponse = await this.deleteObjectsByMultiKeys({ keys: objectsToDelete }); + + fails.push(...deleteResponse.keys); + + isTruncated = listResponse.isTruncated ?? false; + marker = listResponse.nextMarker; + } while (isTruncated); + + return { + bucket: this.options.bucket, + keys: fails + }; + } + + async generatePresignedPutUrl(params: PresignedPutUrlParams): Promise { + const { key, expiredSeconds, metadata, contentType } = params; + + const expiresIn = expiredSeconds ? expiredSeconds : DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS; + + const headersToSign: Record = {}; + if (metadata) { + for (const [k, v] of Object.entries(metadata)) { + if (!k) continue; + headersToSign[`x-oss-meta-${kebabCase(k)}`] = String(v); + } + } + + if (contentType) { + headersToSign['Content-Type'] = contentType; + } + + // @ts-expect-error ali-oss SDK 类型未定义但存在此方法 + // @see https://github.com/ali-sdk/ali-oss?tab=readme-ov-file#signatureurlv4method-expires-request-objectname-additionalheaders + const url = await this.client.signatureUrlV4( + 'PUT', + expiresIn, + { + headers: { + ...headersToSign + } + }, + key + ); + + return { + key, + putUrl: url, + bucket: this.options.bucket, + metadata: headersToSign + }; + } + + async generatePresignedGetUrl(params: PresignedGetUrlParams): Promise { + const { key, expiredSeconds } = params; + const expiresIn = expiredSeconds ? expiredSeconds : DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS; + + const url = this.client.signatureUrl(key, { + method: 'GET', + expires: expiresIn + }); + + return { + key, + getUrl: url, + bucket: this.options.bucket + }; + } + + generatePublicGetUrl(params: GeneratePublicGetUrlParams): GeneratePublicGetUrlResult { + const { key } = params; + + let protocol = 'https:'; + if (!this.options.secure) { + protocol = 'http:'; + } + + let url: string; + if (this.options.cname) { + url = `${protocol}//${this.options.endpoint}/${key}`; + } else { + url = `${protocol}//${this.options.bucket}.${this.options.region}.aliyuncs.com/${key}`; + } + + return { + key, + publicGetUrl: url, + bucket: this.options.bucket + }; + } + + async listObjects(params: ListObjectsParams): Promise { + const { prefix } = params; + + let keys: StorageObjectKey[] = []; + let marker: string | undefined = undefined; + let isTruncated = false; + + do { + const listResponse = await this.client.list( + { + prefix, + 'max-keys': 1000, + marker + }, + { + timeout: 60000 + } + ); + + if (!listResponse.objects || listResponse.objects.length === 0) { + return { + bucket: this.options.bucket, + keys: [] + }; + } + + keys = keys.concat(listResponse.objects.map((object) => object.name)); + isTruncated = listResponse.isTruncated ?? false; + marker = listResponse.nextMarker; + } while (isTruncated); + + return { + keys, + bucket: this.options.bucket + }; + } + + async copyObjectInSelfBucket(params: CopyObjectParams): Promise { + const { sourceKey, targetKey } = params; + + await this.client.copy(sourceKey, targetKey); + + return { + bucket: this.options.bucket, + sourceKey, + targetKey + }; + } + + async destroy(): Promise {} +} + +// Backward compatible export name fix: OSS adapter (typo alias). +export { OosStorageAdapter as OssStorageAdapter }; diff --git a/sdk/storage/src/constants.ts b/sdk/storage/src/constants.ts new file mode 100644 index 000000000..134983c35 --- /dev/null +++ b/sdk/storage/src/constants.ts @@ -0,0 +1,4 @@ +/** + * 默认的预签名 URL 过期时间(秒) + */ +export const DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS = 1800; diff --git a/sdk/storage/src/errors.ts b/sdk/storage/src/errors.ts new file mode 100644 index 000000000..ab103c52a --- /dev/null +++ b/sdk/storage/src/errors.ts @@ -0,0 +1,20 @@ +export class NoSuchBucketError extends Error { + constructor(message: string) { + super(message); + this.name = 'NoSuchBucketError'; + } +} + +export class NoBucketReadPermissionError extends Error { + constructor(message: string) { + super(message); + this.name = 'NoBucketReadPermissionError'; + } +} + +export class EmptyObjectError extends Error { + constructor(message: string) { + super(message); + this.name = 'EmptyObjectError'; + } +} diff --git a/sdk/storage/src/factory.ts b/sdk/storage/src/factory.ts new file mode 100644 index 000000000..444d2d8e9 --- /dev/null +++ b/sdk/storage/src/factory.ts @@ -0,0 +1,31 @@ +import { AwsS3StorageAdapter } from './adapters/aws-s3.adapter'; +import { CosStorageAdapter } from './adapters/cos.adapter'; +import { MinioStorageAdapter } from './adapters/minio.adapter'; +import { OssStorageAdapter } from './adapters/oss.adapter'; +import type { IStorageOptions } from './interface'; + +export function createStorage( + options: IStorageOptions +): AwsS3StorageAdapter | OssStorageAdapter | CosStorageAdapter | MinioStorageAdapter { + switch (options.vendor) { + case 'aws-s3': { + return new AwsS3StorageAdapter(options); + } + + case 'oss': { + return new OssStorageAdapter(options); + } + + case 'cos': { + return new CosStorageAdapter(options); + } + + case 'minio': { + return new MinioStorageAdapter(options); + } + + default: { + throw new Error(`Unsupported storage vendor: ${String((options as any)?.vendor)}`); + } + } +} diff --git a/sdk/storage/src/index.ts b/sdk/storage/src/index.ts new file mode 100644 index 000000000..6a345e3c3 --- /dev/null +++ b/sdk/storage/src/index.ts @@ -0,0 +1,42 @@ +export { createStorage } from './factory'; +export { createVitestStorageMock } from './testing/vitestMock'; +export type { VitestStorageMock, CreateVitestStorageMockParams } from './testing/vitestMock'; +export type { + IStorage, + IStorageOptions, + IAwsS3CompatibleStorageOptions, + IOssStorageOptions, + ICosStorageOptions, + ICommonStorageOptions +} from './interface'; +export type { + StorageBucketName, + StorageObjectKey, + StorageObjectMetadata, + StorageUploadBody, + EnsureBucketResult, + ExistsObjectParams, + ExistsObjectResult, + UploadObjectParams, + UploadObjectResult, + DownloadObjectParams, + DownloadObjectResult, + DeleteObjectParams, + DeleteObjectResult, + DeleteObjectsParams, + DeleteObjectsResult, + DeleteObjectsByPrefixParams, + PresignedPutUrlParams, + PresignedPutUrlResult, + PresignedGetUrlParams, + PresignedGetUrlResult, + ListObjectsParams, + ListObjectsResult, + GetObjectMetadataParams, + GetObjectMetadataResult +} from './types'; +export { NoSuchBucketError, NoBucketReadPermissionError, EmptyObjectError } from './errors'; +export { AwsS3StorageAdapter } from './adapters/aws-s3.adapter'; +export { CosStorageAdapter } from './adapters/cos.adapter'; +export { MinioStorageAdapter } from './adapters/minio.adapter'; +export { OosStorageAdapter } from './adapters/oss.adapter'; diff --git a/sdk/storage/src/interface.ts b/sdk/storage/src/interface.ts new file mode 100644 index 000000000..f51a75070 --- /dev/null +++ b/sdk/storage/src/interface.ts @@ -0,0 +1,381 @@ +import type { + DeleteObjectParams, + DeleteObjectResult, + DeleteObjectsParams, + DeleteObjectsResult, + DeleteObjectsByPrefixParams, + DownloadObjectParams, + DownloadObjectResult, + EnsureBucketResult, + GetObjectMetadataParams, + GetObjectMetadataResult, + ListObjectsParams, + ListObjectsResult, + PresignedPutUrlParams, + PresignedPutUrlResult, + UploadObjectParams, + UploadObjectResult, + ExistsObjectParams, + ExistsObjectResult, + PresignedGetUrlParams, + PresignedGetUrlResult, + CopyObjectParams, + CopyObjectResult, + GeneratePublicGetUrlParams, + GeneratePublicGetUrlResult +} from './types'; + +/** + * 通用存储配置(与具体云厂商无关)。 + * + * 说明: + * - 本包的适配器以「对象存储」为抽象(S3/OSS/COS/MinIO 等)。 + * - `bucket` / `region` / `credentials` 是大多数厂商的最小必填集合。 + * - `endpoint` 用于 S3 兼容协议的自定义域名/私有部署地址等场景。 + * + * 注意: + * - 不同厂商对 `region` 的含义略有差异(有的会叫作 `region`,有的叫 `location`),这里统一命名为 `region`, + * 由各 adapter 在内部做映射或直传。 + */ +export interface ICommonStorageOptions { + /** + * 存储桶名称(Bucket)。 + * + * - 一般全局唯一(取决于厂商),建议使用小写字母/数字/短横线组合。 + * - 对于已存在的 bucket:此处填写目标 bucket 名称即可。 + */ + bucket: string; + + /** + * 区域(Region / Location)。 + * + * - AWS: 例如 `ap-northeast-1` + * - 腾讯云 COS: 例如 `ap-guangzhou` + * - 阿里云 OSS: 例如 `oss-cn-hangzhou`(具体取值以厂商为准) + */ + region: string; + + /** + * 访问凭证(AK/SK)。 + * + * 注意: + * - 不同厂商字段命名不同,这里统一为 `accessKeyId` / `secretAccessKey`。 + * - 强烈建议从环境变量/密钥管理服务中读取,不要硬编码在仓库里。 + */ + credentials: { + /** AccessKeyId / SecretId / AK */ + accessKeyId: string; + /** SecretAccessKey / SecretKey / SK */ + secretAccessKey: string; + }; +} + +/** + * AWS S3 兼容协议的存储配置。 + * + * 适用范围: + * - AWS S3 + * - MinIO(以及其他兼容 S3 的对象存储) + * + * 设计: + * - 通过 `vendor` 做判别联合(discriminated union),便于在运行时和类型层面区分不同厂商配置。 + */ +export interface IAwsS3CompatibleStorageOptions extends ICommonStorageOptions { + /** + * 存储厂商标识(S3 兼容系)。 + * + * - `aws-s3`: AWS S3 + * - `minio`: MinIO(S3 协议兼容) + */ + vendor: 'aws-s3' | 'minio'; + + /** + * 自定义服务端点(Endpoint),可选。 + * + * 常见用途: + * - MinIO / 私有 S3 兼容服务:`http(s)://host:port` + * - 某些厂商的加速域名、内网域名等 + * + * 注意:是否需要带协议、端口、路径取决于具体 adapter 的实现。 + */ + endpoint: string; + + /** + * 是否强制使用 Path-Style 访问(`/{bucket}/{key}`),可选。 + * + * 背景: + * - S3 支持两种寻址方式:virtual-hosted-style(`{bucket}.endpoint/{key}`)和 path-style(`endpoint/{bucket}/{key}`)。 + * - 一些自建/本地化服务(或特定网络环境)对 virtual-hosted-style 支持不完整,此时可能需要开启。 + */ + forcePathStyle?: boolean; + + /** + * 最大重试次数,可选。 + * + * 说明: + * - 仅影响 adapter 内部使用的 SDK 请求重试策略(如果 adapter 支持)。 + * - 若不设置,通常由 SDK 使用默认值。 + */ + maxRetries?: number; +} + +/** + * 阿里云 OSS 存储配置。 + */ +export interface IOssStorageOptions extends ICommonStorageOptions { + /** 存储厂商标识(OSS)。 */ + vendor: 'oss'; + + /** + * 是否使用自定义域名(CNAME),可选。 + * + * - 开启后通常会影响 endpoint/host 的拼接方式。 + * - 具体行为取决于 OSS SDK 与 adapter 实现。 + */ + cname?: boolean; + + /** + * 自定义服务端点(Endpoint),可选。 + * + * 常见用途: + * - MinIO / 私有 S3 兼容服务:`http(s)://host:port` + * - 某些厂商的加速域名、内网域名等 + * + * 注意:是否需要带协议、端口、路径取决于具体 adapter 的实现。 + */ + endpoint?: string; + + /** + * 是否使用 HTTPS 协议,可选。 + * + * 说明: + * - 默认使用 HTTPS 协议。 + * - 如果设置为 false,则使用 HTTP 协议。 + */ + secure?: boolean; + + /** + * 是否启用代理,可选。 + */ + enableProxy?: boolean; + + /** + * 是否走内网(internal endpoint),可选。 + * + * - 在同地域云内网环境可显著降低延迟与流量费用。 + * - 若设置为 true,adapter 可能会自动选择 internal endpoint。 + */ + internal?: boolean; +} + +/** + * 腾讯云 COS 存储配置。 + */ +export interface ICosStorageOptions extends ICommonStorageOptions { + /** 存储厂商标识(COS)。 */ + vendor: 'cos'; + + /** + * 请求协议,可选。 + * + * - `https:` 为默认推荐 + * - 某些内网/测试环境可能使用 `http:` + */ + protocol?: 'http:' | 'https:'; + + /** + * 是否启用全球加速,可选。 + * + * 说明: + * - 仅当 COS 账号/桶已开通加速能力时才有意义。 + * - 开启后 endpoint/域名可能会发生变化(由 adapter 处理)。 + */ + useAccelerate?: boolean; + + /** + * 自定义域名,可选。 + * + * 说明: + * - 用于替代默认的域名,例如 `https://{bucket}.cos.${region}.myqcloud.com`。 + * - 通常用于某些厂商的私有化部署环境。 + */ + domain?: string; + + /** + * 代理,可选。 + * + * 说明: + * - 用于 HTTP 代理访问,例如 `http://127.0.0.1:8080`。 + */ + proxy?: string; +} + +/** + * 百度 BOS 存储配置(预留)。 + * + * 说明: + * - 当前 `IStorageOptions` 并未包含该类型,可能尚未完整实现/接入。 + * - 先保留接口便于后续扩展。 + */ +export interface IBosStorageOptions extends ICommonStorageOptions { + /** 存储厂商标识(BOS)。 */ + vendor: 'bos'; +} + +/** + * 存储配置联合类型(判别联合)。 + * + * 用法: + * - 根据 `vendor` 字段区分不同厂商配置,adapter 工厂函数通常也会基于该字段选择实现。 + * + * 注意: + * - 当前仅包含 `aws-s3`/`minio`/`oss`/`cos`;若后续接入新厂商,需要在此处补齐联合成员。 + */ +export type IStorageOptions = + | IAwsS3CompatibleStorageOptions + | IOssStorageOptions + | ICosStorageOptions; + +/** + * 统一的对象存储能力接口(vendor-agnostic)。 + * + * 约定: + * - `key` 通常表示对象在 bucket 内的路径(例如 `a/b/c.txt`),由调用方自行约定命名规则。 + * - 所有方法都应当是幂等或尽可能接近幂等(例如删除不存在的对象不应抛出致命错误),以便业务层重试。 + */ +export interface IStorage { + bucketName: string; + + /** + * 确保存储桶存在;若不存在则**可能**尝试创建(取决于 vendor 与账号权限)。 + * + * 返回值说明: + * - `exists`: 调用前 bucket 是否存在 + * - `created`: 本次调用是否创建了 bucket + * + * 建议: + * - 对于 OSS/COS 等厂商,很多团队更倾向于在控制台/基础设施层面创建 bucket(权限/策略/生命周期等更可控), + * 应用侧仅做存在性校验即可(bucket 不存在时会抛出底层 SDK 错误)。 + */ + ensureBucket(): Promise; + + /** + * 判断对象是否存在。 + * + * 返回值说明: + * - `exists`: 对象是否存在 + */ + checkObjectExists(params: ExistsObjectParams): Promise; + + /** + * 上传对象到 bucket。 + * + * 典型用法: + * - 上传文件内容(Buffer/Readable) + * - 上传文本(string) + * + * 注意: + * - `contentType`/`contentDisposition`/`metadata` 会映射为不同厂商的 HTTP 头或元数据字段,adapter 负责兼容。 + */ + uploadObject(params: UploadObjectParams): Promise; + + /** + * 下载对象(以 Node.js `Readable` 流形式返回)。 + * + * 说明: + * - 适合大文件流式读取,避免一次性加载到内存。 + * - 调用方负责消费/关闭流(正常读取结束会自动关闭)。 + */ + downloadObject(params: DownloadObjectParams): Promise; + + /** + * 删除单个对象(按 key)。 + * + * 建议: + * - 作为幂等操作处理:若对象不存在,最好仍返回成功(由 adapter 决定具体行为)。 + */ + deleteObject(params: DeleteObjectParams): Promise; + + /** + * 根据多个 key 批量删除对象。 + * + * 注意: + * - 各厂商对单次批量删除的最大数量限制不同,adapter 可能需要分批处理。 + * - 返回的 `deleted` 通常只包含实际删除/确认删除的 key。 + */ + deleteObjectsByMultiKeys(params: DeleteObjectsParams): Promise; + + /** + * 根据前缀批量删除对象(危险操作)。 + * + * 安全建议: + * - `prefix` 必须是非空字符串,且建议包含业务域前缀(例如 `team/{teamId}/`),避免误删整个 bucket。 + * - 对于大规模删除,adapter 可能会先 list 再 delete,耗时与费用需评估。 + * + * 注意: + * - 理论上可以使用 `this.listObjects` 然后 `this.deleteObjectsByMultiKeys` + * 但是这样可能会产生不必要的内存占用问题,所以这里单独实现了一个方法 + */ + deleteObjectsByPrefix(params: DeleteObjectsByPrefixParams): Promise; + + /** + * 生成对象的上传预签名 URL(Presigned URL)。 + * + * 用途: + * - 前端直传(PUT) + * + * 注意: + * - 过期时间、签名算法、header 约束等通常由 adapter 或其底层 SDK 决定。 + * - 返回值中的 `metadata` 字段语义更接近“直传时需要附带的 headers”(不同厂商前缀不同,如 `x-oss-meta-*` / `x-cos-meta-*`)。 + * 字段名因历史原因沿用 `metadata`。 + */ + generatePresignedPutUrl(params: PresignedPutUrlParams): Promise; + + /** + * 生成对象的下载预签名 URL(Presigned URL)。 + * + * 用途: + * - 临时授权下载(GET) + */ + generatePresignedGetUrl(params: PresignedGetUrlParams): Promise; + + /** + * 生成公共对象的访问 URL。 + */ + generatePublicGetUrl(params: GeneratePublicGetUrlParams): GeneratePublicGetUrlResult; + + /** + * 列出对象 key(可按前缀过滤)。 + * + * 注意: + * - 当前返回结构只包含 key 列表,未包含分页/marker/continuationToken 等信息; + * 若业务需要大规模遍历,应扩展 `ListObjectsParams/Result` 支持分页与更多元数据。 + * - `prefix` 为可选;不传则表示列出整个 bucket 内对象(注意对象很多时可能会很慢/很贵)。 + * - 不要用 listObjects + deleteObjectsByMultiKeys 来实现“按前缀删除”:批量删除单次最多 1000 个对象,且 list 结果量可能很大; + * 请使用 `deleteObjectsByPrefix`(adapter 会在内部分页与分批删除)。 + */ + listObjects(params: ListObjectsParams): Promise; + + /** + * 复制对象。 + */ + copyObjectInSelfBucket(params: CopyObjectParams): Promise; + + /** + * 获取对象元数据(Metadata)。 + * + * 说明: + * - 元数据通常包含用户自定义 metadata 以及部分系统字段(取决于 adapter 的实现)。 + * - 不同厂商会对 metadata 的 key 前缀、大小写、可用字符做限制,调用方应尽量使用简单的 ASCII key。 + */ + getObjectMetadata(params: GetObjectMetadataParams): Promise; + + /** + * 资源清理/连接释放。 + * + * 说明: + * - 某些 SDK 会维护连接池或后台任务,业务在进程退出前可调用以更快释放资源。 + * - 若 adapter 无需清理,应实现为空操作(resolved Promise)。 + */ + destroy(): Promise; +} diff --git a/sdk/storage/src/testing/vitestMock.ts b/sdk/storage/src/testing/vitestMock.ts new file mode 100644 index 000000000..21bbe2317 --- /dev/null +++ b/sdk/storage/src/testing/vitestMock.ts @@ -0,0 +1,246 @@ +import type { Readable } from 'node:stream'; +import { Readable as NodeReadable } from 'node:stream'; +import type { MockedFunction } from 'vitest'; +import type { IStorage } from '../interface'; +import type { + CopyObjectParams, + CopyObjectResult, + DeleteObjectParams, + DeleteObjectResult, + DeleteObjectsByPrefixParams, + DeleteObjectsParams, + DeleteObjectsResult, + DownloadObjectParams, + DownloadObjectResult, + EnsureBucketResult, + ExistsObjectParams, + ExistsObjectResult, + GeneratePublicGetUrlParams, + GeneratePublicGetUrlResult, + GetObjectMetadataParams, + GetObjectMetadataResult, + ListObjectsParams, + ListObjectsResult, + PresignedGetUrlParams, + PresignedGetUrlResult, + PresignedPutUrlParams, + PresignedPutUrlResult, + StorageObjectKey, + StorageObjectMetadata, + StorageUploadBody, + UploadObjectParams, + UploadObjectResult +} from '../types'; + +type VitestLike = { + fn: any>(impl?: T) => MockedFunction; +}; + +type StoredObject = { + body: Buffer; + metadata: StorageObjectMetadata; + contentType?: string; + contentLength?: number; + contentDisposition?: string; + etag?: string; +}; + +export type VitestStorageMock = IStorage & { + /** 便于在测试中直接读写内存对象(key -> object)。 */ + __objects: Map; + /** 清空内存对象。 */ + __reset: () => void; + /** 直接写入一个对象(绕过 uploadObject)。 */ + __putObject: (key: StorageObjectKey, obj: Partial & { body: Buffer }) => void; +}; + +export type CreateVitestStorageMockParams = { + vi: VitestLike; + bucketName?: string; + /** + * 用于构造 presigned/public URL 的 base(仅 mock 用)。 + * 例如:`https://mock-storage.local` + */ + baseUrl?: string; +}; + +async function bodyToBuffer(body: StorageUploadBody): Promise { + if (Buffer.isBuffer(body)) return body; + if (typeof body === 'string') return Buffer.from(body); + return await readableToBuffer(body); +} + +async function readableToBuffer(readable: Readable): Promise { + const chunks: Buffer[] = []; + for await (const chunk of readable) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + return Buffer.concat(chunks); +} + +function bufferToReadable(buf: Buffer): Readable { + return NodeReadable.from(buf); +} + +function getEtag(buf: Buffer) { + // mock: 非加密 hash,只是为了在测试里有稳定值可断言 + return `etag_${buf.length}_${buf.subarray(0, 8).toString('hex')}`; +} + +export function createVitestStorageMock(params: CreateVitestStorageMockParams): VitestStorageMock { + const { vi, bucketName = 'mock-bucket', baseUrl = 'https://mock-storage.local' } = params; + + const objects = new Map(); + let bucketEnsured = false; + + const ensureBucket = vi.fn(async (): Promise => { + const exists = bucketEnsured; + bucketEnsured = true; + return { exists, created: !exists, bucket: bucketName }; + }); + + const checkObjectExists = vi.fn( + async ({ key }: ExistsObjectParams): Promise => { + return { bucket: bucketName, key, exists: objects.has(key) }; + } + ); + + const uploadObject = vi.fn(async (p: UploadObjectParams): Promise => { + const buf = await bodyToBuffer(p.body); + const contentLength = p.contentLength ?? buf.length; + objects.set(p.key, { + body: buf, + metadata: p.metadata ?? {}, + contentType: p.contentType, + contentDisposition: p.contentDisposition, + contentLength, + etag: getEtag(buf) + }); + return { bucket: bucketName, key: p.key }; + }); + + const downloadObject = vi.fn(async (p: DownloadObjectParams): Promise => { + const obj = objects.get(p.key); + if (!obj) { + throw new Error(`Object not found: ${p.key}`); + } + return { bucket: bucketName, key: p.key, body: bufferToReadable(obj.body) }; + }); + + const deleteObject = vi.fn(async (p: DeleteObjectParams): Promise => { + objects.delete(p.key); + return { bucket: bucketName, key: p.key }; + }); + + const deleteObjectsByMultiKeys = vi.fn( + async (p: DeleteObjectsParams): Promise => { + for (const key of p.keys) objects.delete(key); + return { bucket: bucketName, keys: p.keys }; + } + ); + + const deleteObjectsByPrefix = vi.fn( + async (p: DeleteObjectsByPrefixParams): Promise => { + if (!p.prefix) { + throw new Error('prefix must be a non-empty string'); + } + const keys: string[] = []; + for (const key of objects.keys()) { + if (key.startsWith(p.prefix)) keys.push(key); + } + for (const key of keys) objects.delete(key); + return { bucket: bucketName, keys }; + } + ); + + const generatePresignedPutUrl = vi.fn( + async (p: PresignedPutUrlParams): Promise => { + const putUrl = `${baseUrl}/put/${encodeURIComponent(bucketName)}/${encodeURIComponent(p.key)}`; + // mock: 直接透传 metadata 作为“headers” + const metadata: Record = p.metadata ? { ...p.metadata } : {}; + return { bucket: bucketName, key: p.key, putUrl, metadata }; + } + ); + + const generatePresignedGetUrl = vi.fn( + async (p: PresignedGetUrlParams): Promise => { + const getUrl = `${baseUrl}/get/${encodeURIComponent(bucketName)}/${encodeURIComponent(p.key)}`; + return { bucket: bucketName, key: p.key, getUrl }; + } + ); + + const generatePublicGetUrl = vi.fn( + ({ key }: GeneratePublicGetUrlParams): GeneratePublicGetUrlResult => { + const publicGetUrl = `${baseUrl}/public/${encodeURIComponent(bucketName)}/${encodeURIComponent(key)}`; + return { publicGetUrl, bucket: bucketName, key }; + } + ); + + const listObjects = vi.fn(async (p: ListObjectsParams): Promise => { + const keys = Array.from(objects.keys()).filter((k) => + p.prefix ? k.startsWith(p.prefix) : true + ); + keys.sort(); + return { bucket: bucketName, keys }; + }); + + const copyObjectInSelfBucket = vi.fn(async (p: CopyObjectParams): Promise => { + const src = objects.get(p.sourceKey); + if (!src) { + throw new Error(`Source object not found: ${p.sourceKey}`); + } + objects.set(p.targetKey, { ...src, body: Buffer.from(src.body) }); + return { bucket: bucketName, sourceKey: p.sourceKey, targetKey: p.targetKey }; + }); + + const getObjectMetadata = vi.fn( + async (p: GetObjectMetadataParams): Promise => { + const obj = objects.get(p.key); + if (!obj) { + throw new Error(`Object not found: ${p.key}`); + } + return { + bucket: bucketName, + key: p.key, + metadata: obj.metadata ?? {}, + contentType: obj.contentType, + contentLength: obj.contentLength, + etag: obj.etag + }; + } + ); + + const destroy = vi.fn(async (): Promise => {}); + + const mock: VitestStorageMock = { + bucketName, + ensureBucket, + checkObjectExists, + uploadObject, + downloadObject, + deleteObject, + deleteObjectsByMultiKeys, + deleteObjectsByPrefix, + generatePresignedPutUrl, + generatePresignedGetUrl, + generatePublicGetUrl, + listObjects, + copyObjectInSelfBucket, + getObjectMetadata, + destroy, + __objects: objects, + __reset: () => objects.clear(), + __putObject: (key, obj) => { + objects.set(key, { + body: obj.body, + metadata: obj.metadata ?? {}, + contentType: obj.contentType, + contentLength: obj.contentLength ?? obj.body.length, + contentDisposition: obj.contentDisposition, + etag: obj.etag ?? getEtag(obj.body) + }); + } + }; + + return mock; +} diff --git a/sdk/storage/src/types.ts b/sdk/storage/src/types.ts new file mode 100644 index 000000000..543b29924 --- /dev/null +++ b/sdk/storage/src/types.ts @@ -0,0 +1,388 @@ +import type { Readable } from 'node:stream'; + +/** + * 对象存储(S3/MinIO/OSS/COS/...)统一类型定义。 + * + * 设计目标: + * - **与厂商无关**:业务侧只依赖这些类型,不直接依赖具体云 SDK 的类型。 + * - **Node 友好**:避免 DOM 专属类型(例如 `Blob` / `ReadableStream`),在服务端环境开箱即用。 + * - **可扩展**:当新增厂商或能力(分页、分片上传、ACL 等)时,优先在这里扩展类型,再由各 adapter 负责实现。 + */ + +/** + * 存储桶名称(Bucket name)。 + * + * 说明: + * - 本质上是 string,但为了语义清晰单独起别名。 + * - 具体命名规则由厂商决定(长度、字符集、是否全局唯一等)。 + */ +export type StorageBucketName = string; + +/** + * 对象 key(Object key / object path)。 + * + * 说明: + * - 在同一个 bucket 内唯一标识一个对象。 + * - 通常形如:`a/b/c.txt`(用 `/` 形成“目录”层级,但对象存储并不是真正的目录结构)。 + */ +export type StorageObjectKey = string; + +/** + * 对象元数据(Metadata)。 + * + * 说明: + * - 用于承载用户自定义键值对(以及 adapter 可能返回的部分系统字段)。 + * - 由于不同厂商对 key/value 的限制差异较大,这里使用宽泛的 `Record`,由 adapter 做必要的转换/过滤。 + * + * 建议: + * - 自定义 key 尽量使用小写 ASCII 与短横线/下划线,避免特殊字符与大小写歧义。 + */ +export type StorageObjectMetadata = Record; + +/** + * 上传内容(body)支持的类型(Node.js 环境)。 + * + * - `Buffer`: 二进制内容(推荐用于小/中等体积文件)。 + * - `string`: 文本内容(注意编码,一般为 UTF-8)。 + * - `Readable`: 流式上传(推荐用于大文件,避免内存峰值)。 + * + * 说明: + * - 这里刻意不引入浏览器侧类型(如 `Blob`),让该包在服务端 tsconfig 下更稳定。 + */ +export type StorageUploadBody = Buffer | string | Readable; + +/** + * `ensureBucket` 的返回结果。 + */ +export type EnsureBucketResult = { + /** 调用前 bucket 是否已经存在。 */ + exists: boolean; + /** 本次调用是否创建了 bucket(存在则为 false)。 */ + created: boolean; + /** bucket 名称(回显)。 */ + bucket: StorageBucketName; +}; + +/** + * 上传对象入参。 + */ +export type UploadObjectParams = { + /** 对象 key。 */ + key: StorageObjectKey; + /** 上传内容。 */ + body: StorageUploadBody; + + /** + * MIME 类型(Content-Type),可选。 + * + * 示例:`image/png`、`application/pdf`、`text/plain; charset=utf-8` + */ + contentType?: string; + + /** + * 内容长度(字节),可选。 + * + * 说明: + * - 某些 SDK/厂商在特定上传方式下需要该值;不填时可能由 SDK 自动计算或走 chunked 传输。 + */ + contentLength?: number; + + /** + * 内容展示方式(Content-Disposition),可选。 + * + * 示例: + * - `inline` + * - `attachment; filename="report.pdf"` + */ + contentDisposition?: string; + + /** + * 自定义元数据(key/value),可选。 + * + * 注意: + * - 不同厂商会使用不同的 header 前缀/命名规则(例如 `x-amz-meta-` 等),adapter 会负责映射。 + * - 元数据大小、字符集限制由厂商决定;尽量保持 key 简洁并控制总体大小。 + */ + metadata?: StorageObjectMetadata; +}; + +/** + * 上传对象返回结果(最小回执)。 + * + * 说明: + * - 目前仅回传 `bucket` 与 `key`,不包含 ETag/版本号/校验和等信息; + * 若业务侧需要这些字段,可以在后续迭代中扩展。 + */ +export type UploadObjectResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 下载对象入参。 + */ +export type DownloadObjectParams = { + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 下载对象结果(流式)。 + */ +export type DownloadObjectResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; + + /** + * 对象内容(Node.js 可读流)。 + * + * 使用建议: + * - 直接 pipe 到文件或 HTTP 响应,避免一次性读入内存: + * `body.pipe(fs.createWriteStream(...))` + */ + body: Readable; +}; + +/** + * 删除单个对象入参。 + */ +export type DeleteObjectParams = { + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 删除单个对象结果(最小回执)。 + */ +export type DeleteObjectResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 批量删除对象入参(按 key 列表)。 + */ +export type DeleteObjectsParams = { + /** 要删除的对象 key 列表。 */ + keys: StorageObjectKey[]; +}; + +/** + * 批量删除对象结果。 + */ +export type DeleteObjectsResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 删除失败的对象 key 列表。 */ + keys: StorageObjectKey[]; +}; + +/** + * 生成上传预签名 URL 入参。 + */ +export type PresignedPutUrlParams = { + /** 对象 key。 */ + key: StorageObjectKey; + /** 过期时间(秒),可选,默认 1800 秒。 */ + expiredSeconds?: number; + + /** + * MIME 类型(Content-Type),可选。 + * + * 示例:`image/png`、`application/pdf`、`text/plain; charset=utf-8` + */ + contentType?: string; + + /** + * 自定义元数据(key/value),可选。 + * + * 注意: + * - 不同厂商会使用不同的 header 前缀/命名规则(例如 `x-amz-meta-` 等),adapter 会负责映射。 + * - 元数据大小、字符集限制由厂商决定;尽量保持 key 简洁并控制总体大小。 + */ + metadata?: StorageObjectMetadata; +}; + +/** + * 生成上传预签名 URL 结果。 + */ +export type PresignedPutUrlResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; + /** 可直接访问的临时 URL。 */ + putUrl: string; + /** + * 与本次 PUT 直传相关的“需要带上的 header”集合。 + * + * 说明: + * - 不同厂商对自定义元数据的 header 前缀不同(例如 S3 是 `x-amz-meta-*`,COS 是 `x-cos-meta-*`,OSS 是 `x-oss-meta-*`)。 + * - 为了让调用方在前端直传时更容易使用,这里统一通过该字段返回 adapter 期望的 header key/value。 + * + * 注意: + * - 该字段名历史原因沿用 `metadata`,但语义更接近“headers”而非纯业务元数据。 + */ + metadata: Record; +}; + +/** + * 生成下载预签名 URL 入参。 + */ +export type PresignedGetUrlParams = { + /** 对象 key。 */ + key: StorageObjectKey; + /** 过期时间(秒),可选,默认 1800 秒。 */ + expiredSeconds?: number; +}; + +/** + * 生成下载预签名 URL 结果。 + */ +export type PresignedGetUrlResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; + /** 可直接访问的临时 URL。 */ + getUrl: string; +}; + +/** + * 生成公共对象的访问 URL 入参。 + */ +export type GeneratePublicGetUrlParams = { + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 生成公共对象的访问 URL 结果。 + */ +export type GeneratePublicGetUrlResult = { + /** 可直接访问的公共 URL。 */ + publicGetUrl: string; + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 列表查询入参(按前缀过滤)。 + * + * 注意: + * - 当前类型未包含分页参数;当对象数量很多时,adapter 可能需要自行分页并在内部合并结果(或未来扩展此类型)。 + */ +export type ListObjectsParams = { + /** + * 前缀过滤(prefix),可选。 + * + * 示例:`team/123/` 会列出该前缀下的所有对象 key。 + */ + prefix?: string; +}; + +/** + * 列表查询结果(仅返回 key 列表)。 + */ +export type ListObjectsResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key 列表。 */ + keys: StorageObjectKey[]; +}; + +/** + * 复制对象入参。 + */ +export type CopyObjectParams = { + /** 源对象 key。 */ + sourceKey: StorageObjectKey; + /** 目标对象 key。 */ + targetKey: StorageObjectKey; +}; + +/** + * 复制对象结果。 + */ +export type CopyObjectResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 源对象 key。 */ + sourceKey: StorageObjectKey; + /** 目标对象 key。 */ + targetKey: StorageObjectKey; +}; + +/** + * 获取对象元数据入参。 + */ +export type GetObjectMetadataParams = { + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 获取对象元数据结果。 + */ +export type GetObjectMetadataResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; + /** 元数据。 */ + metadata: StorageObjectMetadata; + /** MIME 类型(Content-Type)。 */ + contentType?: string; + /** 内容长度(字节)。 */ + contentLength?: number; + /** ETag。 */ + etag?: string; +}; + +/** + * 按前缀批量删除入参(高危)。 + * + * 说明: + * - 通常实现方式是:先按 prefix 列出对象,再分批删除。 + * - 这可能会产生较多 API 调用与费用,并且在对象数很大时耗时较长。 + */ +export type DeleteObjectsByPrefixParams = { + /** + * 前缀(prefix),必须为**非空字符串**。 + * + * 安全原因: + * - 若允许空字符串,等价于删除整个 bucket 内所有对象,风险极高。 + * + * 建议: + * - 使用强业务隔离前缀,例如:`team/{teamId}/`、`dataset/{datasetId}/`。 + */ + prefix: string; +}; + +/** + * 判断对象是否存在入参。 + */ +export type ExistsObjectParams = { + /** 对象 key。 */ + key: StorageObjectKey; +}; + +/** + * 判断对象是否存在结果。 + */ +export type ExistsObjectResult = { + /** bucket 名称。 */ + bucket: StorageBucketName; + /** 对象 key。 */ + key: StorageObjectKey; + /** 对象是否存在。 */ + exists: boolean; +}; diff --git a/sdk/storage/tsconfig.json b/sdk/storage/tsconfig.json new file mode 100644 index 000000000..079d7741a --- /dev/null +++ b/sdk/storage/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + } +} diff --git a/sdk/storage/tsdown.config.ts b/sdk/storage/tsdown.config.ts new file mode 100644 index 000000000..6273d1901 --- /dev/null +++ b/sdk/storage/tsdown.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: 'src/index.ts', + format: 'esm', + dts: { + enabled: true, + sourcemap: false + }, + outExtensions() { + return { + dts: '.d.ts', + js: '.js' + }; + } +}); diff --git a/test/mocks/common/s3.ts b/test/mocks/common/s3.ts index ad4db93e8..2de8c4f21 100644 --- a/test/mocks/common/s3.ts +++ b/test/mocks/common/s3.ts @@ -1,27 +1,70 @@ import { vi } from 'vitest'; +import { createVitestStorageMock } from '../../../sdk/storage/src/testing/vitestMock'; + +const mockStorageByBucket = new Map>(); +const getMockStorage = (bucketName: string) => { + const existing = mockStorageByBucket.get(bucketName); + if (existing) return existing; + const storage = createVitestStorageMock({ + vi, + bucketName, + baseUrl: 'http://localhost:9000' + }); + mockStorageByBucket.set(bucketName, storage); + return storage; +}; // Create mock S3 bucket object for global use -const createMockS3Bucket = () => ({ - name: 'mock-bucket', - client: {}, - externalClient: {}, - exist: vi.fn().mockResolvedValue(true), - delete: vi.fn().mockResolvedValue(undefined), - putObject: vi.fn().mockResolvedValue(undefined), - getFileStream: vi.fn().mockResolvedValue(null), - statObject: vi.fn().mockResolvedValue({ size: 0, etag: 'mock-etag' }), - move: vi.fn().mockResolvedValue(undefined), - copy: vi.fn().mockResolvedValue(undefined), - addDeleteJob: vi.fn().mockResolvedValue(undefined), - createPostPresignedUrl: vi.fn().mockResolvedValue({ - url: 'http://localhost:9000/mock-bucket', - fields: { key: 'mock-key' }, - maxSize: 100 * 1024 * 1024 - }), - createExternalUrl: vi.fn().mockResolvedValue('http://localhost:9000/mock-bucket/mock-key'), - createGetPresignedUrl: vi.fn().mockResolvedValue('http://localhost:9000/mock-bucket/mock-key'), - createPublicUrl: vi.fn((key: string) => `http://localhost:9000/mock-bucket/${key}`) -}); +const createMockS3Bucket = (bucketName = 'mock-bucket') => { + const client = getMockStorage(bucketName); + const externalClient = getMockStorage(bucketName); + + return { + name: bucketName, + client, + externalClient, + exist: vi.fn().mockResolvedValue(true), + delete: vi.fn().mockResolvedValue(undefined), + putObject: vi.fn(async (key: string, body: any) => { + await client.uploadObject({ key, body }); + }), + getFileStream: vi.fn(async (key: string) => { + const res = await client.downloadObject({ key }); + return res.body; + }), + statObject: vi.fn(async (key: string) => { + const meta = await client.getObjectMetadata({ key }); + return { + size: meta.contentLength ?? 0, + etag: meta.etag ?? 'mock-etag' + }; + }), + move: vi.fn(async ({ from, to }: { from: string; to: string }) => { + await client.copyObjectInSelfBucket({ sourceKey: from, targetKey: to }); + await client.deleteObject({ key: from }); + }), + copy: vi.fn(async ({ from, to }: { from: string; to: string }) => { + await client.copyObjectInSelfBucket({ sourceKey: from, targetKey: to }); + }), + addDeleteJob: vi.fn().mockResolvedValue(undefined), + createPostPresignedUrl: vi.fn().mockResolvedValue({ + url: 'http://localhost:9000/mock-bucket', + fields: { key: 'mock-key' }, + maxSize: 100 * 1024 * 1024 + }), + createExternalUrl: vi.fn(async (key: string) => { + const { getUrl } = await externalClient.generatePresignedGetUrl({ key }); + return getUrl; + }), + createGetPresignedUrl: vi.fn(async (key: string) => { + const { getUrl } = await client.generatePresignedGetUrl({ key }); + return getUrl; + }), + createPublicUrl: vi.fn( + (key: string) => externalClient.generatePublicGetUrl({ key }).publicGetUrl + ) + }; +}; // Initialize global s3BucketMap early to prevent any real S3 connections const mockBucket = createMockS3Bucket(); @@ -68,27 +111,36 @@ const createMockBucketClass = (defaultName: string) => { return class MockS3Bucket { public name: string; public options: any; - public client = {}; - public externalClient = {}; + public client = getMockStorage(defaultName); + public externalClient = getMockStorage(defaultName); constructor(bucket?: string, options?: any) { this.name = bucket || defaultName; this.options = options || {}; + this.client = getMockStorage(this.name); + this.externalClient = getMockStorage(this.name); } async exist() { return true; } async delete() {} - async putObject() {} + async putObject(key: string, body: any) { + await this.client.uploadObject({ key, body }); + } async getFileStream() { return null; } async statObject() { return { size: 0, etag: 'mock-etag' }; } - async move() {} - async copy() {} + async move({ from, to }: { from: string; to: string }) { + await this.client.copyObjectInSelfBucket({ sourceKey: from, targetKey: to }); + await this.client.deleteObject({ key: from }); + } + async copy({ from, to }: { from: string; to: string }) { + await this.client.copyObjectInSelfBucket({ sourceKey: from, targetKey: to }); + } async addDeleteJob() {} async createPostPresignedUrl(params: any, options?: any) { return { @@ -98,13 +150,21 @@ const createMockBucketClass = (defaultName: string) => { }; } async createExternalUrl(params: any) { - return `http://localhost:9000/mock-bucket/${params.key}`; + const { getUrl } = await this.externalClient.generatePresignedGetUrl({ + key: params.key, + expiredSeconds: params.expires + }); + return getUrl; } async createGetPresignedUrl(params: any) { - return `http://localhost:9000/mock-bucket/${params.key}`; + const { getUrl } = await this.client.generatePresignedGetUrl({ + key: params.key, + expiredSeconds: params.expires + }); + return getUrl; } createPublicUrl(objectKey: string) { - return `http://localhost:9000/mock-bucket/${objectKey}`; + return this.externalClient.generatePublicGetUrl({ key: objectKey }).publicGetUrl; } }; }; diff --git a/tsconfig.json b/tsconfig.json index 2a98c4faf..684bf6df0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2022", + "target": "esnext", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true,