From f1a6e409cb2a567d8653f1763056147ac403b659 Mon Sep 17 00:00:00 2001
From: DarkSky <25152247+darkskygit@users.noreply.github.com>
Date: Sun, 1 Feb 2026 21:54:39 +0800
Subject: [PATCH] feat(server): lightweight s3 client (#14348)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
#### PR Dependency Tree
* **PR #14348** 👈
This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
## Summary by CodeRabbit
* **New Features**
* Added a dedicated S3-compatible client package and expanded
S3-compatible storage config (endpoint, region, forcePathStyle,
requestTimeoutMs, minPartSize, presign options, sessionToken).
* Document sync now broadcasts batched/compressed doc updates for more
efficient real-time syncing.
* **Tests**
* New unit and benchmark tests for base64 utilities and S3 multipart
listing; updated storage-related tests to match new formats.
✏️ Tip: You can customize this high-level summary in your review
settings.
---
.docker/selfhost/schema.json | 234 ++-
packages/backend/server/package.json | 3 +-
.../__tests__/e2e/storage/r2-proxy.spec.ts | 10 +-
.../src/base/config/__tests__/config.spec.ts | 4 +-
.../storage/__tests__/s3-list-parts.spec.ts | 49 +
.../src/base/storage/__tests__/s3.spec.ts | 44 +-
.../src/base/storage/providers/index.ts | 42 +-
.../server/src/base/storage/providers/r2.ts | 34 +-
.../server/src/base/storage/providers/s3.ts | 307 ++--
.../backend/server/src/core/sync/gateway.ts | 157 +-
packages/backend/server/tsconfig.json | 1 +
.../nbstore/src/__tests__/base64.bench.ts | 23 +
.../nbstore/src/__tests__/base64.spec.ts | 27 +
.../src/__tests__/cloud-doc-updates.spec.ts | 41 +
.../common/nbstore/src/impls/cloud/doc.ts | 34 +-
.../common/nbstore/src/impls/cloud/socket.ts | 60 +-
packages/common/s3-compat/package.json | 18 +
packages/common/s3-compat/src/index.ts | 529 +++++++
packages/common/s3-compat/tsconfig.json | 11 +
.../admin/src/modules/settings/config.ts | 6 +-
.../utils/__tests__/base64.spec.ts | 20 +
.../modules/workspace-engine/utils/base64.ts | 51 +-
packages/frontend/mobile-native/Cargo.toml | 1 +
packages/frontend/mobile-native/src/lib.rs | 48 +-
packages/frontend/native/Cargo.toml | 8 +-
packages/frontend/native/index.d.ts | 4 +-
packages/frontend/native/nbstore/Cargo.toml | 2 +
packages/frontend/native/nbstore/src/lib.rs | 16 +-
packages/frontend/native/src/hashcash.rs | 24 +
packages/frontend/native/src/lib.rs | 4 +
.../blocksuite/e2e/edgeless/clipboard.spec.ts | 15 +
tools/cli/package.json | 2 +-
tools/cli/src/webpack/s3-plugin.ts | 36 +-
tools/cli/tsconfig.json | 5 +-
tools/utils/src/workspace.gen.ts | 9 +-
tsconfig.json | 1 +
yarn.lock | 1371 +----------------
37 files changed, 1539 insertions(+), 1712 deletions(-)
create mode 100644 packages/backend/server/src/base/storage/__tests__/s3-list-parts.spec.ts
create mode 100644 packages/common/nbstore/src/__tests__/base64.bench.ts
create mode 100644 packages/common/nbstore/src/__tests__/base64.spec.ts
create mode 100644 packages/common/nbstore/src/__tests__/cloud-doc-updates.spec.ts
create mode 100644 packages/common/s3-compat/package.json
create mode 100644 packages/common/s3-compat/src/index.ts
create mode 100644 packages/common/s3-compat/tsconfig.json
create mode 100644 packages/frontend/core/src/modules/workspace-engine/utils/__tests__/base64.spec.ts
diff --git a/.docker/selfhost/schema.json b/.docker/selfhost/schema.json
index 795601d5a6..ba56ad6cc0 100644
--- a/.docker/selfhost/schema.json
+++ b/.docker/selfhost/schema.json
@@ -337,8 +337,42 @@
},
"config": {
"type": "object",
- "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
+ "description": "The config for the S3 compatible storage provider.",
"properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://.r2.cloudflarestorage.com\"."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
+ },
+ "forcePathStyle": {
+ "type": "boolean",
+ "description": "Whether to use path-style bucket addressing."
+ },
+ "requestTimeoutMs": {
+ "type": "number",
+ "description": "Request timeout in milliseconds."
+ },
+ "minPartSize": {
+ "type": "number",
+ "description": "Minimum multipart part size in bytes."
+ },
+ "presign": {
+ "type": "object",
+ "description": "Presigned URL behavior configuration.",
+ "properties": {
+ "expiresInSeconds": {
+ "type": "number",
+ "description": "Expiration time in seconds for presigned URLs."
+ },
+ "signContentTypeForPut": {
+ "type": "boolean",
+ "description": "Whether to sign Content-Type for presigned PUT."
+ }
+ }
+ },
"credentials": {
"type": "object",
"description": "The credentials for the s3 compatible storage provider.",
@@ -348,6 +382,9 @@
},
"secretAccessKey": {
"type": "string"
+ },
+ "sessionToken": {
+ "type": "string"
}
}
}
@@ -369,8 +406,42 @@
},
"config": {
"type": "object",
- "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
+ "description": "The config for the S3 compatible storage provider.",
"properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://.r2.cloudflarestorage.com\"."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
+ },
+ "forcePathStyle": {
+ "type": "boolean",
+ "description": "Whether to use path-style bucket addressing."
+ },
+ "requestTimeoutMs": {
+ "type": "number",
+ "description": "Request timeout in milliseconds."
+ },
+ "minPartSize": {
+ "type": "number",
+ "description": "Minimum multipart part size in bytes."
+ },
+ "presign": {
+ "type": "object",
+ "description": "Presigned URL behavior configuration.",
+ "properties": {
+ "expiresInSeconds": {
+ "type": "number",
+ "description": "Expiration time in seconds for presigned URLs."
+ },
+ "signContentTypeForPut": {
+ "type": "boolean",
+ "description": "Whether to sign Content-Type for presigned PUT."
+ }
+ }
+ },
"credentials": {
"type": "object",
"description": "The credentials for the s3 compatible storage provider.",
@@ -380,6 +451,9 @@
},
"secretAccessKey": {
"type": "string"
+ },
+ "sessionToken": {
+ "type": "string"
}
}
},
@@ -458,8 +532,42 @@
},
"config": {
"type": "object",
- "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
+ "description": "The config for the S3 compatible storage provider.",
"properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://.r2.cloudflarestorage.com\"."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
+ },
+ "forcePathStyle": {
+ "type": "boolean",
+ "description": "Whether to use path-style bucket addressing."
+ },
+ "requestTimeoutMs": {
+ "type": "number",
+ "description": "Request timeout in milliseconds."
+ },
+ "minPartSize": {
+ "type": "number",
+ "description": "Minimum multipart part size in bytes."
+ },
+ "presign": {
+ "type": "object",
+ "description": "Presigned URL behavior configuration.",
+ "properties": {
+ "expiresInSeconds": {
+ "type": "number",
+ "description": "Expiration time in seconds for presigned URLs."
+ },
+ "signContentTypeForPut": {
+ "type": "boolean",
+ "description": "Whether to sign Content-Type for presigned PUT."
+ }
+ }
+ },
"credentials": {
"type": "object",
"description": "The credentials for the s3 compatible storage provider.",
@@ -469,6 +577,9 @@
},
"secretAccessKey": {
"type": "string"
+ },
+ "sessionToken": {
+ "type": "string"
}
}
}
@@ -490,8 +601,42 @@
},
"config": {
"type": "object",
- "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
+ "description": "The config for the S3 compatible storage provider.",
"properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://.r2.cloudflarestorage.com\"."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
+ },
+ "forcePathStyle": {
+ "type": "boolean",
+ "description": "Whether to use path-style bucket addressing."
+ },
+ "requestTimeoutMs": {
+ "type": "number",
+ "description": "Request timeout in milliseconds."
+ },
+ "minPartSize": {
+ "type": "number",
+ "description": "Minimum multipart part size in bytes."
+ },
+ "presign": {
+ "type": "object",
+ "description": "Presigned URL behavior configuration.",
+ "properties": {
+ "expiresInSeconds": {
+ "type": "number",
+ "description": "Expiration time in seconds for presigned URLs."
+ },
+ "signContentTypeForPut": {
+ "type": "boolean",
+ "description": "Whether to sign Content-Type for presigned PUT."
+ }
+ }
+ },
"credentials": {
"type": "object",
"description": "The credentials for the s3 compatible storage provider.",
@@ -501,6 +646,9 @@
},
"secretAccessKey": {
"type": "string"
+ },
+ "sessionToken": {
+ "type": "string"
}
}
},
@@ -941,8 +1089,42 @@
},
"config": {
"type": "object",
- "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
+ "description": "The config for the S3 compatible storage provider.",
"properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://.r2.cloudflarestorage.com\"."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
+ },
+ "forcePathStyle": {
+ "type": "boolean",
+ "description": "Whether to use path-style bucket addressing."
+ },
+ "requestTimeoutMs": {
+ "type": "number",
+ "description": "Request timeout in milliseconds."
+ },
+ "minPartSize": {
+ "type": "number",
+ "description": "Minimum multipart part size in bytes."
+ },
+ "presign": {
+ "type": "object",
+ "description": "Presigned URL behavior configuration.",
+ "properties": {
+ "expiresInSeconds": {
+ "type": "number",
+ "description": "Expiration time in seconds for presigned URLs."
+ },
+ "signContentTypeForPut": {
+ "type": "boolean",
+ "description": "Whether to sign Content-Type for presigned PUT."
+ }
+ }
+ },
"credentials": {
"type": "object",
"description": "The credentials for the s3 compatible storage provider.",
@@ -952,6 +1134,9 @@
},
"secretAccessKey": {
"type": "string"
+ },
+ "sessionToken": {
+ "type": "string"
}
}
}
@@ -973,8 +1158,42 @@
},
"config": {
"type": "object",
- "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
+ "description": "The config for the S3 compatible storage provider.",
"properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://.r2.cloudflarestorage.com\"."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
+ },
+ "forcePathStyle": {
+ "type": "boolean",
+ "description": "Whether to use path-style bucket addressing."
+ },
+ "requestTimeoutMs": {
+ "type": "number",
+ "description": "Request timeout in milliseconds."
+ },
+ "minPartSize": {
+ "type": "number",
+ "description": "Minimum multipart part size in bytes."
+ },
+ "presign": {
+ "type": "object",
+ "description": "Presigned URL behavior configuration.",
+ "properties": {
+ "expiresInSeconds": {
+ "type": "number",
+ "description": "Expiration time in seconds for presigned URLs."
+ },
+ "signContentTypeForPut": {
+ "type": "boolean",
+ "description": "Whether to sign Content-Type for presigned PUT."
+ }
+ }
+ },
"credentials": {
"type": "object",
"description": "The credentials for the s3 compatible storage provider.",
@@ -984,6 +1203,9 @@
},
"secretAccessKey": {
"type": "string"
+ },
+ "sessionToken": {
+ "type": "string"
}
}
},
diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json
index c90807448b..e7b94237c2 100644
--- a/packages/backend/server/package.json
+++ b/packages/backend/server/package.json
@@ -26,6 +26,7 @@
"postinstall": "prisma generate"
},
"dependencies": {
+ "@affine/s3-compat": "workspace:*",
"@affine/server-native": "workspace:*",
"@ai-sdk/anthropic": "^2.0.54",
"@ai-sdk/google": "^2.0.45",
@@ -34,8 +35,6 @@
"@ai-sdk/openai-compatible": "^1.0.28",
"@ai-sdk/perplexity": "^2.0.21",
"@apollo/server": "^4.12.2",
- "@aws-sdk/client-s3": "^3.948.0",
- "@aws-sdk/s3-request-presigner": "^3.948.0",
"@fal-ai/serverless-client": "^0.15.0",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
"@google-cloud/opentelemetry-resource-util": "^3.0.0",
diff --git a/packages/backend/server/src/__tests__/e2e/storage/r2-proxy.spec.ts b/packages/backend/server/src/__tests__/e2e/storage/r2-proxy.spec.ts
index 18fb9a2863..799a32ee31 100644
--- a/packages/backend/server/src/__tests__/e2e/storage/r2-proxy.spec.ts
+++ b/packages/backend/server/src/__tests__/e2e/storage/r2-proxy.spec.ts
@@ -41,9 +41,7 @@ class MockR2Provider extends R2StorageProvider {
super(config, bucket);
}
- destroy() {
- this.client.destroy();
- }
+ destroy() {}
// @ts-ignore expect override
override async proxyPutObject(
@@ -66,7 +64,7 @@ class MockR2Provider extends R2StorageProvider {
body: any,
options: { contentLength?: number } = {}
) {
- const etag = `"etag-${partNumber}"`;
+ const etag = `etag-${partNumber}`;
this.partCalls.push({
key,
uploadId,
@@ -322,7 +320,7 @@ e2e('should proxy multipart upload and return etag', async t => {
.send(payload);
t.is(res.status, 200);
- t.is(res.get('etag'), '"etag-1"');
+ t.is(res.get('etag'), 'etag-1');
const calls = getProvider().partCalls;
t.is(calls.length, 1);
@@ -356,7 +354,7 @@ e2e('should resume multipart upload and return uploaded parts', async t => {
const init2 = await createBlobUpload(workspace.id, key, totalSize, 'bin');
t.is(init2.method, 'MULTIPART');
t.is(init2.uploadId, 'upload-id');
- t.deepEqual(init2.uploadedParts, [{ partNumber: 1, etag: '"etag-1"' }]);
+ t.deepEqual(init2.uploadedParts, [{ partNumber: 1, etag: 'etag-1' }]);
t.is(getProvider().createMultipartCalls, 1);
});
diff --git a/packages/backend/server/src/base/config/__tests__/config.spec.ts b/packages/backend/server/src/base/config/__tests__/config.spec.ts
index 55185b4592..0ae9c88d1e 100644
--- a/packages/backend/server/src/base/config/__tests__/config.spec.ts
+++ b/packages/backend/server/src/base/config/__tests__/config.spec.ts
@@ -141,7 +141,7 @@ test('should override correctly', t => {
config: {
credentials: {
accessKeyId: '1',
- accessKeySecret: '1',
+ secretAccessKey: '1',
},
},
},
@@ -169,7 +169,7 @@ test('should override correctly', t => {
config: {
credentials: {
accessKeyId: '1',
- accessKeySecret: '1',
+ secretAccessKey: '1',
},
},
});
diff --git a/packages/backend/server/src/base/storage/__tests__/s3-list-parts.spec.ts b/packages/backend/server/src/base/storage/__tests__/s3-list-parts.spec.ts
new file mode 100644
index 0000000000..407de50bc0
--- /dev/null
+++ b/packages/backend/server/src/base/storage/__tests__/s3-list-parts.spec.ts
@@ -0,0 +1,49 @@
+import { parseListPartsXml } from '@affine/s3-compat';
+import test from 'ava';
+
+test('parseListPartsXml handles array parts and pagination', t => {
+ const xml = `
+
+ test
+ key
+ upload-id
+ 0
+ 3
+ true
+
+ 1
+ "etag-1"
+
+
+ 2
+ etag-2
+
+`;
+
+ const result = parseListPartsXml(xml);
+ t.deepEqual(result.parts, [
+ { partNumber: 1, etag: 'etag-1' },
+ { partNumber: 2, etag: 'etag-2' },
+ ]);
+ t.true(result.isTruncated);
+ t.is(result.nextPartNumberMarker, '3');
+});
+
+test('parseListPartsXml handles single part', t => {
+ const xml = `
+
+ test
+ key
+ upload-id
+ false
+
+ 5
+ "etag-5"
+
+`;
+
+ const result = parseListPartsXml(xml);
+ t.deepEqual(result.parts, [{ partNumber: 5, etag: 'etag-5' }]);
+ t.false(result.isTruncated);
+ t.is(result.nextPartNumberMarker, undefined);
+});
diff --git a/packages/backend/server/src/base/storage/__tests__/s3.spec.ts b/packages/backend/server/src/base/storage/__tests__/s3.spec.ts
index 8d789a73a7..f596784f14 100644
--- a/packages/backend/server/src/base/storage/__tests__/s3.spec.ts
+++ b/packages/backend/server/src/base/storage/__tests__/s3.spec.ts
@@ -4,7 +4,8 @@ import { S3StorageProvider } from '../providers/s3';
import { SIGNED_URL_EXPIRED } from '../providers/utils';
const config = {
- region: 'auto',
+ region: 'us-east-1',
+ endpoint: 'https://s3.us-east-1.amazonaws.com',
credentials: {
accessKeyId: 'test',
secretAccessKey: 'test',
@@ -24,6 +25,8 @@ test('presignPut should return url and headers', async t => {
t.truthy(result);
t.true(result!.url.length > 0);
t.true(result!.url.includes('X-Amz-Algorithm=AWS4-HMAC-SHA256'));
+ t.true(result!.url.includes('X-Amz-SignedHeaders='));
+ t.true(result!.url.includes('content-type'));
t.deepEqual(result!.headers, { 'Content-Type': 'text/plain' });
const now = Date.now();
t.true(result!.expiresAt.getTime() >= now + SIGNED_URL_EXPIRED * 1000 - 2000);
@@ -41,12 +44,15 @@ test('presignUploadPart should return url', async t => {
test('createMultipartUpload should return uploadId', async t => {
const provider = createProvider();
- let receivedCommand: any;
- const sendStub = async (command: any) => {
- receivedCommand = command;
- return { UploadId: 'upload-1' };
+ let receivedKey: string | undefined;
+ let receivedMeta: any;
+ (provider as any).client = {
+ createMultipartUpload: async (key: string, meta: any) => {
+ receivedKey = key;
+ receivedMeta = meta;
+ return { uploadId: 'upload-1' };
+ },
};
- (provider as any).client = { send: sendStub };
const now = Date.now();
const result = await provider.createMultipartUpload('key', {
@@ -56,25 +62,29 @@ test('createMultipartUpload should return uploadId', async t => {
t.is(result?.uploadId, 'upload-1');
t.true(result!.expiresAt.getTime() >= now + SIGNED_URL_EXPIRED * 1000 - 2000);
t.true(result!.expiresAt.getTime() <= now + SIGNED_URL_EXPIRED * 1000 + 2000);
- t.is(receivedCommand.input.Key, 'key');
- t.is(receivedCommand.input.ContentType, 'text/plain');
+ t.is(receivedKey, 'key');
+ t.is(receivedMeta.contentType, 'text/plain');
});
test('completeMultipartUpload should order parts', async t => {
const provider = createProvider();
- let called = false;
- const sendStub = async (command: any) => {
- called = true;
- t.deepEqual(command.input.MultipartUpload.Parts, [
- { ETag: 'a', PartNumber: 1 },
- { ETag: 'b', PartNumber: 2 },
- ]);
+ let receivedParts: any;
+ (provider as any).client = {
+ completeMultipartUpload: async (
+ _key: string,
+ _uploadId: string,
+ parts: any
+ ) => {
+ receivedParts = parts;
+ },
};
- (provider as any).client = { send: sendStub };
await provider.completeMultipartUpload('key', 'upload-1', [
{ partNumber: 2, etag: 'b' },
{ partNumber: 1, etag: 'a' },
]);
- t.true(called);
+ t.deepEqual(receivedParts, [
+ { partNumber: 1, etag: 'a' },
+ { partNumber: 2, etag: 'b' },
+ ]);
});
diff --git a/packages/backend/server/src/base/storage/providers/index.ts b/packages/backend/server/src/base/storage/providers/index.ts
index 6a9096d272..c8480e8b54 100644
--- a/packages/backend/server/src/base/storage/providers/index.ts
+++ b/packages/backend/server/src/base/storage/providers/index.ts
@@ -33,9 +33,44 @@ export type StorageProviderConfig = { bucket: string } & (
const S3ConfigSchema: JSONSchema = {
type: 'object',
- description:
- 'The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html',
+ description: 'The config for the S3 compatible storage provider.',
properties: {
+ endpoint: {
+ type: 'string',
+ description:
+ 'The S3 compatible endpoint. Example: "https://s3.us-east-1.amazonaws.com" or "https://.r2.cloudflarestorage.com".',
+ },
+ region: {
+ type: 'string',
+ description:
+ 'The region for the storage provider. Example: "us-east-1" or "auto" for R2.',
+ },
+ forcePathStyle: {
+ type: 'boolean',
+ description: 'Whether to use path-style bucket addressing.',
+ },
+ requestTimeoutMs: {
+ type: 'number',
+ description: 'Request timeout in milliseconds.',
+ },
+ minPartSize: {
+ type: 'number',
+ description: 'Minimum multipart part size in bytes.',
+ },
+ presign: {
+ type: 'object',
+ description: 'Presigned URL behavior configuration.',
+ properties: {
+ expiresInSeconds: {
+ type: 'number',
+ description: 'Expiration time in seconds for presigned URLs.',
+ },
+ signContentTypeForPut: {
+ type: 'boolean',
+ description: 'Whether to sign Content-Type for presigned PUT.',
+ },
+ },
+ },
credentials: {
type: 'object',
description: 'The credentials for the s3 compatible storage provider.',
@@ -46,6 +81,9 @@ const S3ConfigSchema: JSONSchema = {
secretAccessKey: {
type: 'string',
},
+ sessionToken: {
+ type: 'string',
+ },
},
},
},
diff --git a/packages/backend/server/src/base/storage/providers/r2.ts b/packages/backend/server/src/base/storage/providers/r2.ts
index 5b20643143..b7f08d0d4e 100644
--- a/packages/backend/server/src/base/storage/providers/r2.ts
+++ b/packages/backend/server/src/base/storage/providers/r2.ts
@@ -1,7 +1,6 @@
import assert from 'node:assert';
import { Readable } from 'node:stream';
-import { PutObjectCommand, UploadPartCommand } from '@aws-sdk/client-s3';
import { Logger } from '@nestjs/common';
import {
@@ -39,9 +38,6 @@ export class R2StorageProvider extends S3StorageProvider {
...config,
forcePathStyle: true,
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
- // see https://github.com/aws/aws-sdk-js-v3/issues/6810
- requestChecksumCalculation: 'WHEN_REQUIRED',
- responseChecksumValidation: 'WHEN_REQUIRED',
},
bucket
);
@@ -179,15 +175,10 @@ export class R2StorageProvider extends S3StorageProvider {
body: Readable | Buffer | Uint8Array | string,
options: { contentType?: string; contentLength?: number } = {}
) {
- return this.client.send(
- new PutObjectCommand({
- Bucket: this.bucket,
- Key: key,
- Body: body,
- ContentType: options.contentType,
- ContentLength: options.contentLength,
- })
- );
+ return this.client.putObject(key, body as any, {
+ contentType: options.contentType,
+ contentLength: options.contentLength,
+ });
}
async proxyUploadPart(
@@ -197,18 +188,15 @@ export class R2StorageProvider extends S3StorageProvider {
body: Readable | Buffer | Uint8Array | string,
options: { contentLength?: number } = {}
) {
- const result = await this.client.send(
- new UploadPartCommand({
- Bucket: this.bucket,
- Key: key,
- UploadId: uploadId,
- PartNumber: partNumber,
- Body: body,
- ContentLength: options.contentLength,
- })
+ const result = await this.client.uploadPart(
+ key,
+ uploadId,
+ partNumber,
+ body as any,
+ { contentLength: options.contentLength }
);
- return result.ETag;
+ return result.etag;
}
override async get(
diff --git a/packages/backend/server/src/base/storage/providers/s3.ts b/packages/backend/server/src/base/storage/providers/s3.ts
index 4531fe47d2..1bafcb395c 100644
--- a/packages/backend/server/src/base/storage/providers/s3.ts
+++ b/packages/backend/server/src/base/storage/providers/s3.ts
@@ -1,24 +1,12 @@
/* oxlint-disable @typescript-eslint/no-non-null-assertion */
import { Readable } from 'node:stream';
-import {
- AbortMultipartUploadCommand,
- CompleteMultipartUploadCommand,
- CreateMultipartUploadCommand,
- DeleteObjectCommand,
- GetObjectCommand,
- HeadObjectCommand,
- ListObjectsV2Command,
- ListPartsCommand,
- NoSuchKey,
- NoSuchUpload,
- NotFound,
- PutObjectCommand,
- S3Client,
- S3ClientConfig,
- UploadPartCommand,
-} from '@aws-sdk/client-s3';
-import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
+import type {
+ S3CompatClient,
+ S3CompatConfig,
+ S3CompatCredentials,
+} from '@affine/s3-compat';
+import { createS3CompatClient } from '@affine/s3-compat';
import { Logger } from '@nestjs/common';
import {
@@ -33,30 +21,55 @@ import {
} from './provider';
import { autoMetadata, SIGNED_URL_EXPIRED, toBuffer } from './utils';
-export interface S3StorageConfig extends S3ClientConfig {
+export interface S3StorageConfig {
+ endpoint?: string;
+ region: string;
+ credentials: S3CompatCredentials;
+ forcePathStyle?: boolean;
+ requestTimeoutMs?: number;
+ minPartSize?: number;
+ presign?: {
+ expiresInSeconds?: number;
+ signContentTypeForPut?: boolean;
+ };
usePresignedURL?: {
enabled: boolean;
};
}
+function resolveEndpoint(config: S3StorageConfig) {
+ if (config.endpoint) {
+ return config.endpoint;
+ }
+ if (config.region === 'us-east-1') {
+ return 'https://s3.amazonaws.com';
+ }
+ return `https://s3.${config.region}.amazonaws.com`;
+}
+
export class S3StorageProvider implements StorageProvider {
protected logger: Logger;
- protected client: S3Client;
+ protected client: S3CompatClient;
private readonly usePresignedURL: boolean;
constructor(
config: S3StorageConfig,
public readonly bucket: string
) {
- const { usePresignedURL, ...clientConfig } = config;
- this.client = new S3Client({
- region: 'auto',
- // s3 client uses keep-alive by default to accelerate requests, and max requests queue is 50.
- // If some of them are long holding or dead without response, the whole queue will block.
- // By default no timeout is set for requests or connections, so we set them here.
- requestHandler: { requestTimeout: 60_000, connectionTimeout: 10_000 },
+ const { usePresignedURL, presign, credentials, ...clientConfig } = config;
+
+ const compatConfig: S3CompatConfig = {
...clientConfig,
- });
+ endpoint: resolveEndpoint(config),
+ bucket,
+ requestTimeoutMs: clientConfig.requestTimeoutMs ?? 60_000,
+ presign: {
+ expiresInSeconds: presign?.expiresInSeconds ?? SIGNED_URL_EXPIRED,
+ signContentTypeForPut: presign?.signContentTypeForPut ?? true,
+ },
+ };
+
+ this.client = createS3CompatClient(compatConfig, credentials);
this.usePresignedURL = usePresignedURL?.enabled ?? false;
this.logger = new Logger(`${S3StorageProvider.name}:${bucket}`);
}
@@ -71,19 +84,10 @@ export class S3StorageProvider implements StorageProvider {
metadata = autoMetadata(blob, metadata);
try {
- await this.client.send(
- new PutObjectCommand({
- Bucket: this.bucket,
- Key: key,
- Body: blob,
-
- // metadata
- ContentType: metadata.contentType,
- ContentLength: metadata.contentLength,
- // TODO(@forehalo): Cloudflare doesn't support CRC32, use md5 instead later.
- // ChecksumCRC32: metadata.checksumCRC32,
- })
- );
+ await this.client.putObject(key, blob, {
+ contentType: metadata.contentType,
+ contentLength: metadata.contentLength,
+ });
this.logger.verbose(`Object \`${key}\` put`);
} catch (e) {
@@ -104,20 +108,12 @@ export class S3StorageProvider implements StorageProvider {
): Promise {
try {
const contentType = metadata.contentType ?? 'application/octet-stream';
- const url = await getSignedUrl(
- this.client,
- new PutObjectCommand({
- Bucket: this.bucket,
- Key: key,
- ContentType: contentType,
- }),
- { expiresIn: SIGNED_URL_EXPIRED }
- );
+ const result = await this.client.presignPutObject(key, { contentType });
return {
- url,
- headers: { 'Content-Type': contentType },
- expiresAt: new Date(Date.now() + SIGNED_URL_EXPIRED * 1000),
+ url: result.url,
+ headers: result.headers,
+ expiresAt: result.expiresAt,
};
} catch (e) {
this.logger.error(
@@ -137,20 +133,16 @@ export class S3StorageProvider implements StorageProvider {
): Promise {
try {
const contentType = metadata.contentType ?? 'application/octet-stream';
- const response = await this.client.send(
- new CreateMultipartUploadCommand({
- Bucket: this.bucket,
- Key: key,
- ContentType: contentType,
- })
- );
+ const response = await this.client.createMultipartUpload(key, {
+ contentType,
+ });
- if (!response.UploadId) {
+ if (!response.uploadId) {
return;
}
return {
- uploadId: response.UploadId,
+ uploadId: response.uploadId,
expiresAt: new Date(Date.now() + SIGNED_URL_EXPIRED * 1000),
};
} catch (e) {
@@ -171,20 +163,15 @@ export class S3StorageProvider implements StorageProvider {
partNumber: number
): Promise {
try {
- const url = await getSignedUrl(
- this.client,
- new UploadPartCommand({
- Bucket: this.bucket,
- Key: key,
- UploadId: uploadId,
- PartNumber: partNumber,
- }),
- { expiresIn: SIGNED_URL_EXPIRED }
+ const result = await this.client.presignUploadPart(
+ key,
+ uploadId,
+ partNumber
);
return {
- url,
- expiresAt: new Date(Date.now() + SIGNED_URL_EXPIRED * 1000),
+ url: result.url,
+ expiresAt: result.expiresAt,
};
} catch (e) {
this.logger.error(
@@ -198,47 +185,9 @@ export class S3StorageProvider implements StorageProvider {
key: string,
uploadId: string
): Promise {
- const parts: MultipartUploadPart[] = [];
- let partNumberMarker: string | undefined;
-
try {
- // ListParts is paginated by part number marker
- // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html
- // R2 follows S3 semantics here.
- while (true) {
- const response = await this.client.send(
- new ListPartsCommand({
- Bucket: this.bucket,
- Key: key,
- UploadId: uploadId,
- PartNumberMarker: partNumberMarker,
- })
- );
-
- for (const part of response.Parts ?? []) {
- if (!part.PartNumber || !part.ETag) {
- continue;
- }
- parts.push({ partNumber: part.PartNumber, etag: part.ETag });
- }
-
- if (!response.IsTruncated) {
- break;
- }
-
- if (response.NextPartNumberMarker === undefined) {
- break;
- }
-
- partNumberMarker = response.NextPartNumberMarker;
- }
-
- return parts;
+ return await this.client.listParts(key, uploadId);
} catch (e) {
- // the upload may have been aborted/expired by provider lifecycle rules
- if (e instanceof NoSuchUpload || e instanceof NotFound) {
- return undefined;
- }
this.logger.error(`Failed to list multipart upload parts for \`${key}\``);
throw e;
}
@@ -254,19 +203,7 @@ export class S3StorageProvider implements StorageProvider {
(left, right) => left.partNumber - right.partNumber
);
- await this.client.send(
- new CompleteMultipartUploadCommand({
- Bucket: this.bucket,
- Key: key,
- UploadId: uploadId,
- MultipartUpload: {
- Parts: orderedParts.map(part => ({
- ETag: part.etag,
- PartNumber: part.partNumber,
- })),
- },
- })
- );
+ await this.client.completeMultipartUpload(key, uploadId, orderedParts);
} catch (e) {
this.logger.error(`Failed to complete multipart upload for \`${key}\``);
throw e;
@@ -275,13 +212,7 @@ export class S3StorageProvider implements StorageProvider {
async abortMultipartUpload(key: string, uploadId: string): Promise {
try {
- await this.client.send(
- new AbortMultipartUploadCommand({
- Bucket: this.bucket,
- Key: key,
- UploadId: uploadId,
- })
- );
+ await this.client.abortMultipartUpload(key, uploadId);
} catch (e) {
this.logger.error(`Failed to abort multipart upload for \`${key}\``);
throw e;
@@ -290,25 +221,19 @@ export class S3StorageProvider implements StorageProvider {
async head(key: string) {
try {
- const obj = await this.client.send(
- new HeadObjectCommand({
- Bucket: this.bucket,
- Key: key,
- })
- );
-
- return {
- contentType: obj.ContentType!,
- contentLength: obj.ContentLength!,
- lastModified: obj.LastModified!,
- checksumCRC32: obj.ChecksumCRC32,
- };
- } catch (e) {
- // 404
- if (e instanceof NoSuchKey || e instanceof NotFound) {
+ const obj = await this.client.headObject(key);
+ if (!obj) {
this.logger.verbose(`Object \`${key}\` not found`);
return undefined;
}
+
+ return {
+ contentType: obj.contentType ?? 'application/octet-stream',
+ contentLength: obj.contentLength ?? 0,
+ lastModified: obj.lastModified ?? new Date(0),
+ checksumCRC32: obj.checksumCRC32,
+ };
+ } catch (e) {
this.logger.error(`Failed to head object \`${key}\``);
throw e;
}
@@ -323,25 +248,13 @@ export class S3StorageProvider implements StorageProvider {
redirectUrl?: string;
}> {
try {
- const command = new GetObjectCommand({
- Bucket: this.bucket,
- Key: key,
- });
-
if (this.usePresignedURL && signedUrl) {
const metadata = await this.head(key);
if (metadata) {
- const url = await getSignedUrl(
- this.client,
- new GetObjectCommand({
- Bucket: this.bucket,
- Key: key,
- }),
- { expiresIn: SIGNED_URL_EXPIRED }
- );
+ const result = await this.client.presignGetObject(key);
return {
- redirectUrl: url,
+ redirectUrl: result.url,
metadata,
};
}
@@ -350,68 +263,41 @@ export class S3StorageProvider implements StorageProvider {
return {};
}
- const obj = await this.client.send(command);
-
- if (!obj.Body) {
+ const obj = await this.client.getObjectResponse(key);
+ if (!obj || !obj.body) {
this.logger.verbose(`Object \`${key}\` not found`);
return {};
}
+ const contentType = obj.headers.get('content-type') ?? undefined;
+ const contentLengthHeader = obj.headers.get('content-length');
+ const contentLength = contentLengthHeader
+ ? Number(contentLengthHeader)
+ : undefined;
+ const lastModifiedHeader = obj.headers.get('last-modified');
+ const lastModified = lastModifiedHeader
+ ? new Date(lastModifiedHeader)
+ : undefined;
+
this.logger.verbose(`Read object \`${key}\``);
return {
- // @ts-expect-errors ignore browser response type `Blob`
- body: obj.Body,
+ body: Readable.fromWeb(obj.body as any),
metadata: {
- // always set when putting object
- contentType: obj.ContentType ?? 'application/octet-stream',
- contentLength: obj.ContentLength!,
- lastModified: obj.LastModified!,
- checksumCRC32: obj.ChecksumCRC32,
+ contentType: contentType ?? 'application/octet-stream',
+ contentLength: contentLength ?? 0,
+ lastModified: lastModified ?? new Date(0),
+ checksumCRC32: obj.headers.get('x-amz-checksum-crc32') ?? undefined,
},
};
} catch (e) {
- // 404
- if (e instanceof NoSuchKey) {
- this.logger.verbose(`Object \`${key}\` not found`);
- return {};
- }
this.logger.error(`Failed to read object \`${key}\``);
throw e;
}
}
async list(prefix?: string): Promise {
- // continuationToken should be `string | undefined`,
- // but TypeScript will fail on type infer in the code below.
- // Seems to be a bug in TypeScript
- let continuationToken: any = undefined;
- let hasMore = true;
- let result: ListObjectsMetadata[] = [];
-
try {
- while (hasMore) {
- const listResult = await this.client.send(
- new ListObjectsV2Command({
- Bucket: this.bucket,
- Prefix: prefix,
- ContinuationToken: continuationToken,
- })
- );
-
- if (listResult.Contents?.length) {
- result = result.concat(
- listResult.Contents.map(r => ({
- key: r.Key!,
- lastModified: r.LastModified!,
- contentLength: r.Size!,
- }))
- );
- }
-
- // has more items not listed
- hasMore = !!listResult.IsTruncated;
- continuationToken = listResult.NextContinuationToken;
- }
+ const result = await this.client.listObjectsV2(prefix);
this.logger.verbose(
`List ${result.length} objects with prefix \`${prefix}\``
@@ -425,12 +311,7 @@ export class S3StorageProvider implements StorageProvider {
async delete(key: string): Promise {
try {
- await this.client.send(
- new DeleteObjectCommand({
- Bucket: this.bucket,
- Key: key,
- })
- );
+ await this.client.deleteObject(key);
this.logger.verbose(`Deleted object \`${key}\``);
} catch (e) {
diff --git a/packages/backend/server/src/core/sync/gateway.ts b/packages/backend/server/src/core/sync/gateway.ts
index d789ac58e4..b312241280 100644
--- a/packages/backend/server/src/core/sync/gateway.ts
+++ b/packages/backend/server/src/core/sync/gateway.ts
@@ -23,6 +23,7 @@ import {
SpaceAccessDenied,
} from '../../base';
import { Models } from '../../models';
+import { mergeUpdatesInApplyWay } from '../../native';
import { CurrentUser } from '../auth';
import {
DocReader,
@@ -48,8 +49,9 @@ type EventResponse = Data extends never
data: Data;
};
-// 019 only receives space:broadcast-doc-updates and send space:push-doc-updates
-// 020 only receives space:broadcast-doc-update and send space:push-doc-update
+// sync-019: legacy 0.19.x clients (broadcast-doc-updates/push-doc-updates).
+// Remove after 2026-06-30 once metrics show 0 usage for 30 days.
+// 020+: receives space:broadcast-doc-updates (batch) and sends space:push-doc-update.
type RoomType = 'sync' | `${string}:awareness` | 'sync-019';
function Room(
@@ -105,6 +107,16 @@ interface PushDocUpdateMessage {
update: string;
}
+interface BroadcastDocUpdatesMessage {
+ spaceType: SpaceType;
+ spaceId: string;
+ docId: string;
+ updates: string[];
+ timestamp: number;
+ editor?: string;
+ compressed?: boolean;
+}
+
interface LoadDocMessage {
spaceType: SpaceType;
spaceId: string;
@@ -157,6 +169,62 @@ export class SpaceSyncGateway
private readonly models: Models
) {}
+ private encodeUpdates(updates: Uint8Array[]) {
+ return updates.map(update => Buffer.from(update).toString('base64'));
+ }
+
+ private buildBroadcastPayload(
+ spaceType: SpaceType,
+ spaceId: string,
+ docId: string,
+ updates: Uint8Array[],
+ timestamp: number,
+ editor?: string
+ ): BroadcastDocUpdatesMessage {
+ const encodedUpdates = this.encodeUpdates(updates);
+ if (updates.length <= 1) {
+ return {
+ spaceType,
+ spaceId,
+ docId,
+ updates: encodedUpdates,
+ timestamp,
+ editor,
+ compressed: false,
+ };
+ }
+
+ try {
+ const merged = mergeUpdatesInApplyWay(
+ updates.map(update => Buffer.from(update))
+ );
+ metrics.socketio.counter('doc_updates_compressed').add(1);
+ return {
+ spaceType,
+ spaceId,
+ docId,
+ updates: [Buffer.from(merged).toString('base64')],
+ timestamp,
+ editor,
+ compressed: true,
+ };
+ } catch (error) {
+ this.logger.warn(
+ 'Failed to merge updates for broadcast, falling back to batch',
+ error as Error
+ );
+ return {
+ spaceType,
+ spaceId,
+ docId,
+ updates: encodedUpdates,
+ timestamp,
+ editor,
+ compressed: false,
+ };
+ }
+ }
+
handleConnection() {
this.connectionCount++;
this.logger.debug(`New connection, total: ${this.connectionCount}`);
@@ -184,9 +252,7 @@ export class SpaceSyncGateway
return;
}
- const encodedUpdates = updates.map(update =>
- Buffer.from(update).toString('base64')
- );
+ const encodedUpdates = this.encodeUpdates(updates);
this.server
.to(Room(spaceId, 'sync-019'))
@@ -196,19 +262,27 @@ export class SpaceSyncGateway
docId,
updates: encodedUpdates,
timestamp,
- });
-
- const room = `${spaceType}:${Room(spaceId)}`;
- encodedUpdates.forEach(update => {
- this.server.to(room).emit('space:broadcast-doc-update', {
- spaceType,
- spaceId,
- docId,
- update,
- timestamp,
editor,
});
- });
+ metrics.socketio
+ .counter('sync_019_broadcast')
+ .add(encodedUpdates.length, { event: 'doc_updates_pushed' });
+
+ const room = `${spaceType}:${Room(spaceId)}`;
+ const payload = this.buildBroadcastPayload(
+ spaceType as SpaceType,
+ spaceId,
+ docId,
+ updates,
+ timestamp,
+ editor
+ );
+ this.server.to(room).emit('space:broadcast-doc-updates', payload);
+ metrics.socketio
+ .counter('doc_updates_broadcast')
+ .add(payload.updates.length, {
+ mode: payload.compressed ? 'compressed' : 'batch',
+ });
}
selectAdapter(client: Socket, spaceType: SpaceType): SyncSocketAdapter {
@@ -330,19 +404,35 @@ export class SpaceSyncGateway
user.id
);
+ metrics.socketio
+ .counter('sync_019_event')
+ .add(1, { event: 'push-doc-updates' });
+
// broadcast to 0.19.x clients
- client
- .to(Room(spaceId, 'sync-019'))
- .emit('space:broadcast-doc-updates', { ...message, timestamp });
+ client.to(Room(spaceId, 'sync-019')).emit('space:broadcast-doc-updates', {
+ ...message,
+ timestamp,
+ editor: user.id,
+ });
// broadcast to new clients
- updates.forEach(update => {
- client.to(adapter.room(spaceId)).emit('space:broadcast-doc-update', {
- ...message,
- update,
- timestamp,
+ const decodedUpdates = updates.map(update => Buffer.from(update, 'base64'));
+ const payload = this.buildBroadcastPayload(
+ spaceType,
+ spaceId,
+ docId,
+ decodedUpdates,
+ timestamp,
+ user.id
+ );
+ client
+ .to(adapter.room(spaceId))
+ .emit('space:broadcast-doc-updates', payload);
+ metrics.socketio
+ .counter('doc_updates_broadcast')
+ .add(payload.updates.length, {
+ mode: payload.compressed ? 'compressed' : 'batch',
});
- });
return {
data: {
@@ -378,16 +468,25 @@ export class SpaceSyncGateway
docId,
updates: [update],
timestamp,
+ editor: user.id,
});
- client.to(adapter.room(spaceId)).emit('space:broadcast-doc-update', {
+ const payload = this.buildBroadcastPayload(
spaceType,
spaceId,
docId,
- update,
+ [Buffer.from(update, 'base64')],
timestamp,
- editor: user.id,
- });
+ user.id
+ );
+ client
+ .to(adapter.room(spaceId))
+ .emit('space:broadcast-doc-updates', payload);
+ metrics.socketio
+ .counter('doc_updates_broadcast')
+ .add(payload.updates.length, {
+ mode: payload.compressed ? 'compressed' : 'batch',
+ });
return {
data: {
diff --git a/packages/backend/server/tsconfig.json b/packages/backend/server/tsconfig.json
index 6997a939f7..7c1c612283 100644
--- a/packages/backend/server/tsconfig.json
+++ b/packages/backend/server/tsconfig.json
@@ -12,6 +12,7 @@
},
"include": ["./src"],
"references": [
+ { "path": "../../common/s3-compat" },
{ "path": "../native" },
{ "path": "../../../tools/cli" },
{ "path": "../../../tools/utils" },
diff --git a/packages/common/nbstore/src/__tests__/base64.bench.ts b/packages/common/nbstore/src/__tests__/base64.bench.ts
new file mode 100644
index 0000000000..483db83e92
--- /dev/null
+++ b/packages/common/nbstore/src/__tests__/base64.bench.ts
@@ -0,0 +1,23 @@
+import { bench, describe } from 'vitest';
+
+import { base64ToUint8Array, uint8ArrayToBase64 } from '../impls/cloud/socket';
+
+const data = new Uint8Array(1024 * 256);
+for (let i = 0; i < data.length; i++) {
+ data[i] = i % 251;
+}
+let encoded = '';
+
+await uint8ArrayToBase64(data).then(result => {
+ encoded = result;
+});
+
+describe('base64 helpers', () => {
+ bench('encode Uint8Array to base64', async () => {
+ await uint8ArrayToBase64(data);
+ });
+
+ bench('decode base64 to Uint8Array', () => {
+ base64ToUint8Array(encoded);
+ });
+});
diff --git a/packages/common/nbstore/src/__tests__/base64.spec.ts b/packages/common/nbstore/src/__tests__/base64.spec.ts
new file mode 100644
index 0000000000..0a062b7acf
--- /dev/null
+++ b/packages/common/nbstore/src/__tests__/base64.spec.ts
@@ -0,0 +1,27 @@
+import { describe, expect, test } from 'vitest';
+
+import { base64ToUint8Array, uint8ArrayToBase64 } from '../impls/cloud/socket';
+
+function makeSample(size: number) {
+ const data = new Uint8Array(size);
+ for (let i = 0; i < size; i++) {
+ data[i] = i % 251;
+ }
+ return data;
+}
+
+describe('base64 helpers', () => {
+ test('roundtrip preserves data', async () => {
+ const input = makeSample(1024);
+ const encoded = await uint8ArrayToBase64(input);
+ const decoded = base64ToUint8Array(encoded);
+ expect(decoded).toEqual(input);
+ });
+
+ test('handles large payloads', async () => {
+ const input = makeSample(256 * 1024);
+ const encoded = await uint8ArrayToBase64(input);
+ const decoded = base64ToUint8Array(encoded);
+ expect(decoded).toEqual(input);
+ });
+});
diff --git a/packages/common/nbstore/src/__tests__/cloud-doc-updates.spec.ts b/packages/common/nbstore/src/__tests__/cloud-doc-updates.spec.ts
new file mode 100644
index 0000000000..71816d2d84
--- /dev/null
+++ b/packages/common/nbstore/src/__tests__/cloud-doc-updates.spec.ts
@@ -0,0 +1,41 @@
+import { describe, expect, test } from 'vitest';
+
+import { CloudDocStorage } from '../impls/cloud/doc';
+
+const base64UpdateA = 'AQID';
+const base64UpdateB = 'BAUG';
+
+describe('CloudDocStorage broadcast updates', () => {
+ test('emits updates from batch payload', () => {
+ const storage = new CloudDocStorage({
+ id: 'space-1',
+ serverBaseUrl: 'http://localhost',
+ isSelfHosted: true,
+ type: 'workspace',
+ readonlyMode: true,
+ });
+
+ (storage as any).connection.idConverter = {
+ oldIdToNewId: (id: string) => id,
+ newIdToOldId: (id: string) => id,
+ };
+
+ const received: Uint8Array[] = [];
+ storage.subscribeDocUpdate(update => {
+ received.push(update.bin);
+ });
+
+ storage.onServerUpdates({
+ spaceType: 'workspace',
+ spaceId: 'space-1',
+ docId: 'doc-1',
+ updates: [base64UpdateA, base64UpdateB],
+ timestamp: Date.now(),
+ });
+
+ expect(received).toEqual([
+ new Uint8Array([1, 2, 3]),
+ new Uint8Array([4, 5, 6]),
+ ]);
+ });
+});
diff --git a/packages/common/nbstore/src/impls/cloud/doc.ts b/packages/common/nbstore/src/impls/cloud/doc.ts
index c2fe4a06d7..1bb13a8b48 100644
--- a/packages/common/nbstore/src/impls/cloud/doc.ts
+++ b/packages/common/nbstore/src/impls/cloud/doc.ts
@@ -38,12 +38,32 @@ export class CloudDocStorage extends DocStorageBase {
onServerUpdate: ServerEventsMap['space:broadcast-doc-update'] = message => {
if (
- this.spaceType === message.spaceType &&
- this.spaceId === message.spaceId
+ this.spaceType !== message.spaceType ||
+ this.spaceId !== message.spaceId
) {
+ return;
+ }
+
+ this.emit('update', {
+ docId: this.idConverter.oldIdToNewId(message.docId),
+ bin: base64ToUint8Array(message.update),
+ timestamp: new Date(message.timestamp),
+ editor: message.editor,
+ });
+ };
+
+ onServerUpdates: ServerEventsMap['space:broadcast-doc-updates'] = message => {
+ if (
+ this.spaceType !== message.spaceType ||
+ this.spaceId !== message.spaceId
+ ) {
+ return;
+ }
+
+ for (const update of message.updates) {
this.emit('update', {
docId: this.idConverter.oldIdToNewId(message.docId),
- bin: base64ToUint8Array(message.update),
+ bin: base64ToUint8Array(update),
timestamp: new Date(message.timestamp),
editor: message.editor,
});
@@ -52,7 +72,8 @@ export class CloudDocStorage extends DocStorageBase {
readonly connection = new CloudDocStorageConnection(
this.options,
- this.onServerUpdate
+ this.onServerUpdate,
+ this.onServerUpdates
);
override async getDocSnapshot(docId: string) {
@@ -184,7 +205,8 @@ export class CloudDocStorage extends DocStorageBase {
class CloudDocStorageConnection extends SocketConnection {
constructor(
private readonly options: CloudDocStorageOptions,
- private readonly onServerUpdate: ServerEventsMap['space:broadcast-doc-update']
+ private readonly onServerUpdate: ServerEventsMap['space:broadcast-doc-update'],
+ private readonly onServerUpdates: ServerEventsMap['space:broadcast-doc-updates']
) {
super(options.serverBaseUrl, options.isSelfHosted);
}
@@ -210,6 +232,7 @@ class CloudDocStorageConnection extends SocketConnection {
}
socket.on('space:broadcast-doc-update', this.onServerUpdate);
+ socket.on('space:broadcast-doc-updates', this.onServerUpdates);
return { socket, disconnect };
} catch (e) {
@@ -230,6 +253,7 @@ class CloudDocStorageConnection extends SocketConnection {
spaceId: this.options.id,
});
socket.off('space:broadcast-doc-update', this.onServerUpdate);
+ socket.off('space:broadcast-doc-updates', this.onServerUpdates);
super.doDisconnect({ socket, disconnect });
}
diff --git a/packages/common/nbstore/src/impls/cloud/socket.ts b/packages/common/nbstore/src/impls/cloud/socket.ts
index 37de029545..122361eb2d 100644
--- a/packages/common/nbstore/src/impls/cloud/socket.ts
+++ b/packages/common/nbstore/src/impls/cloud/socket.ts
@@ -30,6 +30,15 @@ interface ServerEvents {
timestamp: number;
editor: string;
};
+ 'space:broadcast-doc-updates': {
+ spaceType: string;
+ spaceId: string;
+ docId: string;
+ updates: string[];
+ timestamp: number;
+ editor?: string;
+ compressed?: boolean;
+ };
'space:collect-awareness': {
spaceType: string;
@@ -124,33 +133,42 @@ export type ClientEventsMap = {
export type Socket = SocketIO;
-export function uint8ArrayToBase64(array: Uint8Array): Promise {
- return new Promise(resolve => {
- // Create a blob from the Uint8Array
- const blob = new Blob([array]);
+type BufferConstructorLike = {
+ from(
+ data: Uint8Array | string,
+ encoding?: string
+ ): Uint8Array & {
+ toString(encoding: string): string;
+ };
+};
- const reader = new FileReader();
- reader.onload = function () {
- const dataUrl = reader.result as string | null;
- if (!dataUrl) {
- resolve('');
- return;
- }
- // The result includes the `data:` URL prefix and the MIME type. We only want the Base64 data
- const base64 = dataUrl.split(',')[1];
- resolve(base64);
- };
+const BufferCtor = (globalThis as { Buffer?: BufferConstructorLike }).Buffer;
+const CHUNK_SIZE = 0x8000;
- reader.readAsDataURL(blob);
- });
+export async function uint8ArrayToBase64(array: Uint8Array): Promise {
+ if (BufferCtor) {
+ return BufferCtor.from(array).toString('base64');
+ }
+
+ let binary = '';
+ for (let i = 0; i < array.length; i += CHUNK_SIZE) {
+ const chunk = array.subarray(i, i + CHUNK_SIZE);
+ binary += String.fromCharCode(...chunk);
+ }
+ return btoa(binary);
}
export function base64ToUint8Array(base64: string) {
+ if (BufferCtor) {
+ return new Uint8Array(BufferCtor.from(base64, 'base64'));
+ }
+
const binaryString = atob(base64);
- const binaryArray = [...binaryString].map(function (char) {
- return char.charCodeAt(0);
- });
- return new Uint8Array(binaryArray);
+ const bytes = new Uint8Array(binaryString.length);
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ return bytes;
}
let authMethod:
diff --git a/packages/common/s3-compat/package.json b/packages/common/s3-compat/package.json
new file mode 100644
index 0000000000..a4aa447135
--- /dev/null
+++ b/packages/common/s3-compat/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@affine/s3-compat",
+ "private": true,
+ "type": "module",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "dependencies": {
+ "aws4": "^1.13.2",
+ "fast-xml-parser": "^5.3.4",
+ "s3mini": "^0.9.1"
+ },
+ "devDependencies": {
+ "@types/aws4": "^1.11.6",
+ "vitest": "^3.2.4"
+ },
+ "version": "0.26.0"
+}
diff --git a/packages/common/s3-compat/src/index.ts b/packages/common/s3-compat/src/index.ts
new file mode 100644
index 0000000000..1dc78d29a5
--- /dev/null
+++ b/packages/common/s3-compat/src/index.ts
@@ -0,0 +1,529 @@
+import { Buffer } from 'node:buffer';
+import { stringify as stringifyQuery } from 'node:querystring';
+import { Readable } from 'node:stream';
+
+import aws4 from 'aws4';
+import { XMLParser } from 'fast-xml-parser';
+import { S3mini, sanitizeETag } from 's3mini';
+
+export type S3CompatCredentials = {
+ accessKeyId: string;
+ secretAccessKey: string;
+ sessionToken?: string;
+};
+
+export type S3CompatConfig = {
+ endpoint: string;
+ region: string;
+ bucket: string;
+ forcePathStyle?: boolean;
+ requestTimeoutMs?: number;
+ minPartSize?: number;
+ presign?: {
+ expiresInSeconds: number;
+ signContentTypeForPut?: boolean;
+ };
+};
+
+export type PresignedResult = {
+ url: string;
+ headers?: Record;
+ expiresAt: Date;
+};
+
+export type ListPartItem = { partNumber: number; etag: string };
+
+export type ListObjectsItem = {
+ key: string;
+ lastModified: Date;
+ contentLength: number;
+};
+
+export interface S3CompatClient {
+ putObject(
+ key: string,
+ body: Blob | Buffer | Uint8Array | ReadableStream | Readable,
+ meta?: { contentType?: string; contentLength?: number }
+ ): Promise;
+ getObjectResponse(key: string): Promise;
+ headObject(key: string): Promise<
+ | {
+ contentType?: string;
+ contentLength?: number;
+ lastModified?: Date;
+ checksumCRC32?: string;
+ }
+ | undefined
+ >;
+ deleteObject(key: string): Promise;
+ listObjectsV2(prefix?: string): Promise;
+
+ createMultipartUpload(
+ key: string,
+ meta?: { contentType?: string }
+ ): Promise<{ uploadId: string }>;
+ uploadPart(
+ key: string,
+ uploadId: string,
+ partNumber: number,
+ body: Blob | Buffer | Uint8Array | ReadableStream | Readable,
+ meta?: { contentLength?: number }
+ ): Promise<{ etag: string }>;
+ listParts(key: string, uploadId: string): Promise;
+ completeMultipartUpload(
+ key: string,
+ uploadId: string,
+ parts: ListPartItem[]
+ ): Promise;
+ abortMultipartUpload(key: string, uploadId: string): Promise;
+
+ presignGetObject(key: string): Promise;
+ presignPutObject(
+ key: string,
+ meta?: { contentType?: string }
+ ): Promise;
+ presignUploadPart(
+ key: string,
+ uploadId: string,
+ partNumber: number
+ ): Promise;
+}
+
+export type ParsedListParts = {
+ parts: ListPartItem[];
+ isTruncated: boolean;
+ nextPartNumberMarker?: string;
+};
+
+const listPartsParser = new XMLParser({
+ ignoreAttributes: false,
+ parseTagValue: false,
+ trimValues: true,
+});
+
+function asArray(value: T | T[] | undefined): T[] {
+ if (!value) return [];
+ return Array.isArray(value) ? value : [value];
+}
+
+function toBoolean(value: unknown): boolean {
+ if (typeof value === 'boolean') return value;
+ if (typeof value === 'string') return value.toLowerCase() === 'true';
+ return false;
+}
+
+function joinPath(basePath: string, suffix: string) {
+ const trimmedBase = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
+ const trimmedSuffix = suffix.startsWith('/') ? suffix.slice(1) : suffix;
+ if (!trimmedBase) {
+ return `/${trimmedSuffix}`;
+ }
+ if (!trimmedSuffix) {
+ return trimmedBase;
+ }
+ return `${trimmedBase}/${trimmedSuffix}`;
+}
+
+function encodeKey(key: string) {
+ return key.split('/').map(encodeURIComponent).join('/');
+}
+
+function buildQuery(params: Record) {
+ const entries = Object.entries(params).filter(
+ ([, value]) => value !== undefined
+ );
+ if (entries.length === 0) return '';
+ return stringifyQuery(
+ Object.fromEntries(entries.map(([key, value]) => [key, String(value)]))
+ );
+}
+
+function detectErrorCode(xml: string): string | undefined {
+ const parsed = listPartsParser.parse(xml);
+ if (!parsed || typeof parsed !== 'object') return undefined;
+ const error = (parsed as any).Error;
+ if (!error || typeof error !== 'object') return undefined;
+ const code = error.Code;
+ return typeof code === 'string' ? code : undefined;
+}
+
+export function parseListPartsXml(xml: string): ParsedListParts {
+ const parsed = listPartsParser.parse(xml);
+ const root =
+ parsed?.ListPartsResult ??
+ parsed?.ListPartsResult?.ListPartsResult ??
+ parsed?.ListPartsResult;
+ const result = root && typeof root === 'object' ? root : parsed;
+ const partsNode = result?.Part;
+
+ const parts = asArray(partsNode)
+ .map((part: any) => {
+ const partNumber = Number(part?.PartNumber);
+ const etag =
+ typeof part?.ETag === 'string' ? sanitizeETag(part.ETag) : '';
+ if (!partNumber || !etag) return undefined;
+ return { partNumber, etag } satisfies ListPartItem;
+ })
+ .filter((part): part is ListPartItem => !!part);
+
+ const isTruncated = toBoolean(result?.IsTruncated);
+ const nextPartNumberMarker =
+ typeof result?.NextPartNumberMarker === 'string'
+ ? result?.NextPartNumberMarker
+ : result?.NextPartNumberMarker !== undefined
+ ? String(result?.NextPartNumberMarker)
+ : undefined;
+
+ return { parts, isTruncated, nextPartNumberMarker };
+}
+
+function buildEndpoint(config: S3CompatConfig) {
+ const url = new URL(config.endpoint);
+ if (config.forcePathStyle) {
+ const segments = url.pathname.split('/').filter(Boolean);
+ if (segments[0] !== config.bucket) {
+ url.pathname = joinPath(url.pathname, config.bucket);
+ }
+ return url;
+ }
+
+ const pathSegments = url.pathname.split('/').filter(Boolean);
+ const hostHasBucket = url.hostname.startsWith(`${config.bucket}.`);
+ const pathHasBucket = pathSegments[0] === config.bucket;
+ if (!hostHasBucket && !pathHasBucket) {
+ url.hostname = `${config.bucket}.${url.hostname}`;
+ }
+ return url;
+}
+
+function shouldUseDuplex(init: RequestInit | undefined) {
+ if (!init?.body) return false;
+ if (typeof init.body === 'string') return false;
+ if (init.body instanceof ArrayBuffer) return false;
+ if (init.body instanceof Uint8Array) return false;
+ if (typeof Blob !== 'undefined' && init.body instanceof Blob) return false;
+ return true;
+}
+
+export class S3Compat implements S3CompatClient {
+ private readonly client: S3mini;
+ private readonly endpoint: URL;
+ private readonly basePath: string;
+ private readonly region: string;
+ private readonly credentials: S3CompatCredentials;
+ private readonly presignConfig: {
+ expiresInSeconds: number;
+ signContentTypeForPut: boolean;
+ };
+ private readonly fetchImpl: typeof fetch;
+
+ constructor(config: S3CompatConfig, credentials: S3CompatCredentials) {
+ this.endpoint = buildEndpoint(config);
+ this.basePath =
+ this.endpoint.pathname === '/' ? '' : this.endpoint.pathname;
+ this.region = config.region;
+ this.credentials = credentials;
+ this.presignConfig = {
+ expiresInSeconds: config.presign?.expiresInSeconds ?? 60 * 60,
+ signContentTypeForPut: config.presign?.signContentTypeForPut ?? true,
+ };
+
+ const fetchImpl = globalThis.fetch.bind(globalThis);
+ this.fetchImpl = (input, init) => {
+ if (shouldUseDuplex(init)) {
+ return fetchImpl(input, { ...init, duplex: 'half' } as RequestInit);
+ }
+ return fetchImpl(input, init);
+ };
+
+ this.client = new S3mini({
+ accessKeyId: credentials.accessKeyId,
+ secretAccessKey: credentials.secretAccessKey,
+ endpoint: this.endpoint.toString(),
+ region: config.region,
+ requestAbortTimeout: config.requestTimeoutMs,
+ minPartSize: config.minPartSize,
+ fetch: this.fetchImpl,
+ });
+ }
+
+ static fromConfig(config: S3CompatConfig, credentials: S3CompatCredentials) {
+ return new S3Compat(config, credentials);
+ }
+
+ private buildObjectPath(key: string) {
+ const encodedKey = encodeKey(key);
+ return joinPath(this.basePath, encodedKey);
+ }
+
+ private async signedFetch(
+ method: string,
+ key: string,
+ query?: Record,
+ headers?: Record
+ ) {
+ const path = this.buildObjectPath(key);
+ const queryString = query ? buildQuery(query) : '';
+ const requestPath = queryString ? `${path}?${queryString}` : path;
+ const signed = aws4.sign(
+ {
+ method,
+ service: 's3',
+ region: this.region,
+ host: this.endpoint.host,
+ path: requestPath,
+ headers: headers ?? {},
+ },
+ this.credentials
+ );
+
+ const signedHeaders = Object.fromEntries(
+ Object.entries(signed.headers ?? {}).map(([key, value]) => [
+ key,
+ String(value),
+ ])
+ );
+
+ const url = `${this.endpoint.origin}${signed.path}`;
+ return this.fetchImpl(url, { method, headers: signedHeaders });
+ }
+
+ private presign(
+ method: string,
+ key: string,
+ query?: Record,
+ headers?: Record
+ ): PresignedResult {
+ const expiresInSeconds = this.presignConfig.expiresInSeconds;
+ const path = this.buildObjectPath(key);
+ const queryString = buildQuery({
+ ...(query ?? {}),
+ 'X-Amz-Expires': expiresInSeconds,
+ });
+ const requestPath = queryString ? `${path}?${queryString}` : path;
+ const signed = aws4.sign(
+ {
+ method,
+ service: 's3',
+ region: this.region,
+ host: this.endpoint.host,
+ path: requestPath,
+ headers: headers ?? {},
+ signQuery: true,
+ },
+ this.credentials
+ );
+
+ return {
+ url: `${this.endpoint.origin}${signed.path}`,
+ headers,
+ expiresAt: new Date(Date.now() + expiresInSeconds * 1000),
+ };
+ }
+
+ async putObject(
+ key: string,
+ body: Blob | Buffer | Uint8Array | ReadableStream | Readable,
+ meta?: { contentType?: string; contentLength?: number }
+ ): Promise {
+ const res = await this.client.putObject(
+ key,
+ body as any,
+ meta?.contentType,
+ undefined,
+ undefined,
+ meta?.contentLength
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to put object: ${res.status}`);
+ }
+ }
+
+ async getObjectResponse(key: string) {
+ return this.client.getObjectResponse(key);
+ }
+
+ async headObject(key: string) {
+ const res = await this.signedFetch('HEAD', key);
+ if (res.status === 404) {
+ return undefined;
+ }
+
+ if (!res.ok) {
+ const errorBody = await res.text();
+ const errorCode = detectErrorCode(errorBody);
+ if (errorCode === 'NoSuchKey' || errorCode === 'NotFound') {
+ return undefined;
+ }
+ throw new Error(`Failed to head object: ${res.status}`);
+ }
+
+ const contentLengthHeader = res.headers.get('content-length');
+ const contentLength = contentLengthHeader
+ ? Number(contentLengthHeader)
+ : undefined;
+ const contentType = res.headers.get('content-type') ?? undefined;
+ const lastModifiedHeader = res.headers.get('last-modified');
+ const lastModified = lastModifiedHeader
+ ? new Date(lastModifiedHeader)
+ : undefined;
+ const checksumCRC32 = res.headers.get('x-amz-checksum-crc32') ?? undefined;
+
+ return {
+ contentType,
+ contentLength,
+ lastModified,
+ checksumCRC32,
+ };
+ }
+
+ async deleteObject(key: string): Promise {
+ await this.client.deleteObject(key);
+ }
+
+ async listObjectsV2(prefix?: string): Promise {
+ const results: ListObjectsItem[] = [];
+ let continuationToken: string | undefined;
+ do {
+ const page = await this.client.listObjectsPaged(
+ '/',
+ prefix ?? '',
+ 1000,
+ continuationToken
+ );
+ if (!page || !page.objects) {
+ break;
+ }
+ for (const item of page.objects) {
+ results.push({
+ key: item.Key,
+ lastModified: item.LastModified,
+ contentLength: item.Size,
+ });
+ }
+ continuationToken = page.nextContinuationToken;
+ } while (continuationToken);
+
+ return results;
+ }
+
+ async createMultipartUpload(
+ key: string,
+ meta?: { contentType?: string }
+ ): Promise<{ uploadId: string }> {
+ const uploadId = await this.client.getMultipartUploadId(
+ key,
+ meta?.contentType
+ );
+ return { uploadId };
+ }
+
+ async uploadPart(
+ key: string,
+ uploadId: string,
+ partNumber: number,
+ body: Blob | Buffer | Uint8Array | ReadableStream | Readable,
+ meta?: { contentLength?: number }
+ ): Promise<{ etag: string }> {
+ const additionalHeaders = meta?.contentLength
+ ? { 'Content-Length': String(meta.contentLength) }
+ : undefined;
+ const part = await this.client.uploadPart(
+ key,
+ uploadId,
+ body as any,
+ partNumber,
+ {},
+ undefined,
+ additionalHeaders
+ );
+ return { etag: part.etag };
+ }
+
+ async listParts(
+ key: string,
+ uploadId: string
+ ): Promise {
+ const parts: ListPartItem[] = [];
+ let partNumberMarker: string | undefined;
+
+ while (true) {
+ const res = await this.signedFetch('GET', key, {
+ uploadId,
+ 'part-number-marker': partNumberMarker,
+ });
+
+ if (res.status === 404) {
+ return undefined;
+ }
+
+ const body = await res.text();
+ if (!res.ok) {
+ const errorCode = detectErrorCode(body);
+ if (errorCode === 'NoSuchUpload' || errorCode === 'NotFound') {
+ return undefined;
+ }
+ throw new Error(`Failed to list multipart upload parts: ${res.status}`);
+ }
+
+ const parsed = parseListPartsXml(body);
+ parts.push(...parsed.parts);
+
+ if (!parsed.isTruncated || !parsed.nextPartNumberMarker) {
+ break;
+ }
+
+ partNumberMarker = parsed.nextPartNumberMarker;
+ }
+
+ return parts;
+ }
+
+ async completeMultipartUpload(
+ key: string,
+ uploadId: string,
+ parts: ListPartItem[]
+ ): Promise {
+ await this.client.completeMultipartUpload(key, uploadId, parts);
+ }
+
+ async abortMultipartUpload(key: string, uploadId: string): Promise {
+ await this.client.abortMultipartUpload(key, uploadId);
+ }
+
+ async presignGetObject(key: string): Promise {
+ return this.presign('GET', key);
+ }
+
+ async presignPutObject(
+ key: string,
+ meta?: { contentType?: string }
+ ): Promise {
+ const contentType = meta?.contentType ?? 'application/octet-stream';
+ const signContentType = this.presignConfig.signContentTypeForPut ?? true;
+ const headers = signContentType
+ ? { 'Content-Type': contentType }
+ : undefined;
+ const result = this.presign('PUT', key, undefined, headers);
+
+ return {
+ ...result,
+ headers: headers ? { 'Content-Type': contentType } : undefined,
+ };
+ }
+
+ async presignUploadPart(
+ key: string,
+ uploadId: string,
+ partNumber: number
+ ): Promise {
+ return this.presign('PUT', key, { uploadId, partNumber });
+ }
+}
+
+export function createS3CompatClient(
+ config: S3CompatConfig,
+ credentials: S3CompatCredentials
+) {
+ return new S3Compat(config, credentials);
+}
diff --git a/packages/common/s3-compat/tsconfig.json b/packages/common/s3-compat/tsconfig.json
new file mode 100644
index 0000000000..e64f5e6230
--- /dev/null
+++ b/packages/common/s3-compat/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.node.json",
+ "compilerOptions": {
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
+ },
+ "include": ["./src"],
+ "references": []
+}
diff --git a/packages/frontend/admin/src/modules/settings/config.ts b/packages/frontend/admin/src/modules/settings/config.ts
index 1a528daa8c..cfbe1183de 100644
--- a/packages/frontend/admin/src/modules/settings/config.ts
+++ b/packages/frontend/admin/src/modules/settings/config.ts
@@ -112,7 +112,7 @@ export const KNOWN_CONFIG_GROUPS = [
key: 'blob.storage',
sub: 'config',
type: 'JSON',
- desc: 'The config passed directly to the storage provider(e.g. aws-sdk)',
+ desc: 'The S3 compatible config for the storage provider (endpoint/region/credentials).',
},
{
key: 'avatar.storage',
@@ -131,7 +131,7 @@ export const KNOWN_CONFIG_GROUPS = [
key: 'avatar.storage',
sub: 'config',
type: 'JSON',
- desc: 'The config passed directly to the storage provider(e.g. aws-sdk)',
+ desc: 'The S3 compatible config for the storage provider (endpoint/region/credentials).',
},
{
key: 'avatar.publicPath',
@@ -175,7 +175,7 @@ export const KNOWN_CONFIG_GROUPS = [
key: 'storage',
sub: 'config',
type: 'JSON',
- desc: 'The config passed directly to the storage provider(e.g. aws-sdk)',
+ desc: 'The S3 compatible config for the storage provider (endpoint/region/credentials).',
},
],
} as ConfigGroup<'copilot'>,
diff --git a/packages/frontend/core/src/modules/workspace-engine/utils/__tests__/base64.spec.ts b/packages/frontend/core/src/modules/workspace-engine/utils/__tests__/base64.spec.ts
new file mode 100644
index 0000000000..ad13151d97
--- /dev/null
+++ b/packages/frontend/core/src/modules/workspace-engine/utils/__tests__/base64.spec.ts
@@ -0,0 +1,20 @@
+import { describe, expect, test } from 'vitest';
+
+import { base64ToUint8Array, uint8ArrayToBase64 } from '../base64';
+
+function makeSample(size: number) {
+ const data = new Uint8Array(size);
+ for (let i = 0; i < size; i++) {
+ data[i] = (i * 13) % 251;
+ }
+ return data;
+}
+
+describe('base64 helpers', () => {
+ test('roundtrip preserves data', async () => {
+ const input = makeSample(2048);
+ const encoded = await uint8ArrayToBase64(input);
+ const decoded = base64ToUint8Array(encoded);
+ expect(decoded).toEqual(input);
+ });
+});
diff --git a/packages/frontend/core/src/modules/workspace-engine/utils/base64.ts b/packages/frontend/core/src/modules/workspace-engine/utils/base64.ts
index 7e2ffd3375..ab0c59cfdb 100644
--- a/packages/frontend/core/src/modules/workspace-engine/utils/base64.ts
+++ b/packages/frontend/core/src/modules/workspace-engine/utils/base64.ts
@@ -1,28 +1,37 @@
-export function uint8ArrayToBase64(array: Uint8Array): Promise {
- return new Promise(resolve => {
- // Create a blob from the Uint8Array
- const blob = new Blob([array]);
+type BufferConstructorLike = {
+ from(
+ data: Uint8Array | string,
+ encoding?: string
+ ): Uint8Array & {
+ toString(encoding: string): string;
+ };
+};
- const reader = new FileReader();
- reader.onload = function () {
- const dataUrl = reader.result as string | null;
- if (!dataUrl) {
- resolve('');
- return;
- }
- // The result includes the `data:` URL prefix and the MIME type. We only want the Base64 data
- const base64 = dataUrl.split(',')[1];
- resolve(base64);
- };
+const BufferCtor = (globalThis as { Buffer?: BufferConstructorLike }).Buffer;
+const CHUNK_SIZE = 0x8000;
- reader.readAsDataURL(blob);
- });
+export async function uint8ArrayToBase64(array: Uint8Array): Promise {
+ if (BufferCtor) {
+ return BufferCtor.from(array).toString('base64');
+ }
+
+ let binary = '';
+ for (let i = 0; i < array.length; i += CHUNK_SIZE) {
+ const chunk = array.subarray(i, i + CHUNK_SIZE);
+ binary += String.fromCharCode(...chunk);
+ }
+ return btoa(binary);
}
export function base64ToUint8Array(base64: string) {
+ if (BufferCtor) {
+ return new Uint8Array(BufferCtor.from(base64, 'base64'));
+ }
+
const binaryString = atob(base64);
- const binaryArray = [...binaryString].map(function (char) {
- return char.charCodeAt(0);
- });
- return new Uint8Array(binaryArray);
+ const bytes = new Uint8Array(binaryString.length);
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ return bytes;
}
diff --git a/packages/frontend/mobile-native/Cargo.toml b/packages/frontend/mobile-native/Cargo.toml
index bbd12cdffe..ef99a2d354 100644
--- a/packages/frontend/mobile-native/Cargo.toml
+++ b/packages/frontend/mobile-native/Cargo.toml
@@ -12,6 +12,7 @@ name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"
[features]
+default = ["use-as-lib"]
use-as-lib = ["affine_nbstore/use-as-lib"]
[dependencies]
diff --git a/packages/frontend/mobile-native/src/lib.rs b/packages/frontend/mobile-native/src/lib.rs
index 9b4e70be54..84212c2670 100644
--- a/packages/frontend/mobile-native/src/lib.rs
+++ b/packages/frontend/mobile-native/src/lib.rs
@@ -89,11 +89,57 @@ impl TryFrom for affine_nbstore::DocUpdate {
timestamp: chrono::DateTime::::from_timestamp_millis(update.timestamp)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
- bin: update.bin.into(),
+ bin: Into::::into(
+ base64_simd::STANDARD
+ .decode_to_vec(update.bin)
+ .map_err(|e| UniffiError::Base64DecodingError(e.to_string()))?,
+ ),
})
}
}
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn doc_update_roundtrip_base64() {
+ let timestamp = chrono::DateTime::::from_timestamp_millis(1_700_000_000_000)
+ .unwrap()
+ .naive_utc();
+ let original = affine_nbstore::DocUpdate {
+ doc_id: "doc-1".to_string(),
+ timestamp,
+ bin: vec![1, 2, 3, 4, 5],
+ };
+
+ let encoded: DocUpdate = original.into();
+ let decoded = affine_nbstore::DocUpdate::try_from(encoded).unwrap();
+
+ assert_eq!(decoded.doc_id, "doc-1");
+ assert_eq!(decoded.timestamp, timestamp);
+ assert_eq!(decoded.bin, vec![1, 2, 3, 4, 5]);
+ }
+
+ #[test]
+ fn doc_update_rejects_invalid_base64() {
+ let update = DocUpdate {
+ doc_id: "doc-2".to_string(),
+ timestamp: 0,
+ bin: "not-base64!!".to_string(),
+ };
+
+ let err = match affine_nbstore::DocUpdate::try_from(update) {
+ Ok(_) => panic!("expected base64 decode error"),
+ Err(err) => err,
+ };
+ match err {
+ UniffiError::Base64DecodingError(_) => {}
+ other => panic!("unexpected error: {other:?}"),
+ }
+ }
+}
+
#[derive(uniffi::Record)]
pub struct DocClock {
pub doc_id: String,
diff --git a/packages/frontend/native/Cargo.toml b/packages/frontend/native/Cargo.toml
index c7bb670896..6656a06ec2 100644
--- a/packages/frontend/native/Cargo.toml
+++ b/packages/frontend/native/Cargo.toml
@@ -9,7 +9,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
affine_common = { workspace = true, features = ["hashcash"] }
affine_media_capture = { path = "./media_capture" }
-affine_nbstore = { path = "./nbstore" }
+affine_nbstore = { workspace = true, features = ["napi"] }
affine_sqlite_v1 = { path = "./sqlite_v1" }
napi = { workspace = true }
napi-derive = { workspace = true }
@@ -25,6 +25,12 @@ sqlx = { workspace = true, default-features = false, features = [
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
+[target.'cfg(not(target_os = "linux"))'.dependencies]
+mimalloc = { workspace = true }
+
+[target.'cfg(all(target_os = "linux", not(target_arch = "arm")))'.dependencies]
+mimalloc = { workspace = true, features = ["local_dynamic_tls"] }
+
[dev-dependencies]
chrono = { workspace = true }
serde_json = { workspace = true }
diff --git a/packages/frontend/native/index.d.ts b/packages/frontend/native/index.d.ts
index 99ebd4f4c5..e85784fa3b 100644
--- a/packages/frontend/native/index.d.ts
+++ b/packages/frontend/native/index.d.ts
@@ -19,10 +19,10 @@ export declare class ApplicationStateChangedSubscriber {
}
export declare class AudioCaptureSession {
+ stop(): void
get sampleRate(): number
get channels(): number
get actualSampleRate(): number
- stop(): void
}
export declare class ShareableContent {
@@ -31,9 +31,9 @@ export declare class ShareableContent {
constructor()
static applications(): Array
static applicationWithProcessId(processId: number): ApplicationInfo | null
+ static isUsingMicrophone(processId: number): boolean
static tapAudio(processId: number, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioCaptureSession
static tapGlobalAudio(excludedProcesses: Array | undefined | null, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioCaptureSession
- static isUsingMicrophone(processId: number): boolean
}
export declare function decodeAudio(buf: Uint8Array, destSampleRate?: number | undefined | null, filename?: string | undefined | null, signal?: AbortSignal | undefined | null): Promise
diff --git a/packages/frontend/native/nbstore/Cargo.toml b/packages/frontend/native/nbstore/Cargo.toml
index 5ef99e5a94..f5ac3550c7 100644
--- a/packages/frontend/native/nbstore/Cargo.toml
+++ b/packages/frontend/native/nbstore/Cargo.toml
@@ -7,6 +7,8 @@ version = "0.0.0"
crate-type = ["cdylib", "rlib"]
[features]
+default = []
+napi = ["affine_common/napi"]
use-as-lib = ["napi-derive/noop", "napi/noop"]
[dependencies]
diff --git a/packages/frontend/native/nbstore/src/lib.rs b/packages/frontend/native/nbstore/src/lib.rs
index 89fc1f6f60..80aea4b7dd 100644
--- a/packages/frontend/native/nbstore/src/lib.rs
+++ b/packages/frontend/native/nbstore/src/lib.rs
@@ -8,6 +8,8 @@ pub mod indexer_sync;
pub mod pool;
pub mod storage;
+#[cfg(not(feature = "use-as-lib"))]
+use affine_common::napi_utils::to_napi_error;
use chrono::NaiveDateTime;
use napi::bindgen_prelude::*;
use napi_derive::napi;
@@ -23,7 +25,7 @@ type Result = napi::Result;
#[cfg(not(feature = "use-as-lib"))]
impl From for napi::Error {
fn from(err: error::Error) -> Self {
- napi::Error::new(napi::Status::GenericFailure, err.to_string())
+ to_napi_error(err, napi::Status::GenericFailure)
}
}
@@ -491,3 +493,15 @@ impl DocStorage {
Ok(())
}
}
+
+#[cfg(all(test, not(feature = "use-as-lib")))]
+mod tests {
+ use super::error;
+
+ #[test]
+ fn napi_error_mapping_preserves_reason() {
+ let err: napi::Error = error::Error::InvalidOperation.into();
+ assert_eq!(err.status, napi::Status::GenericFailure);
+ assert!(err.reason.contains("Invalid operation"));
+ }
+}
diff --git a/packages/frontend/native/src/hashcash.rs b/packages/frontend/native/src/hashcash.rs
index bda991081f..27262f2dcf 100644
--- a/packages/frontend/native/src/hashcash.rs
+++ b/packages/frontend/native/src/hashcash.rs
@@ -64,3 +64,27 @@ impl Task for AsyncMintChallengeResponse {
pub fn mint_challenge_response(resource: String, bits: Option) -> AsyncTask {
AsyncTask::new(AsyncMintChallengeResponse { bits, resource })
}
+
+#[cfg(test)]
+mod tests {
+ use napi::Task;
+
+ use super::*;
+
+ #[test]
+ fn hashcash_roundtrip() {
+ let resource = "test-resource".to_string();
+ let mut mint = AsyncMintChallengeResponse {
+ bits: Some(8),
+ resource: resource.clone(),
+ };
+ let stamp = mint.compute().unwrap();
+
+ let mut verify = AsyncVerifyChallengeResponse {
+ response: stamp,
+ bits: 8,
+ resource,
+ };
+ assert!(verify.compute().unwrap());
+ }
+}
diff --git a/packages/frontend/native/src/lib.rs b/packages/frontend/native/src/lib.rs
index b17678a551..63582db8fb 100644
--- a/packages/frontend/native/src/lib.rs
+++ b/packages/frontend/native/src/lib.rs
@@ -1,5 +1,9 @@
pub mod hashcash;
+#[cfg(not(target_arch = "arm"))]
+#[global_allocator]
+static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
+
#[allow(unused_imports)]
pub use affine_media_capture::*;
pub use affine_nbstore::*;
diff --git a/tests/blocksuite/e2e/edgeless/clipboard.spec.ts b/tests/blocksuite/e2e/edgeless/clipboard.spec.ts
index 52a6a7f3db..f87ece35c8 100644
--- a/tests/blocksuite/e2e/edgeless/clipboard.spec.ts
+++ b/tests/blocksuite/e2e/edgeless/clipboard.spec.ts
@@ -173,6 +173,21 @@ test.describe('frame clipboard', () => {
});
test.describe('pasting URLs', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.route(
+ 'https://affine-worker.toeverything.workers.dev/api/worker/link-preview',
+ async route => {
+ await route.fulfill({
+ json: {},
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ },
+ });
+ }
+ );
+ });
+
test('pasting github pr url', async ({ page }) => {
await commonSetup(page);
await waitNextFrame(page);
diff --git a/tools/cli/package.json b/tools/cli/package.json
index 600081a041..f03a666d2a 100644
--- a/tools/cli/package.json
+++ b/tools/cli/package.json
@@ -16,7 +16,7 @@
},
"dependencies": {
"@affine-tools/utils": "workspace:*",
- "@aws-sdk/client-s3": "^3.948.0",
+ "@affine/s3-compat": "workspace:*",
"@napi-rs/simple-git": "^0.1.22",
"@perfsee/webpack": "^1.13.0",
"@sentry/webpack-plugin": "^3.0.0",
diff --git a/tools/cli/src/webpack/s3-plugin.ts b/tools/cli/src/webpack/s3-plugin.ts
index 00de2355c6..4e5459d7ac 100644
--- a/tools/cli/src/webpack/s3-plugin.ts
+++ b/tools/cli/src/webpack/s3-plugin.ts
@@ -1,8 +1,7 @@
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
-import type { PutObjectCommandInput } from '@aws-sdk/client-s3';
-import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
+import { createS3CompatClient } from '@affine/s3-compat';
import { lookup } from 'mime-types';
import type { Compiler, WebpackPluginInstance } from 'webpack';
@@ -11,16 +10,18 @@ export const R2_BUCKET =
(process.env.BUILD_TYPE === 'canary' ? 'assets-dev' : 'assets-prod');
export class WebpackS3Plugin implements WebpackPluginInstance {
- private readonly s3 = new S3Client({
- region: 'auto',
- endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
- credentials: {
+ private readonly s3 = createS3CompatClient(
+ {
+ region: 'auto',
+ bucket: R2_BUCKET,
+ forcePathStyle: true,
+ endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
+ },
+ {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
- },
- requestChecksumCalculation: 'WHEN_REQUIRED',
- responseChecksumValidation: 'WHEN_REQUIRED',
- });
+ }
+ );
apply(compiler: Compiler) {
compiler.hooks.assetEmitted.tapPromise(
@@ -31,16 +32,11 @@ export class WebpackS3Plugin implements WebpackPluginInstance {
}
const assetPath = join(outputPath, asset);
const assetSource = await readFile(assetPath);
- const putObjectCommandOptions: PutObjectCommandInput = {
- Body: assetSource,
- Bucket: R2_BUCKET,
- Key: asset,
- };
- const contentType = lookup(asset);
- if (contentType) {
- putObjectCommandOptions.ContentType = contentType;
- }
- await this.s3.send(new PutObjectCommand(putObjectCommandOptions));
+ const contentType = lookup(asset) || undefined;
+ await this.s3.putObject(asset, assetSource, {
+ contentType,
+ contentLength: assetSource.byteLength,
+ });
}
);
}
diff --git a/tools/cli/tsconfig.json b/tools/cli/tsconfig.json
index ab3bade10a..006bdb5791 100644
--- a/tools/cli/tsconfig.json
+++ b/tools/cli/tsconfig.json
@@ -6,5 +6,8 @@
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["./src"],
- "references": [{ "path": "../utils" }]
+ "references": [
+ { "path": "../utils" },
+ { "path": "../../packages/common/s3-compat" }
+ ]
}
diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts
index 3ecd85844e..4168f3b7cc 100644
--- a/tools/utils/src/workspace.gen.ts
+++ b/tools/utils/src/workspace.gen.ts
@@ -1166,6 +1166,7 @@ export const PackageList = [
location: 'packages/backend/server',
name: '@affine/server',
workspaceDependencies: [
+ 'packages/common/s3-compat',
'packages/backend/native',
'tools/cli',
'tools/utils',
@@ -1222,6 +1223,11 @@ export const PackageList = [
name: '@affine/reader',
workspaceDependencies: ['blocksuite/affine/all'],
},
+ {
+ location: 'packages/common/s3-compat',
+ name: '@affine/s3-compat',
+ workspaceDependencies: [],
+ },
{
location: 'packages/frontend/admin',
name: '@affine/admin',
@@ -1462,7 +1468,7 @@ export const PackageList = [
{
location: 'tools/cli',
name: '@affine-tools/cli',
- workspaceDependencies: ['tools/utils'],
+ workspaceDependencies: ['tools/utils', 'packages/common/s3-compat'],
},
{
location: 'tools/commitlint',
@@ -1580,6 +1586,7 @@ export type PackageName =
| '@toeverything/infra'
| '@affine/nbstore'
| '@affine/reader'
+ | '@affine/s3-compat'
| '@affine/admin'
| '@affine/android'
| '@affine/electron'
diff --git a/tsconfig.json b/tsconfig.json
index 37dec242cb..fe167fcb0d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -132,6 +132,7 @@
{ "path": "./packages/common/infra" },
{ "path": "./packages/common/nbstore" },
{ "path": "./packages/common/reader" },
+ { "path": "./packages/common/s3-compat" },
{ "path": "./packages/frontend/admin" },
{ "path": "./packages/frontend/apps/android" },
{ "path": "./packages/frontend/apps/electron" },
diff --git a/yarn.lock b/yarn.lock
index 7766bfd3c0..74ddf61839 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -116,7 +116,7 @@ __metadata:
resolution: "@affine-tools/cli@workspace:tools/cli"
dependencies:
"@affine-tools/utils": "workspace:*"
- "@aws-sdk/client-s3": "npm:^3.948.0"
+ "@affine/s3-compat": "workspace:*"
"@napi-rs/simple-git": "npm:^0.1.22"
"@perfsee/webpack": "npm:^1.13.0"
"@sentry/webpack-plugin": "npm:^3.0.0"
@@ -926,6 +926,18 @@ __metadata:
languageName: unknown
linkType: soft
+"@affine/s3-compat@workspace:*, @affine/s3-compat@workspace:packages/common/s3-compat":
+ version: 0.0.0-use.local
+ resolution: "@affine/s3-compat@workspace:packages/common/s3-compat"
+ dependencies:
+ "@types/aws4": "npm:^1.11.6"
+ aws4: "npm:^1.13.2"
+ fast-xml-parser: "npm:^5.3.4"
+ s3mini: "npm:^0.9.1"
+ vitest: "npm:^3.2.4"
+ languageName: unknown
+ linkType: soft
+
"@affine/server-native@workspace:*, @affine/server-native@workspace:packages/backend/native":
version: 0.0.0-use.local
resolution: "@affine/server-native@workspace:packages/backend/native"
@@ -942,6 +954,7 @@ __metadata:
"@affine-tools/cli": "workspace:*"
"@affine-tools/utils": "workspace:*"
"@affine/graphql": "workspace:*"
+ "@affine/s3-compat": "workspace:*"
"@affine/server-native": "workspace:*"
"@ai-sdk/anthropic": "npm:^2.0.54"
"@ai-sdk/google": "npm:^2.0.45"
@@ -950,8 +963,6 @@ __metadata:
"@ai-sdk/openai-compatible": "npm:^1.0.28"
"@ai-sdk/perplexity": "npm:^2.0.21"
"@apollo/server": "npm:^4.12.2"
- "@aws-sdk/client-s3": "npm:^3.948.0"
- "@aws-sdk/s3-request-presigner": "npm:^3.948.0"
"@faker-js/faker": "npm:^10.1.0"
"@fal-ai/serverless-client": "npm:^0.15.0"
"@google-cloud/opentelemetry-cloud-trace-exporter": "npm:^3.0.0"
@@ -1551,711 +1562,6 @@ __metadata:
languageName: node
linkType: hard
-"@aws-crypto/crc32@npm:5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/crc32@npm:5.2.0"
- dependencies:
- "@aws-crypto/util": "npm:^5.2.0"
- "@aws-sdk/types": "npm:^3.222.0"
- tslib: "npm:^2.6.2"
- checksum: 10/1b0a56ad4cb44c9512d8b1668dcf9306ab541d3a73829f435ca97abaec8d56f3db953db03ad0d0698754fea16fcd803d11fa42e0889bc7b803c6a030b04c63de
- languageName: node
- linkType: hard
-
-"@aws-crypto/crc32c@npm:5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/crc32c@npm:5.2.0"
- dependencies:
- "@aws-crypto/util": "npm:^5.2.0"
- "@aws-sdk/types": "npm:^3.222.0"
- tslib: "npm:^2.6.2"
- checksum: 10/08bd1db17d7c772fa6e34b38a360ce77ad041164743113eefa8343c2af917a419697daf090c5854129ef19f3a9673ed1fd8446e03eb32c8ed52d2cc409b0dee7
- languageName: node
- linkType: hard
-
-"@aws-crypto/sha1-browser@npm:5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/sha1-browser@npm:5.2.0"
- dependencies:
- "@aws-crypto/supports-web-crypto": "npm:^5.2.0"
- "@aws-crypto/util": "npm:^5.2.0"
- "@aws-sdk/types": "npm:^3.222.0"
- "@aws-sdk/util-locate-window": "npm:^3.0.0"
- "@smithy/util-utf8": "npm:^2.0.0"
- tslib: "npm:^2.6.2"
- checksum: 10/239f4c59cce9abd33c01117b10553fbef868a063e74faf17edb798c250d759a2578841efa2837e5e51854f52ef57dbc40780b073cae20f89ebed6a8cc7fa06f1
- languageName: node
- linkType: hard
-
-"@aws-crypto/sha256-browser@npm:5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/sha256-browser@npm:5.2.0"
- dependencies:
- "@aws-crypto/sha256-js": "npm:^5.2.0"
- "@aws-crypto/supports-web-crypto": "npm:^5.2.0"
- "@aws-crypto/util": "npm:^5.2.0"
- "@aws-sdk/types": "npm:^3.222.0"
- "@aws-sdk/util-locate-window": "npm:^3.0.0"
- "@smithy/util-utf8": "npm:^2.0.0"
- tslib: "npm:^2.6.2"
- checksum: 10/2b1b701ca6caa876333b4eb2b96e5187d71ebb51ebf8e2d632690dbcdedeff038202d23adcc97e023437ed42bb1963b7b463e343687edf0635fd4b98b2edad1a
- languageName: node
- linkType: hard
-
-"@aws-crypto/sha256-js@npm:5.2.0, @aws-crypto/sha256-js@npm:^5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/sha256-js@npm:5.2.0"
- dependencies:
- "@aws-crypto/util": "npm:^5.2.0"
- "@aws-sdk/types": "npm:^3.222.0"
- tslib: "npm:^2.6.2"
- checksum: 10/f46aace7b873c615be4e787ab0efd0148ef7de48f9f12c7d043e05c52e52b75bb0bf6dbcb9b2852d940d7724fab7b6d5ff1469160a3dd024efe7a68b5f70df8c
- languageName: node
- linkType: hard
-
-"@aws-crypto/supports-web-crypto@npm:^5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/supports-web-crypto@npm:5.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/6ed0c7e17f4f6663d057630805c45edb35d5693380c24ab52d4c453ece303c6c8a6ade9ee93c97dda77d9f6cae376ffbb44467057161c513dffa3422250edaf5
- languageName: node
- linkType: hard
-
-"@aws-crypto/util@npm:5.2.0, @aws-crypto/util@npm:^5.2.0":
- version: 5.2.0
- resolution: "@aws-crypto/util@npm:5.2.0"
- dependencies:
- "@aws-sdk/types": "npm:^3.222.0"
- "@smithy/util-utf8": "npm:^2.0.0"
- tslib: "npm:^2.6.2"
- checksum: 10/f80a174c404e1ad4364741c942f440e75f834c08278fa754349fe23a6edc679d480ea9ced5820774aee58091ed270067022d8059ecf1a7ef452d58134ac7e9e1
- languageName: node
- linkType: hard
-
-"@aws-sdk/client-s3@npm:^3.948.0":
- version: 3.980.0
- resolution: "@aws-sdk/client-s3@npm:3.980.0"
- dependencies:
- "@aws-crypto/sha1-browser": "npm:5.2.0"
- "@aws-crypto/sha256-browser": "npm:5.2.0"
- "@aws-crypto/sha256-js": "npm:5.2.0"
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/credential-provider-node": "npm:^3.972.4"
- "@aws-sdk/middleware-bucket-endpoint": "npm:^3.972.3"
- "@aws-sdk/middleware-expect-continue": "npm:^3.972.3"
- "@aws-sdk/middleware-flexible-checksums": "npm:^3.972.3"
- "@aws-sdk/middleware-host-header": "npm:^3.972.3"
- "@aws-sdk/middleware-location-constraint": "npm:^3.972.3"
- "@aws-sdk/middleware-logger": "npm:^3.972.3"
- "@aws-sdk/middleware-recursion-detection": "npm:^3.972.3"
- "@aws-sdk/middleware-sdk-s3": "npm:^3.972.5"
- "@aws-sdk/middleware-ssec": "npm:^3.972.3"
- "@aws-sdk/middleware-user-agent": "npm:^3.972.5"
- "@aws-sdk/region-config-resolver": "npm:^3.972.3"
- "@aws-sdk/signature-v4-multi-region": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-endpoints": "npm:3.980.0"
- "@aws-sdk/util-user-agent-browser": "npm:^3.972.3"
- "@aws-sdk/util-user-agent-node": "npm:^3.972.3"
- "@smithy/config-resolver": "npm:^4.4.6"
- "@smithy/core": "npm:^3.22.0"
- "@smithy/eventstream-serde-browser": "npm:^4.2.8"
- "@smithy/eventstream-serde-config-resolver": "npm:^4.3.8"
- "@smithy/eventstream-serde-node": "npm:^4.2.8"
- "@smithy/fetch-http-handler": "npm:^5.3.9"
- "@smithy/hash-blob-browser": "npm:^4.2.9"
- "@smithy/hash-node": "npm:^4.2.8"
- "@smithy/hash-stream-node": "npm:^4.2.8"
- "@smithy/invalid-dependency": "npm:^4.2.8"
- "@smithy/md5-js": "npm:^4.2.8"
- "@smithy/middleware-content-length": "npm:^4.2.8"
- "@smithy/middleware-endpoint": "npm:^4.4.12"
- "@smithy/middleware-retry": "npm:^4.4.29"
- "@smithy/middleware-serde": "npm:^4.2.9"
- "@smithy/middleware-stack": "npm:^4.2.8"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/node-http-handler": "npm:^4.4.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/url-parser": "npm:^4.2.8"
- "@smithy/util-base64": "npm:^4.3.0"
- "@smithy/util-body-length-browser": "npm:^4.2.0"
- "@smithy/util-body-length-node": "npm:^4.2.1"
- "@smithy/util-defaults-mode-browser": "npm:^4.3.28"
- "@smithy/util-defaults-mode-node": "npm:^4.2.31"
- "@smithy/util-endpoints": "npm:^3.2.8"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-retry": "npm:^4.2.8"
- "@smithy/util-stream": "npm:^4.5.10"
- "@smithy/util-utf8": "npm:^4.2.0"
- "@smithy/util-waiter": "npm:^4.2.8"
- tslib: "npm:^2.6.2"
- checksum: 10/52867aeab4dee02556a10e84a2c74d67888daa039f3ca8417b28846a4886283f7465037cdfd9795bc5cbc4d71d18fd413a17a96227dcffc462aec31152f3193c
- languageName: node
- linkType: hard
-
-"@aws-sdk/client-sso@npm:3.980.0":
- version: 3.980.0
- resolution: "@aws-sdk/client-sso@npm:3.980.0"
- dependencies:
- "@aws-crypto/sha256-browser": "npm:5.2.0"
- "@aws-crypto/sha256-js": "npm:5.2.0"
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/middleware-host-header": "npm:^3.972.3"
- "@aws-sdk/middleware-logger": "npm:^3.972.3"
- "@aws-sdk/middleware-recursion-detection": "npm:^3.972.3"
- "@aws-sdk/middleware-user-agent": "npm:^3.972.5"
- "@aws-sdk/region-config-resolver": "npm:^3.972.3"
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-endpoints": "npm:3.980.0"
- "@aws-sdk/util-user-agent-browser": "npm:^3.972.3"
- "@aws-sdk/util-user-agent-node": "npm:^3.972.3"
- "@smithy/config-resolver": "npm:^4.4.6"
- "@smithy/core": "npm:^3.22.0"
- "@smithy/fetch-http-handler": "npm:^5.3.9"
- "@smithy/hash-node": "npm:^4.2.8"
- "@smithy/invalid-dependency": "npm:^4.2.8"
- "@smithy/middleware-content-length": "npm:^4.2.8"
- "@smithy/middleware-endpoint": "npm:^4.4.12"
- "@smithy/middleware-retry": "npm:^4.4.29"
- "@smithy/middleware-serde": "npm:^4.2.9"
- "@smithy/middleware-stack": "npm:^4.2.8"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/node-http-handler": "npm:^4.4.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/url-parser": "npm:^4.2.8"
- "@smithy/util-base64": "npm:^4.3.0"
- "@smithy/util-body-length-browser": "npm:^4.2.0"
- "@smithy/util-body-length-node": "npm:^4.2.1"
- "@smithy/util-defaults-mode-browser": "npm:^4.3.28"
- "@smithy/util-defaults-mode-node": "npm:^4.2.31"
- "@smithy/util-endpoints": "npm:^3.2.8"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-retry": "npm:^4.2.8"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/c2715478f72b9d022de424c767e7c57a56e043d03e6fd9930baa11f228da8cda7173561706385e3617d5723cb71e4e8999eb3ddc62430940cc89df48f1c62ce7
- languageName: node
- linkType: hard
-
-"@aws-sdk/core@npm:^3.973.5":
- version: 3.973.5
- resolution: "@aws-sdk/core@npm:3.973.5"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/xml-builder": "npm:^3.972.2"
- "@smithy/core": "npm:^3.22.0"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/signature-v4": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-base64": "npm:^4.3.0"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/f5021f131d9755d72f3e4d871bc75c1eeaf40b944a1f2e483a0166bf1b9b5b9622fce41f3568d3a6ec58da03c811cc7008399edb86bd5d32f10a963a20870101
- languageName: node
- linkType: hard
-
-"@aws-sdk/crc64-nvme@npm:3.972.0":
- version: 3.972.0
- resolution: "@aws-sdk/crc64-nvme@npm:3.972.0"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/47d41dfbff4ed7664d1cc4565f4b190cdf6d87c7b550897a709894ba041c6d4c28171cf7089365af8441bf40234167df916f56bd4ea7c7cd6ba31cab56ed28b1
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-env@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/credential-provider-env@npm:3.972.3"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/a67ccab5c46d7b336ebe91ca8bb93c1741115c067b9243ed6f2164c921001fe5a798e84786381d9d03bc4ff07b4aeb1b0094404a9bac0674a0e975419709f7e4
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-http@npm:^3.972.5":
- version: 3.972.5
- resolution: "@aws-sdk/credential-provider-http@npm:3.972.5"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/fetch-http-handler": "npm:^5.3.9"
- "@smithy/node-http-handler": "npm:^4.4.8"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-stream": "npm:^4.5.10"
- tslib: "npm:^2.6.2"
- checksum: 10/55fd400d28ac906049a87090923ee6cecfbd8c182dd32ee699f3109c3e1c165aa9819c042d9e73f07802675aee620de41c348cc4794588ff7d231c4ff54dddcf
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-ini@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/credential-provider-ini@npm:3.972.3"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/credential-provider-env": "npm:^3.972.3"
- "@aws-sdk/credential-provider-http": "npm:^3.972.5"
- "@aws-sdk/credential-provider-login": "npm:^3.972.3"
- "@aws-sdk/credential-provider-process": "npm:^3.972.3"
- "@aws-sdk/credential-provider-sso": "npm:^3.972.3"
- "@aws-sdk/credential-provider-web-identity": "npm:^3.972.3"
- "@aws-sdk/nested-clients": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/credential-provider-imds": "npm:^4.2.8"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/22ecb40caf4ef4217c403d32bc809837cc0a78431af6004ca25a7d82597835aa00af0e387b826a130570059f1eab1229ce9e0f0c555e39b1218ca229d98dc538
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-login@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/credential-provider-login@npm:3.972.3"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/nested-clients": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/4ac4bd7d38f691311d9bac46e2986943a67ae4fc3d8d15f4539b2ef6d22608e564d0fe007b46815d780ec2de8c37c86b322387789fd05593484e338163691bc7
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-node@npm:^3.972.4":
- version: 3.972.4
- resolution: "@aws-sdk/credential-provider-node@npm:3.972.4"
- dependencies:
- "@aws-sdk/credential-provider-env": "npm:^3.972.3"
- "@aws-sdk/credential-provider-http": "npm:^3.972.5"
- "@aws-sdk/credential-provider-ini": "npm:^3.972.3"
- "@aws-sdk/credential-provider-process": "npm:^3.972.3"
- "@aws-sdk/credential-provider-sso": "npm:^3.972.3"
- "@aws-sdk/credential-provider-web-identity": "npm:^3.972.3"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/credential-provider-imds": "npm:^4.2.8"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/0ee3ad056d78f67f9c8afe78ab46f82ceca7e432079ec1a1c3db29d23ec0c67959c72ece571d3a143d1eab78158825aac720d5c3f47715984ab0dff27c619400
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-process@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/credential-provider-process@npm:3.972.3"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/37421140a546a9a45e890d8d32e8214a5b1b0ed80844031d9deb5c3e2ab2cb5b52242ee9f72795310d194fc54ffc51f6f15f9d36ae07b1ccd32873f99b9fba41
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-sso@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/credential-provider-sso@npm:3.972.3"
- dependencies:
- "@aws-sdk/client-sso": "npm:3.980.0"
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/token-providers": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/f025a35c068548be28b5e1343e52102b09b9fd9e01d0bc433700d68cd06007b92ea56c836c79ec74612ad7fce1112a65293a81e85961aa09023a7b39049cf271
- languageName: node
- linkType: hard
-
-"@aws-sdk/credential-provider-web-identity@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/credential-provider-web-identity@npm:3.972.3"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/nested-clients": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/0022c2e17f2bab8d39d0b3875a6ac65631e984256ea95fb5e0b67cab19a38e0fdc02ce3089051b8307d3ad7ddfef0211e94b76ee39e40fe90ca7e587c740dbe2
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-bucket-endpoint@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-arn-parser": "npm:^3.972.2"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-config-provider": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/5e0906a76ab6f200901759537fb69034546d228405b12b02b64e04f85aefacda0e0818f07d8595617b9956f135fc56545827624f9652858e27da231240cbb9b3
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-expect-continue@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-expect-continue@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/96c2d64294e9482873345543a2d1c11a67941bde5dfdb32c1c05b578a394083583e53c6a1c2c3ccee41e4937391ae38878b7c03fd2b5ba08e06567926e34a248
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-flexible-checksums@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.972.3"
- dependencies:
- "@aws-crypto/crc32": "npm:5.2.0"
- "@aws-crypto/crc32c": "npm:5.2.0"
- "@aws-crypto/util": "npm:5.2.0"
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/crc64-nvme": "npm:3.972.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/is-array-buffer": "npm:^4.2.0"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-stream": "npm:^4.5.10"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/0ba04273b21ffaee56d444dc2c6c65e0f75c2f823ad1ff78973fac959a1c57ad2429f0c6d19e1366830e8981fda471c79d8d07b1cf8389690f7d2f7b45dce340
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-host-header@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-host-header@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/14b6e32f32f1c8b0e66a396b092785d3d597b27df696ed2daf8310d2a463416bcc89480043b6a5083698403fc85904caf5ebbcb0fbd12f89f05dbf10878d2cc7
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-location-constraint@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-location-constraint@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/9c9677e07af9db00af5f748aae79321ec9fb3888b508704e1de0a1fbcf19e1f254037274324d17fc1c11f24ad60c075024560784f0e9958b4868da3e24e9460b
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-logger@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-logger@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/abda3a05b73a2056fbe0d2aa139ee5ad590733d7ef96a18c2ca92b314795ba3fe83216668bd731b8a40f7951b1147eb1ed3566c1b33ee9b8ae9994089596e3b8
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-recursion-detection@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-recursion-detection@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws/lambda-invoke-store": "npm:^0.2.2"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/8308e8eb1344669bca86613f160768dd39640ca3ed37730b579a6f71be14f6deed7acdb4f3d195a7f8c5a130afb82411dc18c8a361f7dc1f769c9dc240aaa16f
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-sdk-s3@npm:^3.972.5":
- version: 3.972.5
- resolution: "@aws-sdk/middleware-sdk-s3@npm:3.972.5"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-arn-parser": "npm:^3.972.2"
- "@smithy/core": "npm:^3.22.0"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/signature-v4": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-config-provider": "npm:^4.2.0"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-stream": "npm:^4.5.10"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/94aef879d027d2bd99facbf485ad6bd0219905f62825c4abda59c69813d9f68b0221dfd347015bcb7cdb848a764cdec1b84630ec86b59c0cad1125bd082e874b
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-ssec@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/middleware-ssec@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/6510039afd2f1dce5b9b4870123fb269b6315246a58111d7b08849fff1dd4312f10f39ca69dc5838406c3b7063923fc182dd746cb6543934b41f6f4a29f61980
- languageName: node
- linkType: hard
-
-"@aws-sdk/middleware-user-agent@npm:^3.972.5":
- version: 3.972.5
- resolution: "@aws-sdk/middleware-user-agent@npm:3.972.5"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-endpoints": "npm:3.980.0"
- "@smithy/core": "npm:^3.22.0"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/2e77b0b5c15eef3ce192c403c86f31ff20418d2657fda4d66f0bd7997116cf5638610e9b277fc2be9fb86ae63f3f804706e7cd96bec839602d350adf800c5f4c
- languageName: node
- linkType: hard
-
-"@aws-sdk/nested-clients@npm:3.980.0":
- version: 3.980.0
- resolution: "@aws-sdk/nested-clients@npm:3.980.0"
- dependencies:
- "@aws-crypto/sha256-browser": "npm:5.2.0"
- "@aws-crypto/sha256-js": "npm:5.2.0"
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/middleware-host-header": "npm:^3.972.3"
- "@aws-sdk/middleware-logger": "npm:^3.972.3"
- "@aws-sdk/middleware-recursion-detection": "npm:^3.972.3"
- "@aws-sdk/middleware-user-agent": "npm:^3.972.5"
- "@aws-sdk/region-config-resolver": "npm:^3.972.3"
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-endpoints": "npm:3.980.0"
- "@aws-sdk/util-user-agent-browser": "npm:^3.972.3"
- "@aws-sdk/util-user-agent-node": "npm:^3.972.3"
- "@smithy/config-resolver": "npm:^4.4.6"
- "@smithy/core": "npm:^3.22.0"
- "@smithy/fetch-http-handler": "npm:^5.3.9"
- "@smithy/hash-node": "npm:^4.2.8"
- "@smithy/invalid-dependency": "npm:^4.2.8"
- "@smithy/middleware-content-length": "npm:^4.2.8"
- "@smithy/middleware-endpoint": "npm:^4.4.12"
- "@smithy/middleware-retry": "npm:^4.4.29"
- "@smithy/middleware-serde": "npm:^4.2.9"
- "@smithy/middleware-stack": "npm:^4.2.8"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/node-http-handler": "npm:^4.4.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/url-parser": "npm:^4.2.8"
- "@smithy/util-base64": "npm:^4.3.0"
- "@smithy/util-body-length-browser": "npm:^4.2.0"
- "@smithy/util-body-length-node": "npm:^4.2.1"
- "@smithy/util-defaults-mode-browser": "npm:^4.3.28"
- "@smithy/util-defaults-mode-node": "npm:^4.2.31"
- "@smithy/util-endpoints": "npm:^3.2.8"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-retry": "npm:^4.2.8"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/601bcf7ec78ca3ffa476d069a17182364fcc6cc4812bf0550af2d4fa58be2b87eb0da1a0c6ba25d3c361aa2a7a05bed6c1e3a25fa8aba8c87037fff97a237b2e
- languageName: node
- linkType: hard
-
-"@aws-sdk/region-config-resolver@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/region-config-resolver@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/config-resolver": "npm:^4.4.6"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/8512a573492a990b028d9f0058d6034d54fb186af20d1da9529ac3d5f8d435c43fa16ef7d3dc0b3ffa679bb90529b55b0d00619160a3549839a136cc698fefb8
- languageName: node
- linkType: hard
-
-"@aws-sdk/s3-request-presigner@npm:^3.948.0":
- version: 3.980.0
- resolution: "@aws-sdk/s3-request-presigner@npm:3.980.0"
- dependencies:
- "@aws-sdk/signature-v4-multi-region": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@aws-sdk/util-format-url": "npm:^3.972.3"
- "@smithy/middleware-endpoint": "npm:^4.4.12"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/b0b92bf7c8270647acbd5eadf1fd1c2ed2ac363d30e2075a8e410fb4f670f4400490c6f4dd3ae5a072837bf719ec6c8d199a4c652ca9464dcc02633f4082c80c
- languageName: node
- linkType: hard
-
-"@aws-sdk/signature-v4-multi-region@npm:3.980.0":
- version: 3.980.0
- resolution: "@aws-sdk/signature-v4-multi-region@npm:3.980.0"
- dependencies:
- "@aws-sdk/middleware-sdk-s3": "npm:^3.972.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/signature-v4": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/456e1617c1f51289616e858181ee84f1d2424abd21dd27ff3a9b1c1834fa07cf88e34674321a20b1a15533988adeced6cb07888a7b61a3988e98044c2189b7f5
- languageName: node
- linkType: hard
-
-"@aws-sdk/token-providers@npm:3.980.0":
- version: 3.980.0
- resolution: "@aws-sdk/token-providers@npm:3.980.0"
- dependencies:
- "@aws-sdk/core": "npm:^3.973.5"
- "@aws-sdk/nested-clients": "npm:3.980.0"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/b9d903cc9d84b95a9260001e617eb2bab3c037a7778bd22728e85a02ad48d207771b7d803135653919ed9d89ca4c0a9dc535cec8d728cde5a7e8dc5569482cbb
- languageName: node
- linkType: hard
-
-"@aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.973.1":
- version: 3.973.1
- resolution: "@aws-sdk/types@npm:3.973.1"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/9cdcb457d6110a88a547fe26922d43450bf7685b26034e935c72c1717de90a22541f298ce4e76fde564d3af11908928b1584b856085dcb175f9bb08853d1a575
- languageName: node
- linkType: hard
-
-"@aws-sdk/util-arn-parser@npm:^3.972.2":
- version: 3.972.2
- resolution: "@aws-sdk/util-arn-parser@npm:3.972.2"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/6c09725259187615199b44c21cc9aaf6e61c4d1f326535fd36cf1e95d9842bd58084542c72a9facbca47c5846c5bd8fed7b179e86a036ee142d4a171a6098092
- languageName: node
- linkType: hard
-
-"@aws-sdk/util-endpoints@npm:3.980.0":
- version: 3.980.0
- resolution: "@aws-sdk/util-endpoints@npm:3.980.0"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/url-parser": "npm:^4.2.8"
- "@smithy/util-endpoints": "npm:^3.2.8"
- tslib: "npm:^2.6.2"
- checksum: 10/a61ec475660cc736960663f756970e07246a7684b762830e8b17ec0873dc5a4f9135fa6104219a2c790d22f30d36369ee19ade124b396d6f09a1139a878e656e
- languageName: node
- linkType: hard
-
-"@aws-sdk/util-format-url@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/util-format-url@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/querystring-builder": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/3d128ba22efc0d58406dd9e9503e62d75ae0dea22ed0276f9755acf598236918d0c2802947e0031ac924a14e8b21c387520e08515bedf56ee00fe83f4747b795
- languageName: node
- linkType: hard
-
-"@aws-sdk/util-locate-window@npm:^3.0.0":
- version: 3.957.0
- resolution: "@aws-sdk/util-locate-window@npm:3.957.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/ab9efda42115c605cd35710750d8fe55a832962d499c77d1218d3e9a127dfeec33342f35f15845dd7688833f7cdda0e190555719b42641eb75a4e76607bcb5e6
- languageName: node
- linkType: hard
-
-"@aws-sdk/util-user-agent-browser@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/util-user-agent-browser@npm:3.972.3"
- dependencies:
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/types": "npm:^4.12.0"
- bowser: "npm:^2.11.0"
- tslib: "npm:^2.6.2"
- checksum: 10/fb51d6ae56ba2a69a1239fc1f83a739c468c78ff678cf336b923273237e861b8ff4bfb296b7a250f5980dc2ef6741492a802432243313daf9a03a5332199f7aa
- languageName: node
- linkType: hard
-
-"@aws-sdk/util-user-agent-node@npm:^3.972.3":
- version: 3.972.3
- resolution: "@aws-sdk/util-user-agent-node@npm:3.972.3"
- dependencies:
- "@aws-sdk/middleware-user-agent": "npm:^3.972.5"
- "@aws-sdk/types": "npm:^3.973.1"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- peerDependencies:
- aws-crt: ">=1.0.0"
- peerDependenciesMeta:
- aws-crt:
- optional: true
- checksum: 10/abeabdf825d9fbcc2e88c0ce6c47f15b29a8a0932e3106cd2637d0843897abca3b7f2eef757b31c82eb0ced0d733b84c9695ff260b950794bab9aac9807871b3
- languageName: node
- linkType: hard
-
-"@aws-sdk/xml-builder@npm:^3.972.2":
- version: 3.972.2
- resolution: "@aws-sdk/xml-builder@npm:3.972.2"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- fast-xml-parser: "npm:5.2.5"
- tslib: "npm:^2.6.2"
- checksum: 10/d2f16b53520589fcc1d7720a290286790da94690f49c472afa7017b1250f98abcdb1d32d39b29d7a6c63542eb6808cb006702d5bd470365e86aef18d6dc76ea4
- languageName: node
- linkType: hard
-
-"@aws/lambda-invoke-store@npm:^0.2.2":
- version: 0.2.2
- resolution: "@aws/lambda-invoke-store@npm:0.2.2"
- checksum: 10/18cd0cec90d9d865c9089218ef2220b0a7302a860c9a3f808b101386f569abc5ee11eb98a36947bed280a63308dd5df23c39e7b07fe9ac4f4ffcd0c4dce537c4
- languageName: node
- linkType: hard
-
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.27.1":
version: 7.27.1
resolution: "@babel/code-frame@npm:7.27.1"
@@ -16356,614 +15662,6 @@ __metadata:
languageName: node
linkType: hard
-"@smithy/abort-controller@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/abort-controller@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/17d5beb1c86227ced459e6abbb03d6a3f205bd6f535a4bca2a10e9b4838292c533be78dbf39cdbf1f8f4af0c2fc3fec2f3081b3d4a1bf4e12a2a2aa52e298173
- languageName: node
- linkType: hard
-
-"@smithy/chunked-blob-reader-native@npm:^4.2.1":
- version: 4.2.1
- resolution: "@smithy/chunked-blob-reader-native@npm:4.2.1"
- dependencies:
- "@smithy/util-base64": "npm:^4.3.0"
- tslib: "npm:^2.6.2"
- checksum: 10/491cd1fbf74c53cc8c63abef1d9c0e93d1c0773db2c4458d4d3bd08217ea58872e413191b56259fd8081653ee07628e3ffcf7ff594d124378401fc3637794474
- languageName: node
- linkType: hard
-
-"@smithy/chunked-blob-reader@npm:^5.2.0":
- version: 5.2.0
- resolution: "@smithy/chunked-blob-reader@npm:5.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/c2f3b93343daba9a71e2f00fb93ae527a03c0adb6c6c6e194834bf4a67111e87f0694e2d9dd9b70bca87e9eb9da1d905d4450147e54e4cd27c6703dd98d58e0c
- languageName: node
- linkType: hard
-
-"@smithy/config-resolver@npm:^4.4.6":
- version: 4.4.6
- resolution: "@smithy/config-resolver@npm:4.4.6"
- dependencies:
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-config-provider": "npm:^4.2.0"
- "@smithy/util-endpoints": "npm:^3.2.8"
- "@smithy/util-middleware": "npm:^4.2.8"
- tslib: "npm:^2.6.2"
- checksum: 10/6440612a9e9a29b74f3420244f3e416d2c2ff0ed4956af323cd39eb4b8efe22a01e791e8cf465c5b0230a778a825290d6b935e3c6d4ca5a92336b48a2b2b4dbd
- languageName: node
- linkType: hard
-
-"@smithy/core@npm:^3.22.0":
- version: 3.22.0
- resolution: "@smithy/core@npm:3.22.0"
- dependencies:
- "@smithy/middleware-serde": "npm:^4.2.9"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-base64": "npm:^4.3.0"
- "@smithy/util-body-length-browser": "npm:^4.2.0"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-stream": "npm:^4.5.10"
- "@smithy/util-utf8": "npm:^4.2.0"
- "@smithy/uuid": "npm:^1.1.0"
- tslib: "npm:^2.6.2"
- checksum: 10/2a10318b5503f02777a29c77578977ff427808edb98bb481d5d0ac770b99b662137abc89564f50ef92e4ef0a64366a3da4e9b8cd86ede13e76a694db1b4e6584
- languageName: node
- linkType: hard
-
-"@smithy/credential-provider-imds@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/credential-provider-imds@npm:4.2.8"
- dependencies:
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/url-parser": "npm:^4.2.8"
- tslib: "npm:^2.6.2"
- checksum: 10/f0d7abbe28a8244cacf65a453f132e38902e8e912b284b8371165b94ce6ae183acedc430d84ab466ef2d6930867f44d6aeaa4bb877e53a06a8f2dbd42c145d69
- languageName: node
- linkType: hard
-
-"@smithy/eventstream-codec@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/eventstream-codec@npm:4.2.8"
- dependencies:
- "@aws-crypto/crc32": "npm:5.2.0"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-hex-encoding": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/45e027b320056dc82ce23928a09d29baa5d080c89008874f409c557228923ce216940990bbe53204d8628a0ca4d1e774cbb5aaceb4b5ba6237b89c108ce39a32
- languageName: node
- linkType: hard
-
-"@smithy/eventstream-serde-browser@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/eventstream-serde-browser@npm:4.2.8"
- dependencies:
- "@smithy/eventstream-serde-universal": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/10aef5211bb360b67861f672084a1270caa8b5c1ab5ccbb388d507080387d65b714239e997e8851ec8a38082144ebca316af0db963b1aae15f5160c5c36a1315
- languageName: node
- linkType: hard
-
-"@smithy/eventstream-serde-config-resolver@npm:^4.3.8":
- version: 4.3.8
- resolution: "@smithy/eventstream-serde-config-resolver@npm:4.3.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/fbd4b1278c047a7b8bde7181a17c46ee17c93c8d907d54f8122312bed16a6ef835914962746ec4cb11154a09c9eec166e7ffd3bdc65af0a38a62ab7083902418
- languageName: node
- linkType: hard
-
-"@smithy/eventstream-serde-node@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/eventstream-serde-node@npm:4.2.8"
- dependencies:
- "@smithy/eventstream-serde-universal": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/603840ac95222293b7b5db6201249b08c2dd9ee343a66fde5a5025b1f3bab130be6b4f6ddd7b657a440b422a2f16868a2f30553eb1a27aafabcf8a0aab1729c9
- languageName: node
- linkType: hard
-
-"@smithy/eventstream-serde-universal@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/eventstream-serde-universal@npm:4.2.8"
- dependencies:
- "@smithy/eventstream-codec": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/814366a4184ed28e51edeeee43c46b3a8e7153d1136e0802e86c6ff9143c73bf6137617b67c7763d374ed921d673f54fd950bf0fdc09aebaf07977eeb0c60e63
- languageName: node
- linkType: hard
-
-"@smithy/fetch-http-handler@npm:^5.3.9":
- version: 5.3.9
- resolution: "@smithy/fetch-http-handler@npm:5.3.9"
- dependencies:
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/querystring-builder": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-base64": "npm:^4.3.0"
- tslib: "npm:^2.6.2"
- checksum: 10/7e350c6a4f49e9c913367791f2fb48bc160ae60ad2a6f314baf384623aed2ee5b50996b4ffcc8ddf8abb0ba9489bb524dedb1769756431c45e3ab7bfc41b7994
- languageName: node
- linkType: hard
-
-"@smithy/hash-blob-browser@npm:^4.2.9":
- version: 4.2.9
- resolution: "@smithy/hash-blob-browser@npm:4.2.9"
- dependencies:
- "@smithy/chunked-blob-reader": "npm:^5.2.0"
- "@smithy/chunked-blob-reader-native": "npm:^4.2.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/de9641b7b66085e35a2896304216419fb7f073609f12686d7df775b0df8c83066e778a757e664be37c07ed4c2f87cce7754878213a2e4cd6f80cc208e61aa42f
- languageName: node
- linkType: hard
-
-"@smithy/hash-node@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/hash-node@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-buffer-from": "npm:^4.2.0"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/db765b8f338e4109aab1d7032175c74673bfedff10cae2241e91034efa42cf01a657f5c0494ef79fc9d7aa2da9ab01981c64583d0a736baf5e6b3038a69a0c1f
- languageName: node
- linkType: hard
-
-"@smithy/hash-stream-node@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/hash-stream-node@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/154583e9f39508aad8250d121bb6810a480db6428319b12a10465b83cc87246c74cbef65ec71953c7a80d626fb55e38506b294d93a082fabf9217be7c7d35cda
- languageName: node
- linkType: hard
-
-"@smithy/invalid-dependency@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/invalid-dependency@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/e1c1d0a654e096f74dfec32e48492075f4d96f7f3694a1c5b530c575e402eb605f381748f321ae7b491b97142d3bfbd55f269b1b3257dcc0d3aa38508e227e2b
- languageName: node
- linkType: hard
-
-"@smithy/is-array-buffer@npm:^2.2.0":
- version: 2.2.0
- resolution: "@smithy/is-array-buffer@npm:2.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/d366743ecc7a9fc3bad21dbb3950d213c12bdd4aeb62b1265bf6cbe38309df547664ef3e51ab732e704485194f15e89d361943b0bfbe3fe1a4b3178b942913cc
- languageName: node
- linkType: hard
-
-"@smithy/is-array-buffer@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/is-array-buffer@npm:4.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/fdc097ce6a8b241565e2d56460ec289730bcd734dcde17c23d1eaaa0996337f897217166276a3fd82491fe9fd17447aadf62e8d9056b3d2b9daf192b4b668af9
- languageName: node
- linkType: hard
-
-"@smithy/md5-js@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/md5-js@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/bc5478f5918c9c9bb7f6f3b62c2a374b20c3f7e0a01df25edf1f8b0832778a0625d69df50bf01c9434e9d8002561c28bc20a2d151cfc7a89d157a79bd900e199
- languageName: node
- linkType: hard
-
-"@smithy/middleware-content-length@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/middleware-content-length@npm:4.2.8"
- dependencies:
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/9077c99f263843d347c847057ba3f7c270a8f71d96018f123fd78f1a0439f076e5ae989e7ce83e158f94b45afc7e8665f67d33e4c2cb66d7bbb88495ae9f1785
- languageName: node
- linkType: hard
-
-"@smithy/middleware-endpoint@npm:^4.4.12":
- version: 4.4.12
- resolution: "@smithy/middleware-endpoint@npm:4.4.12"
- dependencies:
- "@smithy/core": "npm:^3.22.0"
- "@smithy/middleware-serde": "npm:^4.2.9"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/url-parser": "npm:^4.2.8"
- "@smithy/util-middleware": "npm:^4.2.8"
- tslib: "npm:^2.6.2"
- checksum: 10/cd45ae6da1cb327fe2ff79ca1a5635c43ca6c47cdc42dc3c1103bcfc9b61417d444a8a927bf9a3f440ce7b8390520ccb606d72cd77f361433e4f24c65f94c533
- languageName: node
- linkType: hard
-
-"@smithy/middleware-retry@npm:^4.4.29":
- version: 4.4.29
- resolution: "@smithy/middleware-retry@npm:4.4.29"
- dependencies:
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/service-error-classification": "npm:^4.2.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-retry": "npm:^4.2.8"
- "@smithy/uuid": "npm:^1.1.0"
- tslib: "npm:^2.6.2"
- checksum: 10/a95d40ea5d5c44c9815726b803d742975caa49aafbc9c321af5f1dafc1c3810b38cb2a83791be36ecfae50bb6ddbd9deac1b99fa4b81acbd7a3a2217dbfa9387
- languageName: node
- linkType: hard
-
-"@smithy/middleware-serde@npm:^4.2.9":
- version: 4.2.9
- resolution: "@smithy/middleware-serde@npm:4.2.9"
- dependencies:
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/490e9ab6ce6664812e30975d3f24d769c8ba59f153c97a5095516f8fd22ed6d948cd4838cfdb253b020b3ec8914b4ec3cb31f1d6ca84ece7639381d5dec6c463
- languageName: node
- linkType: hard
-
-"@smithy/middleware-stack@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/middleware-stack@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/c4b8dc4e466e31e4adc36a52af5e7f5bdc9adf3cc31e825947a2f73f5e1beb5ef87b72624427e6f3a18951407878d7f0ef33990c210aa7df5143c028f0ef8740
- languageName: node
- linkType: hard
-
-"@smithy/node-config-provider@npm:^4.3.8":
- version: 4.3.8
- resolution: "@smithy/node-config-provider@npm:4.3.8"
- dependencies:
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/shared-ini-file-loader": "npm:^4.4.3"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/e954b98ad121e76174453bf67bf9824b661de61865d3e92e845d6e0656b3d8c41ebc90a176428d3732a14dd8cfe5795644864d17470a5af37599c2c4b3c221fd
- languageName: node
- linkType: hard
-
-"@smithy/node-http-handler@npm:^4.4.8":
- version: 4.4.8
- resolution: "@smithy/node-http-handler@npm:4.4.8"
- dependencies:
- "@smithy/abort-controller": "npm:^4.2.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/querystring-builder": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/f5df30b2dc307c36a866104c415af2b776ad28821948f4391ae18bd62f66a99886530b0ff9c02f7389bcbda1dcc325f5818d6edf9cf1bea361a81f40892cadac
- languageName: node
- linkType: hard
-
-"@smithy/property-provider@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/property-provider@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/d50f51bf029f72ec3679c7945cbb77f71d53fa5f53a20adcbc0ab25f53587add46d1ed1dd90becb1bdf0c97c9caf7f8a45d868eefe3951a4e68bc3ce5ed1eb29
- languageName: node
- linkType: hard
-
-"@smithy/protocol-http@npm:^5.3.8":
- version: 5.3.8
- resolution: "@smithy/protocol-http@npm:5.3.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/6465375d9feff2c2718e5b30d358f3d63f007574b2338c6b08dde11d11a98371697b9ec047455fa71be6ede9770e7e53ee5d9715ed7033dbfb825ec4d029066e
- languageName: node
- linkType: hard
-
-"@smithy/querystring-builder@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/querystring-builder@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-uri-escape": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/13bd560936d31f51006174f962260526c21df1cdb821f83cc3f7e6424c1a37f2b6b76a92bef1241174eebbdd5ef06f050752460ad638f7814f23f499e0a847fa
- languageName: node
- linkType: hard
-
-"@smithy/querystring-parser@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/querystring-parser@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/26e5a3fc8d1623980f9a03662b6b2349a4a4e6f0ecb9af4df9f11a2cc83a58d4ef3571d104e5ff1a10973a4e297b3aa8327f261d647ffc6f5ee871008a740580
- languageName: node
- linkType: hard
-
-"@smithy/service-error-classification@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/service-error-classification@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- checksum: 10/ffcbaa6fa3536642dc03f3c7feb762a3b4acfa5d45ff74e401634f472549fce2608a5b1ebd339de5fc0ba2e0f6296b5fa8e49258cb1b675aa298aed631728542
- languageName: node
- linkType: hard
-
-"@smithy/shared-ini-file-loader@npm:^4.4.3":
- version: 4.4.3
- resolution: "@smithy/shared-ini-file-loader@npm:4.4.3"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/70cf7db0e24768d5e6a019de29d194ca4516e9177cbd9cd97ce7800889ee2bd3d8cfd71958d11cd026f79223cb34c64176234443d464cf6146562e0385f7daea
- languageName: node
- linkType: hard
-
-"@smithy/signature-v4@npm:^5.3.8":
- version: 5.3.8
- resolution: "@smithy/signature-v4@npm:5.3.8"
- dependencies:
- "@smithy/is-array-buffer": "npm:^4.2.0"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-hex-encoding": "npm:^4.2.0"
- "@smithy/util-middleware": "npm:^4.2.8"
- "@smithy/util-uri-escape": "npm:^4.2.0"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/88bd0b507bf1a567519208d5b5fb923142bf63bd9b7bfd8b0d4485a8225a80c4274956770127ef471ace96dbb00f1e0bee0bafeb365c5f5346e5419e6ed882fc
- languageName: node
- linkType: hard
-
-"@smithy/smithy-client@npm:^4.11.1":
- version: 4.11.1
- resolution: "@smithy/smithy-client@npm:4.11.1"
- dependencies:
- "@smithy/core": "npm:^3.22.0"
- "@smithy/middleware-endpoint": "npm:^4.4.12"
- "@smithy/middleware-stack": "npm:^4.2.8"
- "@smithy/protocol-http": "npm:^5.3.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-stream": "npm:^4.5.10"
- tslib: "npm:^2.6.2"
- checksum: 10/b72ed4deea2fea948e89b026d319d85380a23bce7a7f6690769a2615ba5d989656a43f6a5aa85dbbb35b1e7d6c3ffab0546fb97daa506b3d87f6b9d929da1f6e
- languageName: node
- linkType: hard
-
-"@smithy/types@npm:^4.12.0":
- version: 4.12.0
- resolution: "@smithy/types@npm:4.12.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/7fe734b4cae1ae3a5c3f8a0aefae072530026917436a5db699d2e27e3518cde4ba4ffe001ef7c45e4a87a02bdae8eabb67b82e6db80153eaf41776901718aa62
- languageName: node
- linkType: hard
-
-"@smithy/url-parser@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/url-parser@npm:4.2.8"
- dependencies:
- "@smithy/querystring-parser": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/8e99b893502f219e5bd9c17f6f974a433f3e56c6dc899cb753281c7701c19126f202766dcee69c4e5ecb1b941daa68bc5d6ea603dd5121bce0de5135268664d4
- languageName: node
- linkType: hard
-
-"@smithy/util-base64@npm:^4.3.0":
- version: 4.3.0
- resolution: "@smithy/util-base64@npm:4.3.0"
- dependencies:
- "@smithy/util-buffer-from": "npm:^4.2.0"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/87065ca13e3745858e0bb0ab6374433b258c378ee2a5ef865b74f6a4208c56db7db2b9ee5f888e021de0107fae49e9957662c4c6847fe10529e2f6cc882426b4
- languageName: node
- linkType: hard
-
-"@smithy/util-body-length-browser@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/util-body-length-browser@npm:4.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/deeb689b52652651c11530a324e07725805533899215ad1f93c5e9a14931443e22b313491a3c2a6d7f61d6dd1e84f9154d0d32de62bf61e0bd8e6ab7bf5f81ed
- languageName: node
- linkType: hard
-
-"@smithy/util-body-length-node@npm:^4.2.1":
- version: 4.2.1
- resolution: "@smithy/util-body-length-node@npm:4.2.1"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/efb1333d35120124ec0c751b7b7d5657eb9ad6d0bf6171ff61fde2504639883d36e9562613c70eca623b726193b22601c8ff60e40a8156102d4c5b12fae222f8
- languageName: node
- linkType: hard
-
-"@smithy/util-buffer-from@npm:^2.2.0":
- version: 2.2.0
- resolution: "@smithy/util-buffer-from@npm:2.2.0"
- dependencies:
- "@smithy/is-array-buffer": "npm:^2.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/53253e4e351df3c4b7907dca48a0a6ceae783e98a8e73526820b122b3047a53fd127c19f4d8301f68d852011d821da519da783de57e0b22eed57c4df5b90d089
- languageName: node
- linkType: hard
-
-"@smithy/util-buffer-from@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/util-buffer-from@npm:4.2.0"
- dependencies:
- "@smithy/is-array-buffer": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/6a81e658554d7123fe089426a840b5e691aee4aa4f0d72b79af19dcf57ccb212dca518acb447714792d48c2dc99bda5e0e823dab05e450ee2393146706d476f9
- languageName: node
- linkType: hard
-
-"@smithy/util-config-provider@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/util-config-provider@npm:4.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/d65f36401c7a085660cf201a1b317d271e390258b619179fff88248c2db64fc35e6c62fe055f1e55be8935b06eb600379824dabf634fb26d528f54fe60c9d77b
- languageName: node
- linkType: hard
-
-"@smithy/util-defaults-mode-browser@npm:^4.3.28":
- version: 4.3.28
- resolution: "@smithy/util-defaults-mode-browser@npm:4.3.28"
- dependencies:
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/175aa34d207a66a2fb500a1ef68b8a89455e11410fbb2687eba099efb3ededb505144811ac6ed7df1fe29bce3758bf2c748b1b85f5ee8b6f7d4efef61553ff53
- languageName: node
- linkType: hard
-
-"@smithy/util-defaults-mode-node@npm:^4.2.31":
- version: 4.2.31
- resolution: "@smithy/util-defaults-mode-node@npm:4.2.31"
- dependencies:
- "@smithy/config-resolver": "npm:^4.4.6"
- "@smithy/credential-provider-imds": "npm:^4.2.8"
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/property-provider": "npm:^4.2.8"
- "@smithy/smithy-client": "npm:^4.11.1"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/fd5a7d8789b898ca9c17805eb44a62e5b81c35dc4b0823e3d68f70ba0a5a605c36a2c4c528259724293735953b64f53f4184de7e34ac642d2a990b05eb979e20
- languageName: node
- linkType: hard
-
-"@smithy/util-endpoints@npm:^3.2.8":
- version: 3.2.8
- resolution: "@smithy/util-endpoints@npm:3.2.8"
- dependencies:
- "@smithy/node-config-provider": "npm:^4.3.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/65ea9b1d5abaa944290d6cc4106f74909dafb832616187c17b6c6705f4cb3aa9ea33068595cf161418020a01724716e3c3e1534e78983e92a656f3b85cac02bf
- languageName: node
- linkType: hard
-
-"@smithy/util-hex-encoding@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/util-hex-encoding@npm:4.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/478773d73690e39167b67481116c4fd47cecfc97c3a935d88db9271fb0718627bec1cbc143efbf0cd49d1ac417bde7e76aa74139ea07e365b51e66797f63a45d
- languageName: node
- linkType: hard
-
-"@smithy/util-middleware@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/util-middleware@npm:4.2.8"
- dependencies:
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/a675f1968ad4a674cc70833be14e8f0e99b09626db9c5764e1d92c76e663d83ba64af4aac5d03112726436cad045cc817d19a71addc5aca6d363b1964ff51d31
- languageName: node
- linkType: hard
-
-"@smithy/util-retry@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/util-retry@npm:4.2.8"
- dependencies:
- "@smithy/service-error-classification": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/c725368bafc63cc54a2fad528d5667998986699ca87c87c30e12354f45008b0664f7d1b2afb0e310190227a1e99aa4c44dcb27e8663431ca3b37659c44ec339b
- languageName: node
- linkType: hard
-
-"@smithy/util-stream@npm:^4.5.10":
- version: 4.5.10
- resolution: "@smithy/util-stream@npm:4.5.10"
- dependencies:
- "@smithy/fetch-http-handler": "npm:^5.3.9"
- "@smithy/node-http-handler": "npm:^4.4.8"
- "@smithy/types": "npm:^4.12.0"
- "@smithy/util-base64": "npm:^4.3.0"
- "@smithy/util-buffer-from": "npm:^4.2.0"
- "@smithy/util-hex-encoding": "npm:^4.2.0"
- "@smithy/util-utf8": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/7d8fc4f86fc43edba5124836a7701cacacd65aa0f3a917faba4febcc091055c2be176b3de9bdacbcff5b7e8a97ecd35c66e38fd92743de385fd9774bdbdcc42f
- languageName: node
- linkType: hard
-
-"@smithy/util-uri-escape@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/util-uri-escape@npm:4.2.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/a838a3afe557d7087d4500735c79d5da72e0cd5a08f95d1a1c450ba29d9cd85c950228eedbd9b2494156f4eb8658afb0a9a5bd2df3fc4f297faed886c396242b
- languageName: node
- linkType: hard
-
-"@smithy/util-utf8@npm:^2.0.0":
- version: 2.3.0
- resolution: "@smithy/util-utf8@npm:2.3.0"
- dependencies:
- "@smithy/util-buffer-from": "npm:^2.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/c766ead8dac6bc6169f4cac1cc47ef7bd86928d06255148f9528228002f669c8cc49f78dc2b9ba5d7e214d40315024a9e32c5c9130b33e20f0fe4532acd0dff5
- languageName: node
- linkType: hard
-
-"@smithy/util-utf8@npm:^4.2.0":
- version: 4.2.0
- resolution: "@smithy/util-utf8@npm:4.2.0"
- dependencies:
- "@smithy/util-buffer-from": "npm:^4.2.0"
- tslib: "npm:^2.6.2"
- checksum: 10/d49f58fc6681255eecc3dee39c657b80ef8a4c5617e361bdaf6aaa22f02e378622376153cafc9f0655fb80162e88fc98bbf459f8dd5ba6d7c4b9a59e6eaa05f8
- languageName: node
- linkType: hard
-
-"@smithy/util-waiter@npm:^4.2.8":
- version: 4.2.8
- resolution: "@smithy/util-waiter@npm:4.2.8"
- dependencies:
- "@smithy/abort-controller": "npm:^4.2.8"
- "@smithy/types": "npm:^4.12.0"
- tslib: "npm:^2.6.2"
- checksum: 10/d492ed07fc9b1147660d99b142c4db150d730f2155ba3027363894c97c3d6a539cb69ae6952cf25cb5f79b870e4ce13a30d8fcd7346b3a358d223ae1b080188a
- languageName: node
- linkType: hard
-
-"@smithy/uuid@npm:^1.1.0":
- version: 1.1.0
- resolution: "@smithy/uuid@npm:1.1.0"
- dependencies:
- tslib: "npm:^2.6.2"
- checksum: 10/fe77b1cebbbf2d541ee2f07eec6d4573af16e08dd3228758f59dcbe85a504112cefe81b971818cf39e2e3fa0ed1fcc61d392cddc50fca13d9dc9bd835e366db0
- languageName: node
- linkType: hard
-
"@socket.io/component-emitter@npm:~3.1.0":
version: 3.1.2
resolution: "@socket.io/component-emitter@npm:3.1.2"
@@ -17778,6 +16476,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/aws4@npm:^1.11.6":
+ version: 1.11.6
+ resolution: "@types/aws4@npm:1.11.6"
+ dependencies:
+ "@types/node": "npm:*"
+ checksum: 10/7b75159338526f27ce55530bba7addd82152acf5db743728f8006a23cfab730f33e4d2bb788cc279a36947a5ef25d23ed0c2484639f2ffaf04e8d3d27911da3a
+ languageName: node
+ linkType: hard
+
"@types/babel__core@npm:^7.20.5":
version: 7.20.5
resolution: "@types/babel__core@npm:7.20.5"
@@ -20619,6 +19326,13 @@ __metadata:
languageName: node
linkType: hard
+"aws4@npm:^1.13.2":
+ version: 1.13.2
+ resolution: "aws4@npm:1.13.2"
+ checksum: 10/290b9f84facbad013747725bfd8b4c42d0b3b04b5620d8418f0219832ef95a7dc597a4af7b1589ae7fce18bacde96f40911c3cda36199dd04d9f8e01f72fa50a
+ languageName: node
+ linkType: hard
+
"axios@npm:^1.8.3":
version: 1.12.2
resolution: "axios@npm:1.12.2"
@@ -20868,13 +19582,6 @@ __metadata:
languageName: node
linkType: hard
-"bowser@npm:^2.11.0":
- version: 2.11.0
- resolution: "bowser@npm:2.11.0"
- checksum: 10/ef46500eafe35072455e7c3ae771244e97827e0626686a9a3601c436d16eb272dad7ccbd49e2130b599b617ca9daa67027de827ffc4c220e02f63c84b69a8751
- languageName: node
- linkType: hard
-
"boxen@npm:7.0.0":
version: 7.0.0
resolution: "boxen@npm:7.0.0"
@@ -25456,17 +24163,6 @@ __metadata:
languageName: node
linkType: hard
-"fast-xml-parser@npm:5.2.5":
- version: 5.2.5
- resolution: "fast-xml-parser@npm:5.2.5"
- dependencies:
- strnum: "npm:^2.1.0"
- bin:
- fxparser: src/cli/cli.js
- checksum: 10/305017cff6968a34cbac597317be1516e85c44f650f30d982c84f8c30043e81fd38d39a8810d570136c921399dd43b9ac4775bdfbbbcfee96456f3c086b48bdd
- languageName: node
- linkType: hard
-
"fast-xml-parser@npm:^5.3.4":
version: 5.3.4
resolution: "fast-xml-parser@npm:5.3.4"
@@ -35101,6 +33797,13 @@ __metadata:
languageName: node
linkType: hard
+"s3mini@npm:^0.9.1":
+ version: 0.9.1
+ resolution: "s3mini@npm:0.9.1"
+ checksum: 10/cefb0caac2d24119193c96bcf798ad6a2a7c89cef9a26638d74a304d39313c669090b15e72fdb6b36be1fd1eae55f1388e3085cc0e52f67a269572546cb8d26d
+ languageName: node
+ linkType: hard
+
"safe-buffer@npm:@nolyfill/safe-buffer@^1":
version: 1.0.44
resolution: "@nolyfill/safe-buffer@npm:1.0.44"