Feat/s3 storage #2

Closed
maaz519 wants to merge 30 commits from feat/s3-storage into main
6 changed files with 479 additions and 4 deletions
Showing only changes of commit 0c6650f3c6 - Show all commits
+9
View File
@@ -59,3 +59,12 @@ IIOS_ATTESTATION_AUDIENCE=iios-core
# When '1', every guarded request MUST carry a valid X-Context-Attestation (else 403).
# Leave OFF until callers (AppShell/be-crm) forward attestations. Verified-if-present regardless.
IIOS_REQUIRE_ATTESTATION=0
# ─── Object storage (media + email attachments) ───────────────────
# Unset → local disk (MEDIA_DIR). Set these → S3-compatible (AWS S3 / MinIO / R2 / Supabase).
# IIOS_S3_ENDPOINT=https://minio.your-server:9000 # omit for AWS S3
# IIOS_S3_BUCKET=iios-media
# IIOS_S3_ACCESS_KEY=...
# IIOS_S3_SECRET_KEY=...
# IIOS_S3_REGION=us-east-1 # any value for MinIO
# IIOS_S3_FORCE_PATH_STYLE=true # true for MinIO/self-hosted
+1
View File
@@ -12,6 +12,7 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1090.0",
"@insignia/iios-adapter-sdk": "workspace:*",
"@insignia/iios-contracts": "workspace:*",
"@nestjs/common": "^11.1.27",
@@ -1,16 +1,26 @@
import { Module } from '@nestjs/common';
import { Module, Logger } from '@nestjs/common';
import { PlatformModule } from '../platform/platform.module';
import { IdentityModule } from '../identity/identity.module';
import { MediaController } from './media.controller';
import { MediaService } from './media.service';
import { LocalDiskStorage } from './local-disk.storage';
import { STORAGE_PORT } from './storage.port';
import { S3Storage, s3ConfigFromEnv } from './s3.storage';
import { STORAGE_PORT, type StoragePort } from './storage.port';
/** S3/MinIO when its env is set (IIOS_S3_BUCKET + keys), else local disk. Env-driven swap. */
function makeStorage(): StoragePort {
const cfg = s3ConfigFromEnv();
if (cfg) {
new Logger('MediaStorage').log(`using S3 storage (bucket "${cfg.bucket}"${cfg.endpoint ? ` @ ${cfg.endpoint}` : ''})`);
return new S3Storage(cfg);
}
return new LocalDiskStorage();
}
@Module({
imports: [PlatformModule, IdentityModule],
controllers: [MediaController],
// Dev binds local disk; prod swaps STORAGE_PORT to an S3/Supabase adapter.
providers: [MediaService, { provide: STORAGE_PORT, useClass: LocalDiskStorage }],
providers: [MediaService, { provide: STORAGE_PORT, useFactory: makeStorage }],
// Exported so the capability layer can resolve email attachment bytes at send time.
exports: [STORAGE_PORT],
})
@@ -0,0 +1,66 @@
import { describe, it, expect } from 'vitest';
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { S3Storage, s3ConfigFromEnv, type S3Config, type S3Like } from './s3.storage';
const CFG: S3Config = { endpoint: 'https://minio.test', region: 'us-east-1', bucket: 'iios', accessKeyId: 'k', secretAccessKey: 's', forcePathStyle: true };
/** A recording stub S3 client; GetObject returns whatever `store[key]` holds (or a NoSuchKey error). */
function stub(store: Record<string, { body: Buffer; mime: string }> = {}) {
const calls: unknown[] = [];
const client: S3Like = {
async send(command: unknown) {
calls.push(command);
if (command instanceof PutObjectCommand) { store[command.input.Key!] = { body: command.input.Body as Buffer, mime: command.input.ContentType ?? '' }; return {}; }
if (command instanceof DeleteObjectCommand) { delete store[command.input.Key!]; return {}; }
if (command instanceof GetObjectCommand) {
const hit = store[command.input.Key!];
if (!hit) throw Object.assign(new Error('missing'), { name: 'NoSuchKey' });
return { Body: { transformToByteArray: async () => new Uint8Array(hit.body) }, ContentType: hit.mime };
}
return {};
},
};
return { client, calls, store };
}
describe('s3ConfigFromEnv', () => {
it('builds config from env (path-style default true for MinIO)', () => {
const cfg = s3ConfigFromEnv({ IIOS_S3_ENDPOINT: 'https://minio.x', IIOS_S3_BUCKET: 'b', IIOS_S3_ACCESS_KEY: 'k', IIOS_S3_SECRET_KEY: 's' } as NodeJS.ProcessEnv);
expect(cfg).toMatchObject({ endpoint: 'https://minio.x', bucket: 'b', region: 'us-east-1', forcePathStyle: true });
});
it('returns null when bucket/keys are missing', () => {
expect(s3ConfigFromEnv({ IIOS_S3_ENDPOINT: 'https://minio.x' } as NodeJS.ProcessEnv)).toBeNull();
});
});
describe('S3Storage (StoragePort over S3/MinIO)', () => {
it('put stores the object and returns size + sha256', async () => {
const s = stub();
const store = new S3Storage(CFG, s.client);
const res = await store.put('scope/obj1', Buffer.from('hello'), 'text/plain');
expect(res.sizeBytes).toBe(5);
expect(res.checksumSha256).toMatch(/^[a-f0-9]{64}$/);
const put = s.calls[0] as PutObjectCommand;
expect(put.input).toMatchObject({ Bucket: 'iios', Key: 'scope/obj1', ContentType: 'text/plain' });
});
it('get round-trips the bytes + mime', async () => {
const s = stub();
const store = new S3Storage(CFG, s.client);
await store.put('scope/obj2', Buffer.from('PDFDATA'), 'application/pdf');
const got = await store.get('scope/obj2');
expect(got).toEqual({ data: Buffer.from('PDFDATA'), mime: 'application/pdf', sizeBytes: 7 });
});
it('get returns null for a missing key (NoSuchKey → null, not throw)', async () => {
const store = new S3Storage(CFG, stub().client);
expect(await store.get('nope')).toBeNull();
});
it('remove issues a DeleteObject', async () => {
const s = stub({ 'k': { body: Buffer.from('x'), mime: 't' } });
await new S3Storage(CFG, s.client).remove('k');
expect(s.calls.some((c) => c instanceof DeleteObjectCommand)).toBe(true);
expect(s.store['k']).toBeUndefined();
});
});
@@ -0,0 +1,87 @@
import { createHash } from 'node:crypto';
import { Injectable } from '@nestjs/common';
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import type { StoragePort } from './storage.port';
export interface S3Config {
endpoint?: string; // MinIO/self-hosted URL; omit for AWS
region: string;
bucket: string;
accessKeyId: string;
secretAccessKey: string;
forcePathStyle: boolean; // true for MinIO
}
/** The one method this adapter uses — lets tests inject a stub client (no live S3/MinIO). */
export interface S3Like {
send(command: unknown): Promise<unknown>;
}
/** Build an S3Config from env, or null if the required bits are missing (→ fall back to disk). */
export function s3ConfigFromEnv(env: NodeJS.ProcessEnv = process.env): S3Config | null {
const bucket = env.IIOS_S3_BUCKET;
const accessKeyId = env.IIOS_S3_ACCESS_KEY;
const secretAccessKey = env.IIOS_S3_SECRET_KEY;
if (!bucket || !accessKeyId || !secretAccessKey) return null;
return {
...(env.IIOS_S3_ENDPOINT ? { endpoint: env.IIOS_S3_ENDPOINT } : {}),
region: env.IIOS_S3_REGION ?? 'us-east-1',
bucket,
accessKeyId,
secretAccessKey,
// MinIO/self-hosted needs path-style; default true unless explicitly disabled for AWS.
forcePathStyle: env.IIOS_S3_FORCE_PATH_STYLE !== 'false',
};
}
/**
* S3-compatible object storage (AWS S3, MinIO, R2, Supabase Storage). A drop-in for LocalDiskStorage:
* same StoragePort contract. sha256 is computed locally on put (S3 doesn't return it), matching the
* disk adapter. A missing object reads back as null (not an error).
*/
@Injectable()
export class S3Storage implements StoragePort {
private readonly client: S3Like;
private readonly bucket: string;
constructor(config: S3Config, client?: S3Like) {
this.bucket = config.bucket;
this.client = client ?? new S3Client({
region: config.region,
...(config.endpoint ? { endpoint: config.endpoint } : {}),
forcePathStyle: config.forcePathStyle,
credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },
});
}
async put(objectKey: string, data: Buffer, mime: string): Promise<{ sizeBytes: number; checksumSha256: string }> {
const checksumSha256 = createHash('sha256').update(data).digest('hex');
await this.client.send(new PutObjectCommand({ Bucket: this.bucket, Key: objectKey, Body: data, ContentType: mime }));
return { sizeBytes: data.length, checksumSha256 };
}
async get(objectKey: string): Promise<{ data: Buffer; mime: string; sizeBytes: number } | null> {
try {
const res = (await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: objectKey }))) as {
Body?: { transformToByteArray(): Promise<Uint8Array> };
ContentType?: string;
};
if (!res.Body) return null;
const bytes = await res.Body.transformToByteArray();
const data = Buffer.from(bytes);
return { data, mime: res.ContentType ?? 'application/octet-stream', sizeBytes: data.length };
} catch (err) {
if (isNotFound(err)) return null;
throw err;
}
}
async remove(objectKey: string): Promise<void> {
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey }));
}
}
function isNotFound(err: unknown): boolean {
const e = err as { name?: string; $metadata?: { httpStatusCode?: number } };
return e.name === 'NoSuchKey' || e.name === 'NotFound' || e.$metadata?.httpStatusCode === 404;
}
+302
View File
@@ -328,6 +328,9 @@ importers:
packages/iios-service:
dependencies:
'@aws-sdk/client-s3':
specifier: ^3.1090.0
version: 3.1090.0
'@insignia/iios-adapter-sdk':
specifier: workspace:*
version: link:../iios-adapter-sdk
@@ -487,6 +490,78 @@ packages:
resolution: {integrity: sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==}
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
'@aws-sdk/checksums@3.1000.18':
resolution: {integrity: sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/client-s3@3.1090.0':
resolution: {integrity: sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==}
engines: {node: '>=20.0.0'}
'@aws-sdk/core@3.975.3':
resolution: {integrity: sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-env@3.972.59':
resolution: {integrity: sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-http@3.972.61':
resolution: {integrity: sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-ini@3.973.4':
resolution: {integrity: sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-login@3.972.66':
resolution: {integrity: sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-node@3.972.70':
resolution: {integrity: sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-process@3.972.59':
resolution: {integrity: sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-sso@3.973.3':
resolution: {integrity: sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-web-identity@3.972.65':
resolution: {integrity: sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/middleware-sdk-s3@3.972.64':
resolution: {integrity: sha512-RBi43anhDBUv+HCfxCOXwGOE7GmT4n7ChV04Mwr22RhXTNcamW/iWnJlOotDPCZSrJ4dEvhZSiWWQMwLX+ZhFA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/nested-clients@3.997.33':
resolution: {integrity: sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/signature-v4-multi-region@3.996.41':
resolution: {integrity: sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==}
engines: {node: '>=20.0.0'}
'@aws-sdk/token-providers@3.1088.0':
resolution: {integrity: sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/types@3.974.2':
resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/xml-builder@3.972.36':
resolution: {integrity: sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==}
engines: {node: '>=20.0.0'}
'@aws/lambda-invoke-store@0.3.0':
resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
engines: {node: '>=18.0.0'}
'@babel/code-frame@7.29.7':
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
engines: {node: '>=6.9.0'}
@@ -1450,6 +1525,30 @@ packages:
cpu: [x64]
os: [win32]
'@smithy/core@3.29.5':
resolution: {integrity: sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==}
engines: {node: '>=18.0.0'}
'@smithy/credential-provider-imds@4.4.10':
resolution: {integrity: sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==}
engines: {node: '>=18.0.0'}
'@smithy/fetch-http-handler@5.6.7':
resolution: {integrity: sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==}
engines: {node: '>=18.0.0'}
'@smithy/node-http-handler@4.9.7':
resolution: {integrity: sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==}
engines: {node: '>=18.0.0'}
'@smithy/signature-v4@5.6.6':
resolution: {integrity: sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==}
engines: {node: '>=18.0.0'}
'@smithy/types@4.16.1':
resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==}
engines: {node: '>=18.0.0'}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@@ -1766,6 +1865,9 @@ packages:
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
engines: {node: '>=18'}
bowser@2.14.1:
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
brace-expansion@1.1.15:
resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
@@ -3362,6 +3464,171 @@ snapshots:
transitivePeerDependencies:
- chokidar
'@aws-sdk/checksums@3.1000.18':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/client-s3@3.1090.0':
dependencies:
'@aws-sdk/checksums': 3.1000.18
'@aws-sdk/core': 3.975.3
'@aws-sdk/credential-provider-node': 3.972.70
'@aws-sdk/middleware-sdk-s3': 3.972.64
'@aws-sdk/signature-v4-multi-region': 3.996.41
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/fetch-http-handler': 5.6.7
'@smithy/node-http-handler': 4.9.7
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/core@3.975.3':
dependencies:
'@aws-sdk/types': 3.974.2
'@aws-sdk/xml-builder': 3.972.36
'@aws/lambda-invoke-store': 0.3.0
'@smithy/core': 3.29.5
'@smithy/signature-v4': 5.6.6
'@smithy/types': 4.16.1
bowser: 2.14.1
tslib: 2.8.1
'@aws-sdk/credential-provider-env@3.972.59':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-http@3.972.61':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/fetch-http-handler': 5.6.7
'@smithy/node-http-handler': 4.9.7
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-ini@3.973.4':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/credential-provider-env': 3.972.59
'@aws-sdk/credential-provider-http': 3.972.61
'@aws-sdk/credential-provider-login': 3.972.66
'@aws-sdk/credential-provider-process': 3.972.59
'@aws-sdk/credential-provider-sso': 3.973.3
'@aws-sdk/credential-provider-web-identity': 3.972.65
'@aws-sdk/nested-clients': 3.997.33
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/credential-provider-imds': 4.4.10
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-login@3.972.66':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/nested-clients': 3.997.33
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-node@3.972.70':
dependencies:
'@aws-sdk/credential-provider-env': 3.972.59
'@aws-sdk/credential-provider-http': 3.972.61
'@aws-sdk/credential-provider-ini': 3.973.4
'@aws-sdk/credential-provider-process': 3.972.59
'@aws-sdk/credential-provider-sso': 3.973.3
'@aws-sdk/credential-provider-web-identity': 3.972.65
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/credential-provider-imds': 4.4.10
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-process@3.972.59':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-sso@3.973.3':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/nested-clients': 3.997.33
'@aws-sdk/token-providers': 3.1088.0
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-web-identity@3.972.65':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/nested-clients': 3.997.33
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/middleware-sdk-s3@3.972.64':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/signature-v4-multi-region': 3.996.41
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/nested-clients@3.997.33':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/signature-v4-multi-region': 3.996.41
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/fetch-http-handler': 5.6.7
'@smithy/node-http-handler': 4.9.7
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/signature-v4-multi-region@3.996.41':
dependencies:
'@aws-sdk/types': 3.974.2
'@smithy/signature-v4': 5.6.6
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/token-providers@3.1088.0':
dependencies:
'@aws-sdk/core': 3.975.3
'@aws-sdk/nested-clients': 3.997.33
'@aws-sdk/types': 3.974.2
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/types@3.974.2':
dependencies:
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/xml-builder@3.972.36':
dependencies:
'@smithy/types': 4.16.1
tslib: 2.8.1
'@aws/lambda-invoke-store@0.3.0': {}
'@babel/code-frame@7.29.7':
dependencies:
'@babel/helper-validator-identifier': 7.29.7
@@ -4105,6 +4372,39 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.62.2':
optional: true
'@smithy/core@3.29.5':
dependencies:
'@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/credential-provider-imds@4.4.10':
dependencies:
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/fetch-http-handler@5.6.7':
dependencies:
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/node-http-handler@4.9.7':
dependencies:
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/signature-v4@5.6.6':
dependencies:
'@smithy/core': 3.29.5
'@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/types@4.16.1':
dependencies:
tslib: 2.8.1
'@socket.io/component-emitter@3.1.2': {}
'@socket.io/redis-adapter@8.3.0(socket.io-adapter@2.5.8)':
@@ -4492,6 +4792,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
bowser@2.14.1: {}
brace-expansion@1.1.15:
dependencies:
balanced-match: 1.0.2