diff --git a/.changeset/two-sites-bow.md b/.changeset/two-sites-bow.md new file mode 100644 index 00000000..9f19c427 --- /dev/null +++ b/.changeset/two-sites-bow.md @@ -0,0 +1,5 @@ +--- +"medialit": minor +--- + +Uploads are temporary by default diff --git a/.migrations/00005-migrate-to-dual-bucket-architecture.js b/.migrations/00005-migrate-to-dual-bucket-architecture.js new file mode 100644 index 00000000..91e5fd51 --- /dev/null +++ b/.migrations/00005-migrate-to-dual-bucket-architecture.js @@ -0,0 +1,4 @@ +db.media.updateMany( + { accessControl: "public-read" }, + { $set: { accessControl: "public" } }, +); diff --git a/.prettierignore b/.prettierignore index e116e54a..eedc7ff3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ node_modules dist **/.next -apps/docs/.source \ No newline at end of file +apps/docs/.source +apps/docs/out \ No newline at end of file diff --git a/README.md b/README.md index c7a272ca..96555c08 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Before you start uploading to your bucket, make sure you have set up the correct If you need to use a Cloudfront CDN, you can enable it in the app, by setting up the following values in your .env file. ```sh -USE_CLOUDFRONT=true -CLOUDFRONT_ENDPOINT=CLOUDFRONT_DISTRIBUTION_NAME +ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT=true +CDN_ENDPOINT=CLOUDFRONT_DISTRIBUTION_NAME CLOUDFRONT_PRIVATE_KEY="PRIVATE_KEY" CLOUDFRONT_KEY_PAIR_ID=KEY_PAIR_ID ``` diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 0a1c869b..321f1418 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-slim AS base +FROM node:24-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable diff --git a/apps/api/__tests__/media/utils/get-public-urls.test.ts b/apps/api/__tests__/media/utils/get-public-urls.test.ts new file mode 100644 index 00000000..dfdb4164 --- /dev/null +++ b/apps/api/__tests__/media/utils/get-public-urls.test.ts @@ -0,0 +1,348 @@ +import { describe, test, beforeEach, afterEach } from "node:test"; +import assert from "node:assert"; +import { Constants, Media } from "@medialit/models"; + +// Helper to clear module cache and re-import +const clearModuleCache = () => { + const modulePath = require.resolve("@/media/utils/get-public-urls"); + const constantsPath = require.resolve("@/config/constants"); + delete require.cache[modulePath]; + delete require.cache[constantsPath]; +}; + +// Helper to create mock media +const createMockMedia = (overrides: Partial = {}): Media => ({ + fileName: "main.jpg", + mediaId: "test-media-id-123", + apikey: "test-apikey", + originalFileName: "original.jpg", + mimeType: "image/jpeg", + size: 1024, + thumbnailGenerated: true, + accessControl: Constants.AccessControl.PUBLIC, + ...overrides, +}); + +describe("get-public-urls", () => { + const originalEnv: Record = {}; + + beforeEach(() => { + // Save original env vars + originalEnv.CDN_ENDPOINT = process.env.CDN_ENDPOINT; + originalEnv.CLOUD_ENDPOINT = process.env.CLOUD_ENDPOINT; + originalEnv.CLOUD_ENDPOINT_PUBLIC = process.env.CLOUD_ENDPOINT_PUBLIC; + originalEnv.PATH_PREFIX = process.env.PATH_PREFIX; + }); + + afterEach(() => { + // Restore original env vars + if (originalEnv.CDN_ENDPOINT !== undefined) { + process.env.CDN_ENDPOINT = originalEnv.CDN_ENDPOINT; + } else { + delete process.env.CDN_ENDPOINT; + } + if (originalEnv.CLOUD_ENDPOINT !== undefined) { + process.env.CLOUD_ENDPOINT = originalEnv.CLOUD_ENDPOINT; + } else { + delete process.env.CLOUD_ENDPOINT; + } + if (originalEnv.CLOUD_ENDPOINT_PUBLIC !== undefined) { + process.env.CLOUD_ENDPOINT_PUBLIC = + originalEnv.CLOUD_ENDPOINT_PUBLIC; + } else { + delete process.env.CLOUD_ENDPOINT_PUBLIC; + } + if (originalEnv.PATH_PREFIX !== undefined) { + process.env.PATH_PREFIX = originalEnv.PATH_PREFIX; + } else { + delete process.env.PATH_PREFIX; + } + clearModuleCache(); + }); + + describe("getPublicFileUrl", () => { + test("should use CDN_ENDPOINT when provided (takes precedence)", () => { + process.env.CDN_ENDPOINT = "https://cdn.example.com"; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + fileName: "main.jpg", + }); + + const url = getPublicFileUrl(media); + assert.strictEqual( + url, + `https://cdn.example.com/${Constants.PathKey.PUBLIC}/test-media-id-123/main.jpg`, + ); + }); + + test("should use CLOUD_ENDPOINT_PUBLIC when CDN_ENDPOINT not provided and media is public", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + fileName: "main.png", + }); + + const url = getPublicFileUrl(media); + assert.strictEqual( + url, + `https://public.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/main.png`, + ); + }); + + test("should fallback to CLOUD_ENDPOINT when CLOUD_ENDPOINT_PUBLIC not provided and media is public", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + delete process.env.CLOUD_ENDPOINT_PUBLIC; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + fileName: "main.webp", + }); + + const url = getPublicFileUrl(media); + assert.strictEqual( + url, + `https://private.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/main.webp`, + ); + }); + + test("should use CLOUD_ENDPOINT when media is private (defensive check)", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PRIVATE, + fileName: "main.jpg", + }); + + const url = getPublicFileUrl(media); + assert.strictEqual( + url, + `https://private.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/main.jpg`, + ); + }); + + test("should include PATH_PREFIX when provided", () => { + process.env.CDN_ENDPOINT = "https://cdn.example.com"; + process.env.PATH_PREFIX = "tenant-123"; + clearModuleCache(); + + const { + getPublicFileUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + fileName: "main.jpg", + }); + + const url = getPublicFileUrl(media); + assert.strictEqual( + url, + `https://cdn.example.com/tenant-123/${Constants.PathKey.PUBLIC}/test-media-id-123/main.jpg`, + ); + }); + }); + + describe("getThumbnailUrl", () => { + test("should use CDN_ENDPOINT when provided (takes precedence)", () => { + process.env.CDN_ENDPOINT = "https://cdn.example.com"; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia(); + + const url = getThumbnailUrl(media); + assert.strictEqual( + url, + `https://cdn.example.com/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + + test("should use CLOUD_ENDPOINT_PUBLIC when CDN_ENDPOINT not provided", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia(); + + const url = getThumbnailUrl(media); + assert.strictEqual( + url, + `https://public.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + + test("should use CLOUD_ENDPOINT_PUBLIC when CDN_ENDPOINT not provided (validation ensures it exists)", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = "https://private.s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia(); + + const url = getThumbnailUrl(media); + assert.strictEqual( + url, + `https://public.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + + test("should include PATH_PREFIX when provided", () => { + process.env.CDN_ENDPOINT = "https://cdn.example.com"; + process.env.PATH_PREFIX = "tenant-456"; + clearModuleCache(); + + const { + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia(); + + const url = getThumbnailUrl(media); + assert.strictEqual( + url, + `https://cdn.example.com/tenant-456/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + }); + + describe("Progressive behavior", () => { + test("Scenario 1: Base setup - CLOUD_ENDPOINT + CLOUD_ENDPOINT_PUBLIC (no CDN)", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = + "https://private.r2.cloudflarestorage.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.r2.cloudflarestorage.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + }); + + const mainUrl = getPublicFileUrl(media); + const thumbUrl = getThumbnailUrl(media); + + assert.strictEqual( + mainUrl, + `https://public.r2.cloudflarestorage.com/${Constants.PathKey.PUBLIC}/test-media-id-123/main.jpg`, + ); + assert.strictEqual( + thumbUrl, + `https://public.r2.cloudflarestorage.com/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + + test("Scenario 2: With CDN_ENDPOINT (progressive enhancement)", () => { + process.env.CDN_ENDPOINT = "https://cdn.medialit.cloud"; + process.env.CLOUD_ENDPOINT = + "https://private.r2.cloudflarestorage.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.r2.cloudflarestorage.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + }); + + const mainUrl = getPublicFileUrl(media); + const thumbUrl = getThumbnailUrl(media); + + // CDN should take precedence + assert.strictEqual( + mainUrl, + `https://cdn.medialit.cloud/${Constants.PathKey.PUBLIC}/test-media-id-123/main.jpg`, + ); + assert.strictEqual( + thumbUrl, + `https://cdn.medialit.cloud/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + + test("Scenario 3: CLOUD_ENDPOINT + CLOUD_ENDPOINT_PUBLIC (no CDN, validation ensures both exist)", () => { + delete process.env.CDN_ENDPOINT; + process.env.CLOUD_ENDPOINT = "https://s3.amazonaws.com"; + process.env.CLOUD_ENDPOINT_PUBLIC = + "https://public.s3.amazonaws.com"; + process.env.PATH_PREFIX = ""; + clearModuleCache(); + + const { + getPublicFileUrl, + getThumbnailUrl, + } = require("@/media/utils/get-public-urls"); + const media = createMockMedia({ + accessControl: Constants.AccessControl.PUBLIC, + }); + + const mainUrl = getPublicFileUrl(media); + const thumbUrl = getThumbnailUrl(media); + + // Main files use CLOUD_ENDPOINT_PUBLIC for public media + assert.strictEqual( + mainUrl, + `https://public.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/main.jpg`, + ); + // Thumbnails always use CLOUD_ENDPOINT_PUBLIC + assert.strictEqual( + thumbUrl, + `https://public.s3.amazonaws.com/${Constants.PathKey.PUBLIC}/test-media-id-123/thumb.webp`, + ); + }); + }); +}); diff --git a/apps/api/__tests__/services/s3.test.ts b/apps/api/__tests__/services/s3.test.ts new file mode 100644 index 00000000..e0f6ca10 --- /dev/null +++ b/apps/api/__tests__/services/s3.test.ts @@ -0,0 +1,150 @@ +import { describe, test, beforeEach, afterEach } from "node:test"; +import assert from "node:assert"; + +// Helper to clear module cache and re-import +const clearModuleCache = () => { + const modulePath = require.resolve("@/services/s3"); + const constantsPath = require.resolve("@/config/constants"); + delete require.cache[modulePath]; + delete require.cache[constantsPath]; +}; + +describe("S3 Client Configuration", () => { + const originalEnv: Record = {}; + + beforeEach(() => { + // Save original env vars + originalEnv.CLOUD_ENDPOINT = process.env.CLOUD_ENDPOINT; + originalEnv.CLOUD_ENDPOINT_PUBLIC = process.env.CLOUD_ENDPOINT_PUBLIC; + originalEnv.CLOUD_BUCKET_NAME = process.env.CLOUD_BUCKET_NAME; + originalEnv.CLOUD_PUBLIC_BUCKET_NAME = + process.env.CLOUD_PUBLIC_BUCKET_NAME; + }); + + afterEach(() => { + // Restore original env vars + if (originalEnv.CLOUD_ENDPOINT !== undefined) { + process.env.CLOUD_ENDPOINT = originalEnv.CLOUD_ENDPOINT; + } else { + delete process.env.CLOUD_ENDPOINT; + } + if (originalEnv.CLOUD_ENDPOINT_PUBLIC !== undefined) { + process.env.CLOUD_ENDPOINT_PUBLIC = + originalEnv.CLOUD_ENDPOINT_PUBLIC; + } else { + delete process.env.CLOUD_ENDPOINT_PUBLIC; + } + if (originalEnv.CLOUD_BUCKET_NAME !== undefined) { + process.env.CLOUD_BUCKET_NAME = originalEnv.CLOUD_BUCKET_NAME; + } else { + delete process.env.CLOUD_BUCKET_NAME; + } + if (originalEnv.CLOUD_PUBLIC_BUCKET_NAME !== undefined) { + process.env.CLOUD_PUBLIC_BUCKET_NAME = + originalEnv.CLOUD_PUBLIC_BUCKET_NAME; + } else { + delete process.env.CLOUD_PUBLIC_BUCKET_NAME; + } + clearModuleCache(); + }); + + describe("Private S3 Client Config", () => { + test("should include endpoint and forcePathStyle when CLOUD_ENDPOINT is set (non-AWS)", () => { + process.env.CLOUD_ENDPOINT = "http://localhost:9000"; + process.env.CLOUD_REGION = "us-east-1"; + process.env.CLOUD_KEY = "test-key"; + process.env.CLOUD_SECRET = "test-secret"; + process.env.CLOUD_BUCKET_NAME = "test-bucket"; + clearModuleCache(); + + const { getPrivateS3ClientConfig } = require("@/services/s3"); + const config = getPrivateS3ClientConfig(); + + assert.ok(config !== undefined, "config should be defined"); + assert.strictEqual( + config.endpoint, + "http://localhost:9000", + "endpoint should be set when CLOUD_ENDPOINT is provided", + ); + assert.strictEqual( + config.forcePathStyle, + true, + "forcePathStyle should be true for non-AWS endpoints", + ); + }); + + test("should not include endpoint or forcePathStyle when CLOUD_ENDPOINT is not set", () => { + delete process.env.CLOUD_ENDPOINT; + process.env.CLOUD_REGION = "us-east-1"; + process.env.CLOUD_KEY = "test-key"; + process.env.CLOUD_SECRET = "test-secret"; + process.env.CLOUD_BUCKET_NAME = "test-bucket"; + clearModuleCache(); + + const { getPrivateS3ClientConfig } = require("@/services/s3"); + const config = getPrivateS3ClientConfig(); + + assert.ok(config !== undefined, "config should be defined"); + assert.strictEqual( + config.endpoint, + undefined, + "endpoint should not be set when CLOUD_ENDPOINT is not provided", + ); + assert.strictEqual( + config.forcePathStyle, + undefined, + "forcePathStyle should not be set when CLOUD_ENDPOINT is not provided", + ); + }); + }); + + describe("Public S3 Client Config", () => { + test("should include endpoint and forcePathStyle when CLOUD_ENDPOINT_PUBLIC is set (non-AWS)", () => { + process.env.CLOUD_ENDPOINT_PUBLIC = "http://localhost:9001"; + process.env.CLOUD_REGION = "us-east-1"; + process.env.CLOUD_KEY = "test-key"; + process.env.CLOUD_SECRET = "test-secret"; + process.env.CLOUD_PUBLIC_BUCKET_NAME = "test-public-bucket"; + clearModuleCache(); + + const { getPublicS3ClientConfig } = require("@/services/s3"); + const config = getPublicS3ClientConfig(); + + assert.ok(config !== undefined, "config should be defined"); + assert.strictEqual( + config.endpoint, + "http://localhost:9001", + "endpoint should be set when CLOUD_ENDPOINT_PUBLIC is provided", + ); + assert.strictEqual( + config.forcePathStyle, + true, + "forcePathStyle should be true for non-AWS endpoints", + ); + }); + + test("should not include endpoint or forcePathStyle when CLOUD_ENDPOINT_PUBLIC is not set", () => { + delete process.env.CLOUD_ENDPOINT_PUBLIC; + process.env.CLOUD_REGION = "us-east-1"; + process.env.CLOUD_KEY = "test-key"; + process.env.CLOUD_SECRET = "test-secret"; + process.env.CLOUD_PUBLIC_BUCKET_NAME = "test-public-bucket"; + clearModuleCache(); + + const { getPublicS3ClientConfig } = require("@/services/s3"); + const config = getPublicS3ClientConfig(); + + assert.ok(config !== undefined, "config should be defined"); + assert.strictEqual( + config.endpoint, + undefined, + "endpoint should not be set when CLOUD_ENDPOINT_PUBLIC is not provided", + ); + assert.strictEqual( + config.forcePathStyle, + undefined, + "forcePathStyle should not be set when CLOUD_ENDPOINT_PUBLIC is not provided", + ); + }); + }); +}); diff --git a/apps/api/package.json b/apps/api/package.json index 71725386..151e82fa 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "@medialit/api", - "version": "0.0.28", + "version": "0.2.2", "private": true, "description": "A better API to manage your files on AWS S3 compatible cloud storages", "author": { @@ -31,9 +31,9 @@ "test": "node --import tsx --test '**/*.test.ts'" }, "dependencies": { - "@aws-sdk/client-s3": "^3.55.0", + "@aws-sdk/client-s3": "^3.922.0", "@aws-sdk/cloudfront-signer": "^3.572.0", - "@aws-sdk/s3-request-presigner": "^3.55.0", + "@aws-sdk/s3-request-presigner": "^3.922.0", "@medialit/images": "workspace:*", "@medialit/models": "workspace:*", "@medialit/thumbnail": "workspace:*", @@ -42,14 +42,14 @@ "@tus/server": "^2.3.0", "aws-sdk": "^2.1692.0", "cors": "^2.8.5", - "dotenv": "^16.4.7", + "dotenv": "^17.2.3", "express": "^4.2.0", - "express-fileupload": "^1.3.1", + "express-fileupload": "^1.5.2", "joi": "^17.6.0", - "mongoose": "^8.0.1", - "passport": "^0.5.2", - "passport-jwt": "^4.0.0", - "pino": "^7.9.1" + "mongoose": "^8.19.3", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "pino": "^10.1.0" }, "devDependencies": { "@types/cors": "^2.8.12", @@ -62,9 +62,10 @@ "@typescript-eslint/eslint-plugin": "^5.17.0", "@typescript-eslint/parser": "^5.17.0", "eslint": "^8.12.0", - "nodemon": "^3.0.3", - "ts-node": "^10.7.0", - "tsx": "^4.7.0", - "typescript": "^5.2.2" + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3" } } diff --git a/apps/api/src/config/constants.ts b/apps/api/src/config/constants.ts index 86f31abb..f520c670 100644 --- a/apps/api/src/config/constants.ts +++ b/apps/api/src/config/constants.ts @@ -45,22 +45,30 @@ export const mailFrom = process.env.EMAIL_FROM; export const mailPort = parseInt(process.env.EMAIL_PORT || "") || 587; // AWS S3 config -export const cloudEndpoint = process.env.CLOUD_ENDPOINT || ""; +export const CLOUD_ENDPOINT = process.env.CLOUD_ENDPOINT || ""; +export const CLOUD_ENDPOINT_PUBLIC = process.env.CLOUD_ENDPOINT_PUBLIC || ""; export const cloudRegion = process.env.CLOUD_REGION || ""; export const cloudKey = process.env.CLOUD_KEY || ""; export const cloudSecret = process.env.CLOUD_SECRET || ""; export const cloudBucket = process.env.CLOUD_BUCKET_NAME || ""; -export const CLOUD_PREFIX = process.env.CLOUD_PREFIX || ""; -export const S3_ENDPOINT = process.env.S3_ENDPOINT || ""; +export const cloudPublicBucket = process.env.CLOUD_PUBLIC_BUCKET_NAME || ""; +export const PATH_PREFIX = process.env.PATH_PREFIX || ""; +export const HOUR_IN_SECONDS = 1000 * 60 * 60; // Cloudfront config -export const USE_CLOUDFRONT = process.env.USE_CLOUDFRONT === "true"; -export const CLOUDFRONT_ENDPOINT = process.env.CLOUDFRONT_ENDPOINT || ""; +export const ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT = + process.env.ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT === "true"; export const CLOUDFRONT_KEY_PAIR_ID = process.env.CLOUDFRONT_KEY_PAIR_ID || ""; export const CLOUDFRONT_PRIVATE_KEY = process.env.CLOUDFRONT_PRIVATE_KEY || ""; export const CDN_MAX_AGE = process.env.CDN_MAX_AGE ? +process.env.CDN_MAX_AGE - : 1000 * 60 * 60; // one hour + : HOUR_IN_SECONDS; // one hour -export const ENDPOINT = USE_CLOUDFRONT ? CLOUDFRONT_ENDPOINT : S3_ENDPOINT; -export const HOSTNAME_OVERRIDE = process.env.HOSTNAME_OVERRIDE || ""; // Useful for hosting via Docker +// CDN config +export const CDN_ENDPOINT = process.env.CDN_ENDPOINT || ""; + +export const TEMP_MEDIA_EXPIRATION_HOURS = process.env + .TEMP_MEDIA_EXPIRATION_HOURS + ? +process.env.TEMP_MEDIA_EXPIRATION_HOURS + : 24; // 24 hours +export const DISABLE_TAGGING = process.env.DISABLE_TAGGING === "true"; diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 8a5a8ab5..1dd61294 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -13,7 +13,9 @@ import { createUser, findByEmail } from "./user/queries"; import { Apikey, User } from "@medialit/models"; import { createApiKey } from "./apikey/queries"; import { spawn } from "child_process"; -import { Cleanup } from "./tus/cleanup"; +import { cleanupTUSUploads } from "./tus/cleanup"; +import { cleanupExpiredTempUploads } from "./media/cleanup"; +import { HOUR_IN_SECONDS } from "./config/constants"; connectToDatabase(); const app = express(); @@ -34,25 +36,75 @@ app.use("/media/signature", signatureRoutes); app.use("/media", tusRoutes); app.use("/media", mediaRoutes); +app.get("/cleanup/temp", async (req, res) => { + await cleanupExpiredTempUploads(); + res.status(200).json({ + message: "Expired temp uploads cleaned up", + }); +}); +app.get("/cleanup/tus", async (req, res) => { + await cleanupTUSUploads(); + res.status(200).json({ + message: "Expired tus uploads cleaned up", + }); +}); + const port = process.env.PORT || 80; if (process.env.EMAIL) { createAdminUser(); } -checkDependencies().then(() => { - app.listen(port, () => { - logger.info(`Medialit server running at ${port}`); +checkConfig() + .then(checkDependencies) + .then(() => { + app.listen(port, () => { + logger.info(`Medialit server running at ${port}`); + }); + + // Setup background cleanup job for expired tus uploads + setInterval( + async () => { + await cleanupTUSUploads(); + }, + HOUR_IN_SECONDS, // 1 hour + ); + + // Setup background cleanup job for expired temp uploads + setInterval( + async () => { + await cleanupExpiredTempUploads(); + }, + HOUR_IN_SECONDS, // 1 hour + ); }); - // Setup background cleanup job for expired tus uploads - setInterval( - async () => { - await Cleanup(); - }, - 1000 * 60 * 60, // 1 hours - ); -}); +async function checkConfig() { + if (!process.env.DB_CONNECTION_STRING) { + throw new Error("DB_CONNECTION_STRING is not set"); + } + if (!process.env.CLOUD_KEY || !process.env.CLOUD_SECRET) { + throw new Error( + "Cloud credentials (CLOUD_KEY and CLOUD_SECRET) are not set", + ); + } + if ( + !process.env.CLOUD_BUCKET_NAME || + !process.env.CLOUD_PUBLIC_BUCKET_NAME + ) { + throw new Error( + "Cloud bucket name (CLOUD_BUCKET_NAME and CLOUD_PUBLIC_BUCKET_NAME) are not set", + ); + } + if ( + !process.env.CDN_ENDPOINT && + (!process.env.CLOUD_ENDPOINT || !process.env.CLOUD_ENDPOINT_PUBLIC) + ) { + throw new Error( + "If CDN_ENDPOINT is not set, both CLOUD_ENDPOINT and CLOUD_ENDPOINT_PUBLIC must be provided", + ); + } +} async function checkDependencies() { try { diff --git a/apps/api/src/media/GetPageProps.ts b/apps/api/src/media/GetPageProps.ts index 794e4ed7..9bdfb50b 100644 --- a/apps/api/src/media/GetPageProps.ts +++ b/apps/api/src/media/GetPageProps.ts @@ -1,9 +1,10 @@ +import { AccessControl } from "@medialit/models"; import mongoose from "mongoose"; export default interface GetPageProps { userId: mongoose.Types.ObjectId; apikey: string; - access: "public-read" | "private"; + access: AccessControl; page: number; recordsPerPage: number; group?: string; diff --git a/apps/api/src/media/cleanup.ts b/apps/api/src/media/cleanup.ts new file mode 100644 index 00000000..3a2223ad --- /dev/null +++ b/apps/api/src/media/cleanup.ts @@ -0,0 +1,55 @@ +import logger from "../services/log"; +import MediaModel from "./model"; +import { deleteFolder } from "../services/s3"; +import { + PATH_PREFIX, + TEMP_MEDIA_EXPIRATION_HOURS, + cloudBucket, +} from "../config/constants"; +import { Constants } from "@medialit/models"; + +export async function cleanupExpiredTempUploads(): Promise { + const cutoff = new Date( + Date.now() - TEMP_MEDIA_EXPIRATION_HOURS * 1000 * 60 * 60, + ); + + try { + const expired = await MediaModel.find({ + temp: true, + createdAt: { $lt: cutoff }, + }).lean(); + + if (expired.length === 0) { + logger.info("No expired temp uploads found to cleanup"); + return; + } + + logger.info( + { count: expired.length }, + "Found expired temp uploads to cleanup", + ); + + let count = 0; + for (const media of expired) { + try { + // Delete S3 objects from private bucket (using private path prefix) + const tmpPrefix = `${PATH_PREFIX ? `${PATH_PREFIX}/` : ""}${Constants.PathKey.PRIVATE}/${media.mediaId}/`; + await deleteFolder(tmpPrefix, cloudBucket); + + // Delete media record + await MediaModel.deleteOne({ _id: media._id }); + count++; + } catch (err: any) { + logger.error( + { err, mediaId: media.mediaId }, + "Error cleaning up expired temp upload", + ); + } + } + logger.info({ count }, "Cleaned up expired temp uploads"); + } catch (err: any) { + logger.error({ err }, "Error in cleanupExpiredTempUploads"); + } +} + +export default cleanupExpiredTempUploads; diff --git a/apps/api/src/media/handlers.ts b/apps/api/src/media/handlers.ts index ec145076..79659776 100644 --- a/apps/api/src/media/handlers.ts +++ b/apps/api/src/media/handlers.ts @@ -32,7 +32,7 @@ export async function uploadMedia( res: any, next: (...args: any[]) => void, ) { - req.socket.setTimeout(10 * 60 * 1000); + req.socket.setTimeout(10 * 60 * 1000); // 10 minutes if (!req.files || !req.files.file) { return res.status(400).json({ error: FILE_IS_REQUIRED }); @@ -75,7 +75,7 @@ export async function uploadMedia( return res.status(200).json(media); } catch (err: any) { logger.error({ err }, err.message); - res.status(500).json({ error: err.message }); + return res.status(500).json({ error: err.message }); } } @@ -111,7 +111,7 @@ export async function getMedia( return res.status(200).json(result); } catch (err: any) { logger.error({ err }, err.message); - return res.status(500).json(err.message); + return res.status(500).json({ error: err.message }); } } @@ -123,7 +123,7 @@ export async function getMediaCount(req: any, res: any) { const totalMediaFiles = await getCount({ userId, apikey }); return res.status(200).json({ count: totalMediaFiles }); } catch (err: any) { - return res.status(500).json(err.message); + return res.status(500).json({ error: err.message }); } } @@ -140,7 +140,7 @@ export async function getTotalSpaceOccupied(req: any, res: any) { : maxStorageAllowedNotSubscribed, }); } catch (err: any) { - return res.status(500).json(err.message); + return res.status(500).json({ error: err.message }); } } @@ -160,7 +160,7 @@ export async function getMediaDetails(req: any, res: any) { return res.status(200).json(media); } catch (err: any) { logger.error({ err }, err.message); - return res.status(500).json(err.message); + return res.status(500).json({ error: err.message }); } } @@ -177,6 +177,37 @@ export async function deleteMedia(req: any, res: any) { return res.status(200).json({ message: SUCCESS }); } catch (err: any) { logger.error({ err }, err.message); - return res.status(500).json(err.message); + return res.status(500).json({ error: err.message }); + } +} + +export async function sealMedia(req: any, res: any) { + const { mediaId } = req.params; + + try { + const media = await mediaService.sealMedia({ + userId: req.user.id, + apikey: req.apikey, + mediaId, + }); + + const mediaDetails = await mediaService.getMediaDetails({ + userId: req.user.id, + apikey: req.apikey, + mediaId: media.mediaId, + }); + + return res.status(200).json(mediaDetails); + } catch (err: any) { + const statusCode = + err.message === "Media not found" + ? 404 + : err.message === "Media is already sealed" + ? 409 + : 500; + if (statusCode === 500) { + logger.error({ err }, err.message); + } + return res.status(statusCode).json({ error: err.message }); } } diff --git a/apps/api/src/media/model.ts b/apps/api/src/media/model.ts index f5fbb92a..68ba5d60 100644 --- a/apps/api/src/media/model.ts +++ b/apps/api/src/media/model.ts @@ -1,30 +1,4 @@ -import { Media } from "@medialit/models"; +import { MediaSchema } from "@medialit/models"; import mongoose from "mongoose"; -export type MediaWithUserId = Media & { userId: mongoose.Types.ObjectId }; - -const MediaSchema = new mongoose.Schema( - { - fileName: { type: String, required: true }, - mediaId: { type: String, required: true }, - userId: { type: mongoose.Schema.Types.ObjectId, required: true }, - apikey: { type: String, required: true }, - originalFileName: { type: String, required: true }, - mimeType: { type: String, required: true }, - size: { type: Number, required: true }, - thumbnailGenerated: { type: Boolean, required: true, default: false }, - accessControl: { type: String, required: true, default: "private" }, - group: { type: String }, - caption: { type: String }, - }, - { - timestamps: true, - }, -); - -MediaSchema.index({ - originalFileName: "text", - caption: "text", -}); - export default mongoose.models.Media || mongoose.model("Media", MediaSchema); diff --git a/apps/api/src/media/queries.ts b/apps/api/src/media/queries.ts index a9b26a7c..5624c735 100644 --- a/apps/api/src/media/queries.ts +++ b/apps/api/src/media/queries.ts @@ -1,7 +1,8 @@ -import mongoose from "mongoose"; +import mongoose, { FilterQuery } from "mongoose"; import { numberOfRecordsPerPage } from "../config/constants"; import GetPageProps from "./GetPageProps"; -import MediaModel, { MediaWithUserId } from "./model"; +import MediaModel from "./model"; +import { Constants, type MediaWithUserId } from "@medialit/models"; export async function getMedia({ userId, @@ -16,6 +17,7 @@ export async function getMedia({ mediaId, apikey, userId, + // temp: { $ne: true }, }).lean()) as MediaWithUserId | null; } @@ -26,7 +28,11 @@ export async function getMediaCount({ userId: string; apikey: string; }): Promise { - return await MediaModel.countDocuments({ apikey, userId }).lean(); + return await MediaModel.countDocuments({ + apikey, + userId, + temp: { $ne: true }, + }).lean(); } export async function getTotalSpace({ @@ -36,7 +42,9 @@ export async function getTotalSpace({ userId: mongoose.Types.ObjectId; apikey?: string; }): Promise { - const query = apikey ? { userId, apikey } : { userId }; + const query = apikey + ? { userId, apikey, temp: { $ne: true } } + : { userId, temp: { $ne: true } }; const result = await MediaModel.aggregate([ { $match: query, @@ -70,9 +78,16 @@ export async function getPaginatedMedia({ group, recordsPerPage, }: GetPageProps): Promise { - const query: Partial = { userId, apikey }; + const query: FilterQuery = { + userId, + apikey, + temp: { $ne: true }, + }; if (access) { - query.accessControl = access === "private" ? "private" : "public-read"; + query.accessControl = + access === Constants.AccessControl.PRIVATE + ? Constants.AccessControl.PRIVATE + : Constants.AccessControl.PUBLIC; } if (group) { query.group = group; @@ -102,32 +117,10 @@ export async function deleteMediaQuery( return await MediaModel.deleteOne({ userId, mediaId }); } -export async function createMedia({ - fileName, - mediaId, - userId, - apikey, - originalFileName, - mimeType, - size, - thumbnailGenerated, - caption, - accessControl, - group, -}: MediaWithUserId): Promise { - const media: MediaWithUserId = await MediaModel.create({ - fileName, - mediaId, - userId, - apikey, - originalFileName, - mimeType, - size, - thumbnailGenerated, - caption, - accessControl, - group, - }); +export async function createMedia( + mediaData: MediaWithUserId, +): Promise { + const media: MediaWithUserId = await MediaModel.create(mediaData); return media; } diff --git a/apps/api/src/media/routes.ts b/apps/api/src/media/routes.ts index f26f957c..c2d72ac7 100644 --- a/apps/api/src/media/routes.ts +++ b/apps/api/src/media/routes.ts @@ -11,6 +11,7 @@ import { getMediaDetails, uploadMedia, deleteMedia, + sealMedia, getMediaCount, getTotalSpaceOccupied, } from "./handlers"; @@ -51,6 +52,7 @@ router.post("/get/count", apikey, getMediaCount); router.post("/get/size", apikey, getTotalSpaceOccupied); router.post("/get/:mediaId", apikey, getMediaDetails); router.post("/get", apikey, getMedia); +router.post("/seal/:mediaId", apikey, sealMedia); router.delete("/delete/:mediaId", apikey, deleteMedia); export default router; diff --git a/apps/api/src/media/service.ts b/apps/api/src/media/service.ts index 627b54d9..e697cbf2 100644 --- a/apps/api/src/media/service.ts +++ b/apps/api/src/media/service.ts @@ -6,7 +6,10 @@ import { imagePattern, videoPattern, imagePatternForThumbnailGeneration, - USE_CLOUDFRONT, + ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT, + DISABLE_TAGGING, + cloudBucket, + cloudPublicBucket, } from "../config/constants"; import imageUtils from "@medialit/images"; import { @@ -14,14 +17,14 @@ import { createFolders, moveFile, } from "./utils/manage-files-on-disk"; -import type { MediaWithUserId } from "./model"; import { generateSignedUrl, - generateCDNSignedUrl, + generateCloudfrontSignedUrl, putObject, deleteObject, + copyObject, + getObjectTagging, UploadParams, - getObjectTagging as objectTagging, } from "../services/s3"; import logger from "../services/log"; import generateKey from "./utils/generate-key"; @@ -35,9 +38,11 @@ import { getPaginatedMedia, createMedia, } from "./queries"; +import MediaModel from "./model"; import * as presignedUrlService from "../signature/service"; import getTags from "./utils/get-tags"; -import { getMainFileUrl, getThumbnailUrl } from "./utils/get-public-urls"; +import { getPublicFileUrl, getThumbnailUrl } from "./utils/get-public-urls"; +import { AccessControl, Constants, MediaWithUserId } from "@medialit/models"; const generateAndUploadThumbnail = async ({ workingDirectory, @@ -45,12 +50,14 @@ const generateAndUploadThumbnail = async ({ mimetype, originalFilePath, tags, + bucket, }: { workingDirectory: string; key: string; mimetype: string; originalFilePath: string; tags: string; + bucket?: string; }): Promise => { const thumbPath = `${workingDirectory}/thumb.webp`; @@ -69,8 +76,8 @@ const generateAndUploadThumbnail = async ({ Key: key, Body: createReadStream(thumbPath), ContentType: "image/webp", - ACL: USE_CLOUDFRONT ? "private" : "public-read", Tagging: tags, + Bucket: bucket || cloudBucket, }); } @@ -122,16 +129,12 @@ async function upload({ const uploadParams: UploadParams = { Key: generateKey({ mediaId: fileName.name, - access: access === "public" ? "public" : "private", + path: Constants.PathKey.PRIVATE, filename: `main.${fileExtension}`, }), Body: createReadStream(mainFilePath), ContentType: mimeType, - ACL: USE_CLOUDFRONT - ? "private" - : access === "public" - ? "public-read" - : "private", + Bucket: cloudBucket, }; const tags = getTags(userId, group); uploadParams.Tagging = tags; @@ -146,10 +149,11 @@ async function upload({ originalFilePath: mainFilePath, key: generateKey({ mediaId: fileName.name, - access: "public", + path: Constants.PathKey.PRIVATE, filename: "thumb.webp", }), tags, + bucket: cloudBucket, }); } catch (err: any) { logger.error({ err }, err.message); @@ -167,8 +171,12 @@ async function upload({ size: file.size, thumbnailGenerated: isThumbGenerated, caption, - accessControl: access === "public" ? "public-read" : "private", + accessControl: + access === Constants.AccessControl.PUBLIC + ? Constants.AccessControl.PUBLIC + : Constants.AccessControl.PRIVATE, group, + temp: true, }; const media = await createMedia(mediaObject); @@ -185,9 +193,9 @@ async function upload({ } type MappedMedia = Partial< - Omit, "thumbnailGenerated"> + Omit, "thumbnailGenerated"> > & { - access: "private" | "public"; + access: AccessControl; thumbnail: string; }; @@ -208,15 +216,16 @@ async function getPage({ recordsPerPage, }); const mappedResult = result.map( - (media: MediaWithUserId): MappedMedia => ({ + (media): MappedMedia => ({ mediaId: media.mediaId, originalFileName: media.originalFileName, mimeType: media.mimeType, size: media.size, - access: media.accessControl === "private" ? "private" : "public", - thumbnail: media.thumbnailGenerated - ? getThumbnailUrl(media.mediaId) - : "", + access: + media.accessControl === Constants.AccessControl.PRIVATE + ? Constants.AccessControl.PRIVATE + : Constants.AccessControl.PUBLIC, + thumbnail: media.thumbnailGenerated ? getThumbnailUrl(media) : "", caption: media.caption, group: media.group, }), @@ -243,32 +252,63 @@ async function getMediaDetails({ return null; } - const key = generateKey({ - mediaId: media.mediaId, - access: media.accessControl === "private" ? "private" : "public", - filename: `main.${path.extname(media.fileName).replace(".", "")}`, - }); + // Determine file URL based on access control and temp status + let fileUrl: string; + if (media.temp || media.accessControl === Constants.AccessControl.PRIVATE) { + // Temp or private files: use signed URL from private bucket + fileUrl = await getPrivateFileUrl(media); + } else { + // Public sealed files: use direct URL from public bucket + fileUrl = getPublicFileUrl(media); + } + + // Determine thumbnail URL + let thumbnailUrl = ""; + if (media.thumbnailGenerated) { + if (media.temp) { + // Temp thumbnail: use signed URL from private bucket + thumbnailUrl = await getPrivateFileUrl(media, true); + } else { + // Sealed thumbnail: use direct URL from public bucket + thumbnailUrl = getThumbnailUrl(media); + } + } return { mediaId: media.mediaId, originalFileName: media.originalFileName, mimeType: media.mimeType, size: media.size, - access: media.accessControl === "private" ? "private" : "public", - file: - media.accessControl === "private" - ? USE_CLOUDFRONT - ? generateCDNSignedUrl(key) - : await generateSignedUrl(key) - : getMainFileUrl(media), - thumbnail: media.thumbnailGenerated - ? getThumbnailUrl(media.mediaId) - : "", + access: + media.accessControl === Constants.AccessControl.PRIVATE + ? Constants.AccessControl.PRIVATE + : Constants.AccessControl.PUBLIC, + file: fileUrl, + thumbnail: thumbnailUrl, caption: media.caption, group: media.group, }; } +async function getPrivateFileUrl(media: MediaWithUserId, thumb?: boolean) { + const filename = thumb + ? "thumb.webp" + : `main.${path.extname(media.fileName).replace(".", "")}`; + + const key = generateKey({ + mediaId: media.mediaId, + path: Constants.PathKey.PRIVATE, + filename, + }); + + // Private files are always in private bucket + const bucket = cloudBucket; + + return ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT + ? generateCloudfrontSignedUrl(key) + : await generateSignedUrl(key, bucket); +} + async function deleteMedia({ userId, apikey, @@ -281,28 +321,161 @@ async function deleteMedia({ const media = await getMedia({ userId, apikey, mediaId }); if (!media) return; + // Determine which bucket the main file is in + const mainBucket = + media.temp || media.accessControl === Constants.AccessControl.PRIVATE + ? cloudBucket + : cloudPublicBucket; + + const mainPath = + media.temp || media.accessControl === Constants.AccessControl.PRIVATE + ? Constants.PathKey.PRIVATE + : Constants.PathKey.PUBLIC; + + const fileExtension = path.extname(media.fileName).replace(".", ""); const key = generateKey({ mediaId, - access: media.accessControl === "private" ? "private" : "public", - filename: `main.${media.fileName.split(".")[1]}`, + path: mainPath, + filename: `main.${fileExtension}`, }); - await deleteObject({ Key: key }); + await deleteObject({ Key: key, Bucket: mainBucket }); if (media.thumbnailGenerated) { + // Thumbnails are in public bucket if sealed, private bucket if temp + const thumbBucket = media.temp ? cloudBucket : cloudPublicBucket; + const thumbPath = media.temp + ? Constants.PathKey.PRIVATE + : Constants.PathKey.PUBLIC; const thumbKey = generateKey({ mediaId, - access: "public", + path: thumbPath, filename: "thumb.webp", }); - await deleteObject({ Key: thumbKey }); + await deleteObject({ Key: thumbKey, Bucket: thumbBucket }); } await deleteMediaQuery(userId, mediaId); } +async function sealMedia({ + userId, + apikey, + mediaId, +}: { + userId: string; + apikey: string; + mediaId: string; +}): Promise { + const media = await getMedia({ userId, apikey, mediaId }); + if (!media) { + throw new Error("Media not found"); + } + + if (!media.temp) { + return media; + } + + const fileExtension = path.extname(media.fileName).replace(".", ""); + + // Get tags from source object (in private bucket) + const tmpMainKey = generateKey({ + mediaId, + path: Constants.PathKey.PRIVATE, + filename: `main.${fileExtension}`, + }); + let tags: string | undefined; + if (!DISABLE_TAGGING) { + try { + const taggingResponse = await getObjectTagging({ + Key: tmpMainKey, + Bucket: cloudBucket, + }); + if (taggingResponse.TagSet && taggingResponse.TagSet.length > 0) { + tags = taggingResponse.TagSet.map( + (tag: any) => `${tag.Key}=${tag.Value}`, + ).join("&"); + } + } catch (err: any) { + logger.warn({ err }, "Failed to get tags from source object"); + } + } + + // Determine destination bucket for main file + const isPublic = media.accessControl === Constants.AccessControl.PUBLIC; + + // Copy main file from private bucket to public bucket (only if public) + if (isPublic) { + const finalMainKey = generateKey({ + mediaId, + path: Constants.PathKey.PUBLIC, + filename: `main.${fileExtension}`, + }); + + await copyObject({ + sourceKey: tmpMainKey, + sourceBucket: cloudBucket, + destinationKey: finalMainKey, + destinationBucket: cloudPublicBucket, + ContentType: media.mimeType, + Tagging: tags, + }); + } + + // Copy thumbnail from private bucket to public bucket (if exists) + if (media.thumbnailGenerated) { + const tmpThumbKey = generateKey({ + mediaId, + path: Constants.PathKey.PRIVATE, + filename: "thumb.webp", + }); + const finalThumbKey = generateKey({ + mediaId, + path: Constants.PathKey.PUBLIC, + filename: "thumb.webp", + }); + + await copyObject({ + sourceKey: tmpThumbKey, + sourceBucket: cloudBucket, + destinationKey: finalThumbKey, + destinationBucket: cloudPublicBucket, + ContentType: "image/webp", + Tagging: tags, + }); + + // Delete thumbnail from private bucket (it's now in public bucket) + await deleteObject({ + Key: tmpThumbKey, + Bucket: cloudBucket, + }); + } + + // Delete main file from private bucket only if it was copied to public bucket + if (isPublic) { + await deleteObject({ + Key: tmpMainKey, + Bucket: cloudBucket, + }); + } + + // Update media record to remove temp flag + await MediaModel.updateOne( + { mediaId, userId, apikey }, + { $unset: { temp: "" } }, + ); + + // Fetch and return the updated media + const updatedMedia = await getMedia({ userId, apikey, mediaId }); + if (!updatedMedia) { + throw new Error("Failed to retrieve updated media"); + } + return updatedMedia; +} + export default { upload, getPage, getMediaDetails, deleteMedia, + sealMedia, }; diff --git a/apps/api/src/media/utils/generate-key.ts b/apps/api/src/media/utils/generate-key.ts index 1edbc0e1..74374320 100644 --- a/apps/api/src/media/utils/generate-key.ts +++ b/apps/api/src/media/utils/generate-key.ts @@ -1,15 +1,14 @@ -import { CLOUD_PREFIX } from "../../config/constants"; +import { PathKey } from "@medialit/models"; +import { PATH_PREFIX } from "../../config/constants"; export default function generateKey({ mediaId, - access, + path, filename, }: { mediaId: string; - access: "private" | "public"; + path: PathKey; // this helps in serving both private and public files from the same CDN filename: string; }): string { - return `${ - CLOUD_PREFIX ? `${CLOUD_PREFIX}/` : "" - }${access}/${mediaId}/${filename}`; + return `${PATH_PREFIX ? `${PATH_PREFIX}/` : ""}${path}/${mediaId}/${filename}`; } diff --git a/apps/api/src/media/utils/get-public-urls.ts b/apps/api/src/media/utils/get-public-urls.ts index b63577a3..4392952a 100644 --- a/apps/api/src/media/utils/get-public-urls.ts +++ b/apps/api/src/media/utils/get-public-urls.ts @@ -1,15 +1,26 @@ import path from "path"; -import { ENDPOINT, CLOUD_PREFIX } from "../../config/constants"; -import { Media } from "@medialit/models"; +import { + CDN_ENDPOINT, + CLOUD_ENDPOINT, + PATH_PREFIX, + CLOUD_ENDPOINT_PUBLIC, +} from "../../config/constants"; +import { Constants, Media } from "@medialit/models"; -const prefix = CLOUD_PREFIX ? `${CLOUD_PREFIX}/` : ""; +const prefix = PATH_PREFIX ? `${PATH_PREFIX}/` : ""; -export function getMainFileUrl(media: Media) { - return `${ENDPOINT}/${prefix}public/${media.mediaId}/main${path.extname( +export function getPublicFileUrl(media: Media) { + const ENDPOINT = + CDN_ENDPOINT || + (media.accessControl === Constants.AccessControl.PUBLIC + ? CLOUD_ENDPOINT_PUBLIC || CLOUD_ENDPOINT + : CLOUD_ENDPOINT); + return `${ENDPOINT}/${prefix}${Constants.PathKey.PUBLIC}/${media.mediaId}/main${path.extname( media.fileName, )}`; } -export function getThumbnailUrl(mediaId: string) { - return `${ENDPOINT}/${prefix}public/${mediaId}/thumb.webp`; +export function getThumbnailUrl(media: Media) { + const ENDPOINT = CDN_ENDPOINT || CLOUD_ENDPOINT_PUBLIC; + return `${ENDPOINT}/${prefix}${Constants.PathKey.PUBLIC}/${media.mediaId}/thumb.webp`; } diff --git a/apps/api/src/services/s3.ts b/apps/api/src/services/s3.ts index 8e935a81..ea10ba66 100644 --- a/apps/api/src/services/s3.ts +++ b/apps/api/src/services/s3.ts @@ -6,17 +6,22 @@ import { DeleteObjectCommand, GetObjectCommand, GetObjectTaggingCommand, + CopyObjectCommand, + ListObjectsV2Command, } from "@aws-sdk/client-s3"; import { - cloudEndpoint, + CLOUD_ENDPOINT, + CLOUD_ENDPOINT_PUBLIC, cloudKey, cloudSecret, cloudBucket, + cloudPublicBucket, cloudRegion, CLOUDFRONT_KEY_PAIR_ID, CLOUDFRONT_PRIVATE_KEY, CDN_MAX_AGE, - CLOUDFRONT_ENDPOINT, + CDN_ENDPOINT, + DISABLE_TAGGING, } from "../config/constants"; import { getSignedUrl as getCfSignedUrl } from "@aws-sdk/cloudfront-signer"; @@ -24,12 +29,22 @@ export interface UploadParams { Key: string; Body: ReadStream; ContentType: string; - ACL: "private" | "public-read"; Tagging?: string; + Bucket?: string; } export interface DeleteParams { Key: string; + Bucket?: string; +} + +export interface CopyParams { + sourceKey: string; + destinationKey: string; + ContentType?: string; + Tagging?: string; + sourceBucket?: string; + destinationBucket?: string; } export interface PresignedURLParams { @@ -37,69 +52,171 @@ export interface PresignedURLParams { mimetype?: string; } -let s3Client: S3Client | null = null; - -const getS3Client = () => { - if (!s3Client) { - s3Client = new S3Client({ - region: cloudRegion, - endpoint: cloudEndpoint, - credentials: { - accessKeyId: cloudKey, - secretAccessKey: cloudSecret, - }, - }); +let s3PrivateClient: S3Client | null = null; +let s3PublicClient: S3Client | null = null; + +export const getPrivateS3ClientConfig = () => { + const config: any = { + region: cloudRegion, + credentials: { + accessKeyId: cloudKey, + secretAccessKey: cloudSecret, + }, + }; + + if (CLOUD_ENDPOINT && !CLOUD_ENDPOINT.includes("amazonaws.com")) { + const isVirtualHostedStyle = CLOUD_ENDPOINT.includes(cloudBucket); + config.endpoint = CLOUD_ENDPOINT; + config.forcePathStyle = !isVirtualHostedStyle; + } + + return config; +}; + +export const getPublicS3ClientConfig = () => { + const config: any = { + region: cloudRegion, + credentials: { + accessKeyId: cloudKey, + secretAccessKey: cloudSecret, + }, + }; + + if ( + CLOUD_ENDPOINT_PUBLIC && + !CLOUD_ENDPOINT_PUBLIC.includes("amazonaws.com") + ) { + const isVirtualHostedStyle = + CLOUD_ENDPOINT_PUBLIC.includes(cloudPublicBucket); + config.endpoint = CLOUD_ENDPOINT_PUBLIC; + config.forcePathStyle = !isVirtualHostedStyle; + } + + return config; +}; + +const getS3Client = (bucket?: string): S3Client => { + const targetBucket = bucket || cloudBucket; + // Check if target bucket is the public bucket (handle empty string edge case) + const isPublicBucket = + cloudPublicBucket && targetBucket === cloudPublicBucket; + + if (isPublicBucket) { + if (!s3PublicClient) { + s3PublicClient = new S3Client(getPublicS3ClientConfig()); + } + return s3PublicClient; + } else { + if (!s3PrivateClient) { + s3PrivateClient = new S3Client(getPrivateS3ClientConfig()); + } + return s3PrivateClient; } - return s3Client; }; export const putObject = async (params: UploadParams) => { + if (DISABLE_TAGGING) { + delete params.Tagging; + } + const bucket = params.Bucket || cloudBucket; + const { Bucket, ...restParams } = params; const command = new PutObjectCommand( - Object.assign({}, { Bucket: cloudBucket }, params), + Object.assign({}, { Bucket: bucket }, restParams), ); - const response = await getS3Client().send(command); + const response = await getS3Client(bucket).send(command); return response; }; export const deleteObject = async (params: DeleteParams) => { + const bucket = params.Bucket || cloudBucket; + const { Bucket, ...restParams } = params; const command = new DeleteObjectCommand( - Object.assign({}, { Bucket: cloudBucket }, params), + Object.assign({}, { Bucket: bucket }, restParams), ); - const response = await getS3Client().send(command); + const response = await getS3Client(bucket).send(command); return response; }; -export const getObjectTagging = async (params: { Key: string }) => { +export const getObjectTagging = async (params: { + Key: string; + Bucket?: string; +}) => { + const bucket = params.Bucket || cloudBucket; + const { Bucket, ...restParams } = params; const command = new GetObjectTaggingCommand( - Object.assign({}, { Bucket: cloudBucket }, params), + Object.assign({}, { Bucket: bucket }, restParams), ); - const response = await getS3Client().send(command); + const response = await getS3Client(bucket).send(command); return response; }; -export const generateSignedUrl = async (key: string): Promise => { +export const generateSignedUrl = async ( + key: string, + bucket?: string, +): Promise => { + const targetBucket = bucket || cloudBucket; const command = new GetObjectCommand({ - Bucket: cloudBucket, + Bucket: targetBucket, Key: key, }); - const url = await getS3SignedUrl(getS3Client(), command); + const url = await getS3SignedUrl(getS3Client(targetBucket), command); return url; }; -export const generateCDNSignedUrl = (key: string): string => { - if ( - !CLOUDFRONT_ENDPOINT || - !CLOUDFRONT_KEY_PAIR_ID || - !CLOUDFRONT_PRIVATE_KEY - ) { +export const generateCloudfrontSignedUrl = (key: string): string => { + if (!CDN_ENDPOINT || !CLOUDFRONT_KEY_PAIR_ID || !CLOUDFRONT_PRIVATE_KEY) { throw new Error("CDN configuration is missing"); } const url = getCfSignedUrl({ - url: `${CLOUDFRONT_ENDPOINT}/${key}`, + url: `${CDN_ENDPOINT}/${key}`, keyPairId: CLOUDFRONT_KEY_PAIR_ID, privateKey: CLOUDFRONT_PRIVATE_KEY, dateLessThan: new Date(Date.now() + CDN_MAX_AGE).toISOString(), }); return url; }; + +export const copyObject = async (params: CopyParams) => { + const sourceBucket = params.sourceBucket || cloudBucket; + const destinationBucket = params.destinationBucket || cloudBucket; + const copySource = `${sourceBucket}/${params.sourceKey}`; + const command = new CopyObjectCommand({ + Bucket: destinationBucket, + CopySource: copySource, + Key: params.destinationKey, + ContentType: params.ContentType, + Tagging: params.Tagging, + }); + // Use destination bucket's client for copy operations + const response = await getS3Client(destinationBucket).send(command); + return response; +}; + +export const deleteFolder = async ( + prefix: string, + bucket?: string, +): Promise => { + const targetBucket = bucket || cloudBucket; + const client = getS3Client(targetBucket); + let continuationToken: string | undefined; + + do { + const command = new ListObjectsV2Command({ + Bucket: targetBucket, + Prefix: prefix, + ContinuationToken: continuationToken, + }); + + const response = await client.send(command); + + if (response.Contents && response.Contents.length > 0) { + const deletePromises = response.Contents.map((object: any) => + deleteObject({ Key: object.Key!, Bucket: targetBucket }), + ); + await Promise.all(deletePromises); + } + + continuationToken = response.NextContinuationToken; + } while (continuationToken); +}; diff --git a/apps/api/src/signature/handlers.ts b/apps/api/src/signature/handlers.ts index a71a0b01..9ee75761 100644 --- a/apps/api/src/signature/handlers.ts +++ b/apps/api/src/signature/handlers.ts @@ -2,7 +2,6 @@ import { Request } from "express"; import Joi from "joi"; import logger from "../services/log"; import * as preSignedUrlService from "./service"; -import { HOSTNAME_OVERRIDE } from "../config/constants"; function validatePresigningOptions(req: Request): Joi.ValidationResult { const uploadSchema = Joi.object({ @@ -26,8 +25,6 @@ export async function getSignature( const signature = await preSignedUrlService.generateSignature({ userId: req.user.id, apikey: req.apikey, - protocol: req.protocol, - host: HOSTNAME_OVERRIDE || req.get("Host"), group: req.body.group, }); return res.status(200).json({ signature }); diff --git a/apps/api/src/signature/service.ts b/apps/api/src/signature/service.ts index b282716d..38d1c880 100644 --- a/apps/api/src/signature/service.ts +++ b/apps/api/src/signature/service.ts @@ -37,16 +37,12 @@ export async function getUserAndGroupFromPresignedUrl( interface GenerateSignedUrlProps { userId: string; apikey: string; - protocol: string; - host: string; group?: string; } export async function generateSignature({ userId, apikey, - protocol, - host, group, }: GenerateSignedUrlProps): Promise { const presignedUrl = await queries.createPresignedUrl( diff --git a/apps/api/src/tus/cleanup.ts b/apps/api/src/tus/cleanup.ts index 0df5f8c6..34400e7c 100644 --- a/apps/api/src/tus/cleanup.ts +++ b/apps/api/src/tus/cleanup.ts @@ -2,7 +2,7 @@ import logger from "../services/log"; import TusUploadModel, { TusUpload } from "./model"; import { removeTusFiles } from "./utils"; -export async function Cleanup() { +export async function cleanupTUSUploads() { logger.info({}, "Starting the tus uploads cleanup job"); const now = new Date(); @@ -10,10 +10,23 @@ export async function Cleanup() { expiresAt: { $lt: now }, }).lean()) as unknown as TusUpload[]; + if (expiredUploads.length === 0) { + logger.info("No expired tus uploads found to cleanup"); + return; + } + + logger.info( + { count: expiredUploads.length }, + "Found expired tus uploads to cleanup", + ); + for (const expiredUpload of expiredUploads) { removeTusFiles(expiredUpload.tempFilePath); await TusUploadModel.deleteOne({ _id: (expiredUpload as any)._id }); } - logger.info({}, "Ending the tus uploads cleanup job"); + logger.info( + { count: expiredUploads.length }, + "Cleaned up expired tus uploads", + ); } diff --git a/apps/api/src/tus/finalize.ts b/apps/api/src/tus/finalize.ts index 6814325a..8d2d7f32 100644 --- a/apps/api/src/tus/finalize.ts +++ b/apps/api/src/tus/finalize.ts @@ -12,14 +12,14 @@ import { imagePattern, imagePatternForThumbnailGeneration, videoPattern, - USE_CLOUDFRONT, + cloudBucket, } from "../config/constants"; import imageUtils from "@medialit/images"; import { foldersExist, createFolders, } from "../media/utils/manage-files-on-disk"; -import type { MediaWithUserId } from "../media/model"; +import { Constants, type MediaWithUserId } from "@medialit/models"; import { putObject, UploadParams } from "../services/s3"; import logger from "../services/log"; import generateKey from "../media/utils/generate-key"; @@ -96,16 +96,12 @@ export default async function finalizeUpload( const uploadParams: UploadParams = { Key: generateKey({ mediaId: fileName.name, - access: metadata.accessControl === "public" ? "public" : "private", + path: Constants.PathKey.PRIVATE, filename: `main.${fileExtension}`, }), Body: createReadStream(mainFilePath), ContentType: mimeType, - ACL: USE_CLOUDFRONT - ? "private" - : metadata.accessControl === "public" - ? "public-read" - : "private", + Bucket: cloudBucket, }; const tags = getTags(userId, metadata.group); uploadParams.Tagging = tags; @@ -120,10 +116,11 @@ export default async function finalizeUpload( originalFilePath: mainFilePath, key: generateKey({ mediaId: fileName.name, - access: "public", + path: Constants.PathKey.PRIVATE, filename: "thumb.webp", }), tags, + bucket: cloudBucket, }); } catch (err: any) { logger.error({ err }, err.message); @@ -131,7 +128,7 @@ export default async function finalizeUpload( await fsPromises.rm(temporaryFolderForWork, { recursive: true }); - const mediaObject: MediaWithUserId = { + const mediaObject = { fileName: `main.${fileExtension}`, mediaId: fileName.name, userId: new mongoose.Types.ObjectId(userId), @@ -141,10 +138,15 @@ export default async function finalizeUpload( size: uploadLength, thumbnailGenerated: isThumbGenerated, caption: metadata.caption, + // accessControl: + // metadata.accessControl === "public" ? "public-read" : "private", accessControl: - metadata.accessControl === "public" ? "public-read" : "private", + metadata.accessControl === Constants.AccessControl.PUBLIC + ? Constants.AccessControl.PUBLIC + : Constants.AccessControl.PRIVATE, group: metadata.group, - }; + temp: true, + } as MediaWithUserId; const media = await createMedia(mediaObject); // Mark upload as complete @@ -178,12 +180,14 @@ const generateAndUploadThumbnail = async ({ mimetype, originalFilePath, tags, + bucket, }: { workingDirectory: string; key: string; mimetype: string; originalFilePath: string; tags: string; + bucket?: string; }): Promise => { const thumbPath = `${workingDirectory}/thumb.webp`; let isGenerated = false; @@ -202,8 +206,8 @@ const generateAndUploadThumbnail = async ({ Key: key, Body: createReadStream(thumbPath), ContentType: "image/webp", - ACL: USE_CLOUDFRONT ? "private" : "public-read", Tagging: tags, + Bucket: bucket || cloudBucket, }); await fsPromises.rm(thumbPath); } diff --git a/apps/api/src/tus/utils.ts b/apps/api/src/tus/utils.ts index 9678d6e6..159247de 100644 --- a/apps/api/src/tus/utils.ts +++ b/apps/api/src/tus/utils.ts @@ -1,15 +1,20 @@ import path from "path"; import { tempFileDirForUploads } from "../config/constants"; +import logger from "../services/log"; export function removeTusFiles(uploadId: string) { - const tusFilePath = path.join( - `${tempFileDirForUploads}/tus-uploads`, - uploadId, - ); - require("fs").unlinkSync(tusFilePath); - const tusJSONFilePath = path.join( - `${tempFileDirForUploads}/tus-uploads`, - `${uploadId}.json`, - ); - require("fs").unlinkSync(tusJSONFilePath); + try { + const tusFilePath = path.join( + `${tempFileDirForUploads}/tus-uploads`, + uploadId, + ); + require("fs").unlinkSync(tusFilePath); + const tusJSONFilePath = path.join( + `${tempFileDirForUploads}/tus-uploads`, + `${uploadId}.json`, + ); + require("fs").unlinkSync(tusJSONFilePath); + } catch (err: any) { + logger.error({ err }, "Error removing tus files"); + } } diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 85cc3dbe..de7833dc 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,6 +1,7 @@ { "ts-node": { - "files": true + "files": true, + "require": ["tsconfig-paths/register"] }, "compilerOptions": { "outDir": "dist", @@ -9,6 +10,10 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } } } diff --git a/apps/docs/content/docs/meta.json b/apps/docs/content/docs/meta.json index ef1fe06d..157d1a73 100644 --- a/apps/docs/content/docs/meta.json +++ b/apps/docs/content/docs/meta.json @@ -11,6 +11,7 @@ "rest-api", "---Resources---", "self-hosting", + "migrate-to-dual-bucket-architecture", "faqs" ] -} +} \ No newline at end of file diff --git a/apps/docs/content/docs/migrate-to-dual-bucket-architecture.mdx b/apps/docs/content/docs/migrate-to-dual-bucket-architecture.mdx new file mode 100644 index 00000000..be5bbda3 --- /dev/null +++ b/apps/docs/content/docs/migrate-to-dual-bucket-architecture.mdx @@ -0,0 +1,49 @@ +--- +title: Migrate to dual bucket architecture +description: Learn how to migrate from single bucket to dual bucket architecture +--- + +In order to make MediaLit truly vendor agnostic, we decided to move from a single bucket architecture to a dual bucket architecture. + +This is based on our years of learning from running MediaLit in production and all the community feedback we have received over the years. + +## Why dual bucket architecture? + +This makes the architecture portable across cloud providers. We discovered that providers like Backblaze B2 does not support hosting both private and public files in the same bucket. + +## How to migrate to dual bucket architecture? + +1. Create new buckets as detailed in [Self hosting](/self-hosting) section. + +2. Copy data from the old bucket to the new bucket. To do this, you will have to checkout MediaLit's Git [repo](https://github.com/codelitdev/medialit), then do the following in the terminal + +```sh +cd +pnpm i +pnpm --filter @medialit/scripts migrate:dual_bucket_architecture --dry-run # To see the changes without actually performing those +pnpm --filter @medialit/scripts migrate:dual_bucket_architecture +``` +3. Upgrade the MediaLit DB by running the following migration, by logging into your MediaLit's database and copy pasting the content of the file. + +`.migrations/00005-migrate-to-dual-bucket-architecture` + +4. Update .env file for the MediaLit API container. + +```sh +CLOUD_BUCKET_NAME=your-private-bucket +CLOUD_PUBLIC_BUCKET_NAME=your-public-bucket +PATH_PREFIX=bucket-folder-to-place-the-files-in # This is optional +CDN_ENDPOINT=endpoint + +# Optional, if using Cloudfront +ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT=true +CLOUDFRONT_KEY_PAIR_ID=key +CLOUDFRONT_PRIVATE_KEY=priv-key +``` + +5. Re-deploy the MediaLit API container. +``` +docker compose pull +docker compose down +docker compose up -d +``` \ No newline at end of file diff --git a/apps/docs/content/docs/rest-api.mdx b/apps/docs/content/docs/rest-api.mdx index 9cd24ee8..4979b90d 100644 --- a/apps/docs/content/docs/rest-api.mdx +++ b/apps/docs/content/docs/rest-api.mdx @@ -1,11 +1,11 @@ --- title: REST API -description: Integrate MediaLit with your app using our REST API +description: Upload files in under five minutes with our API --- -We offer an official [Postman](https://postman.com) collection. The collection contains all the relevant documenation. +## API Documentation -[MediaLit Postman collection](https://www.postman.com/dark-rocket-625879/codelit/collection/5b8hfkr/medialit) +Access the complete endpoint reference through our official [Postman](https://www.postman.com/codelitdev/codelit/collection/5b8hfkr/medialit) collection. ## Video Tutorial diff --git a/apps/docs/content/docs/self-hosting.mdx b/apps/docs/content/docs/self-hosting.mdx index f08e92fe..931a1ce7 100644 --- a/apps/docs/content/docs/self-hosting.mdx +++ b/apps/docs/content/docs/self-hosting.mdx @@ -3,4 +3,99 @@ title: Self hosting description: Learn how to host MediaLit API --- -Coming soon \ No newline at end of file +This guide explains how to self-host MediaLit with an S3-compatible storage provider. + +## Requirements +1. Two buckets: one dedicated to private files and one to public files. +2. Private bucket access settings: public access disabled. +3. Public bucket access settings: public access enabled. + +## Configuring storage + +### AWS S3 + +**1. Configure the private bucket** +![AWS Private bucket private access](/aws-private-bucket-policy.png) + +**2. Configure the public bucket** + +**2.1 Allow public access** +![AWS Public bucket public access](/aws-public-bucket-policy.png) + +**2.2 Configure bucket policy** +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadAccess", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3::://*" + } + ] +} +``` +**3. Set environment variables** +```sh +CLOUD_KEY=your_aws_access_key +CLOUD_SECRET=your_aws_secret_key +CLOUD_BUCKET_NAME=your_bucket_name +CLOUD_PUBLIC_BUCKET_NAME=your_public_bucket_name +CLOUD_REGION=ap-southeast-1 +CDN_ENDPOINT=https://.s3..amazonaws.com +``` + +#### Add CloudFront CDN + +**1. CloudFront distribution configuration** + +Add the public bucket to CloudFront as an origin. + +**2. Set environment variables** + +```sh +CDN_ENDPOINT=https://your-cloudfront-endpoint.example.com +``` + +In this setup, private URLs are served directly from S3. To serve private bucket content via CloudFront as well, keep reading. + +**3. Put the private bucket behind CloudFront** + +**3.1. Add the private bucket to CloudFront** + +Add the private bucket to CloudFront as an origin. + +![Put both buckets behind CloudFront](/serve-public-private-buckets-via-cdn.png) + +**3.2. Set environment variables** + +``` +ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT=true # Enables URL signing for CloudFront +CLOUDFRONT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_CONTENTS\n-----END PRIVATE KEY-----" +CLOUDFRONT_KEY_PAIR_ID=YOUR_KEY_PAIR_ID +``` + + +### Cloudflare R2 + +**1. Configure the private bucket** +![Cloudflare R2 private bucket config](/cloudflare-private-bucket-config.png) + +**2. Configure the public bucket** +![Cloudflare R2 public bucket config](/cloudflare-public-bucket-config.png) + +**3. Set environment variables** +```sh +CLOUD_KEY=your_cloudflare_r2_access_key +CLOUD_SECRET=your_cloudflare_r2_secret_key +CLOUD_BUCKET_NAME=your_private_bucket_name +CLOUD_PUBLIC_BUCKET_NAME=your_public_bucket_name +CLOUD_REGION=auto +CLOUD_ENDPOINT=https://.r2.cloudflarestorage.com +CLOUD_ENDPOINT_PUBLIC=https://.r2.cloudflarestorage.com # Same URL as CLOUD_ENDPOINT +CDN_ENDPOINT=https://.r2.dev # For accessing public bucket content +``` + +> In the Cloudflare setup, you cannot serve private bucket content through the CDN. \ No newline at end of file diff --git a/apps/docs/public/aws-private-bucket-policy.png b/apps/docs/public/aws-private-bucket-policy.png new file mode 100644 index 00000000..dfc11348 Binary files /dev/null and b/apps/docs/public/aws-private-bucket-policy.png differ diff --git a/apps/docs/public/aws-public-bucket-policy.png b/apps/docs/public/aws-public-bucket-policy.png new file mode 100644 index 00000000..5af69ad8 Binary files /dev/null and b/apps/docs/public/aws-public-bucket-policy.png differ diff --git a/apps/docs/public/cloudflare-private-bucket-config.png b/apps/docs/public/cloudflare-private-bucket-config.png new file mode 100644 index 00000000..92ff2ca7 Binary files /dev/null and b/apps/docs/public/cloudflare-private-bucket-config.png differ diff --git a/apps/docs/public/cloudflare-public-bucket-config.png b/apps/docs/public/cloudflare-public-bucket-config.png new file mode 100644 index 00000000..43c5c6f7 Binary files /dev/null and b/apps/docs/public/cloudflare-public-bucket-config.png differ diff --git a/apps/docs/public/serve-public-private-buckets-via-cdn.png b/apps/docs/public/serve-public-private-buckets-via-cdn.png new file mode 100644 index 00000000..3f99345e Binary files /dev/null and b/apps/docs/public/serve-public-private-buckets-via-cdn.png differ diff --git a/apps/docs/public/serve-public-private-buckets-via-cdn2.png b/apps/docs/public/serve-public-private-buckets-via-cdn2.png new file mode 100644 index 00000000..c3a573dc Binary files /dev/null and b/apps/docs/public/serve-public-private-buckets-via-cdn2.png differ diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 5581719f..3a186bf2 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -22,6 +22,14 @@ const nextConfig = { protocol: "https", hostname: "d27g932tzd9f7s.cloudfront.net", }, + { + protocol: "https", + hostname: "cdn.medialit.clqa.online", + }, + { + protocol: "https", + hostname: "cdn.medialit.cloud", + }, ], }, }; diff --git a/docker-compose.yml b/docker-compose.yml index 5bbb15b2..222a2821 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,13 +12,12 @@ services: - CLOUD_KEY=${CLOUD_KEY?'Cloud key is required'} - CLOUD_SECRET=${CLOUD_SECRET?'Cloud secret is required'} - CLOUD_BUCKET_NAME=${CLOUD_BUCKET_NAME?'Cloud bucket name is required'} - - CLOUD_PREFIX=${CLOUD_PREFIX?'Cloud prefix is required'} - - S3_ENDPOINT=${S3_ENDPOINT?'S3 endpoint is required'} + - PATH_PREFIX=${PATH_PREFIX?'Cloud prefix is required'} - TEMP_FILE_DIR_FOR_UPLOADS=${TEMP_FILE_DIR_FOR_UPLOADS?'A temporary directory for uploads transformations is required'} - PORT=8000 - ENABLE_TRUST_PROXY=${ENABLE_TRUST_PROXY} - - USE_CLOUDFRONT=${USE_CLOUDFRONT} - - CLOUDFRONT_ENDPOINT=${CLOUDFRONT_ENDPOINT} + - ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT=${ACCESS_PRIVATE_BUCKET_VIA_CLOUDFRONT} + - CDN_ENDPOINT=${CDN_ENDPOINT} - CLOUDFRONT_KEY_PAIR_ID=${CLOUDFRONT_KEY_PAIR_ID} - CLOUDFRONT_PRIVATE_KEY=${CLOUDFRONT_PRIVATE_KEY} - CDN_MAX_AGE=${CDN_MAX_AGE} diff --git a/docs/prds/dual-bucket-architecture.md b/docs/prds/dual-bucket-architecture.md new file mode 100644 index 00000000..495ed100 --- /dev/null +++ b/docs/prds/dual-bucket-architecture.md @@ -0,0 +1,454 @@ + + +# Product Requirements Document (PRD) + +**Title:** + +Dual-Bucket Architecture for S3-Compatible Storage Providers + +**Product:** + +MediaLit — Cloud Digital Asset Management Service + +**Author:** + +Rajat Saxena + +**Version:** + +v1.0 — December 2024 + +--- + +## 1. Overview + +MediaLit currently uses a single S3 bucket with path-based separation (`/tmp`, `/private`, `/public`) and relies on bucket policies for access control. However, some S3-compatible storage providers (notably Cloudflare R2) do not support bucket policies, making this architecture incompatible. + +This PRD defines a dual-bucket architecture that: + +- Uses separate buckets for private and public files +- Eliminates path prefixes (no `/tmp`, `/private`, `/public` in paths) +- Supports all target S3-compatible providers: AWS S3, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, Wasabi, and MinIO +- Maintains the existing temp → seal → cleanup lifecycle + +--- + +## 2. Objectives + +1. **Bucket Separation**: Deploy private files to `CLOUD_BUCKET_NAME` and public files to `CLOUD_PUBLIC_BUCKET_NAME` +2. **Path Simplification**: Remove path prefixes; use flat structure: `{mediaId}/main.{ext}` and `{mediaId}/thumb.webp` +3. **Provider Compatibility**: Ensure architecture works with all listed S3-compatible storage providers +4. **Lifecycle Preservation**: Maintain temp → seal → cleanup flow +5. **Backward Compatibility**: Support existing deployments (with migration path) +6. **Access Control**: Private bucket requires signed URLs; public bucket allows direct access + +--- + +## 3. Storage Architecture + +### Bucket Structure + +**Private Bucket (`CLOUD_BUCKET_NAME`):** + +``` +s3://private-bucket/ + ├── {mediaId1}/ + │ ├── main.{ext} # temp or private files + │ └── thumb.webp # temp thumbnails (before sealing) + ├── {mediaId2}/ + │ └── main.{ext} + └── ... +``` + +**Public Bucket (`CLOUD_PUBLIC_BUCKET_NAME`):** + +``` +s3://public-bucket/ + ├── {mediaId1}/ + │ ├── main.{ext} # sealed public files + │ └── thumb.webp # all sealed thumbnails (always public) + ├── {mediaId2}/ + │ └── main.{ext} + └── ... +``` + +### Key Principles + +| File Type | Bucket | Path Structure | Access Method | + +|-----------|--------|----------------|---------------| + +| Temp uploads | Private | `{mediaId}/main.{ext}` | Signed URL (internal) | + +| Temp thumbnails | Private | `{mediaId}/thumb.webp` | Signed URL (internal) | + +| Private sealed files | Private | `{mediaId}/main.{ext}` | Signed URL | + +| Public sealed files | Public | `{mediaId}/main.{ext}` | Direct HTTP/CDN | + +| Sealed thumbnails | Public | `{mediaId}/thumb.webp` | Direct HTTP/CDN | + +--- + +## 4. Configuration + +### Environment Variables + +**Required:** + +- `CLOUD_BUCKET_NAME` - Private bucket (temp and private files) +- `CLOUD_PUBLIC_BUCKET_NAME` - Public bucket (public files and all thumbnails) + +**Shared Configuration (applies to both buckets):** + +- `CLOUD_ENDPOINT` - S3 endpoint URL +- `CLOUD_REGION` - Region (if applicable) +- `CLOUD_KEY` - Access key ID +- `CLOUD_SECRET` - Secret access key +- `PATH_PREFIX` - Optional prefix (if needed for multi-tenant) + +**Note**: Both buckets MUST use the same credentials and endpoint. Different credentials/endpoints per bucket are out of scope for v1. + +--- + +## 5. Lifecycle Flow + +### Stage 1: Upload (Temporary) + +**Routes:** + +- `POST /media/create` (multipart upload) +- `POST /media/create/resumable` (TUS upload) + +**Process:** + +1. Generate `mediaId` (nanoid) +2. Upload main file to private bucket: `{mediaId}/main.{ext}` +3. Optionally generate and upload thumbnail to private bucket: `{mediaId}/thumb.webp` +4. Create DB record with `temp: true` + +**Implementation:** + +- `putObject()` uses `CLOUD_BUCKET_NAME` +- Key generation: `{PATH_PREFIX}/{mediaId}/main.{ext}` (if prefix exists) + +--- + +### Stage 2: Seal (Finalize) + +**Route:** + +- `POST /media/seal/{mediaId}` + +**Process:** + +1. Retrieve media record and determine access control +2. **Main file:** + + - If `accessControl === "public"`: Copy from private bucket → public bucket + - If `accessControl === "private"`: Move within private bucket (or copy if cross-bucket move not supported) + +3. **Thumbnail (if exists):** + + - Always copy from private bucket → public bucket + +4. Delete temp files from private bucket +5. Update DB record: `temp: undefined` + +**Implementation Details:** + +- Use `CopyObjectCommand` with source bucket + key and destination bucket + key +- For same-bucket moves (private → private), use copy + delete +- For cross-bucket copies (private → public), use copy from source bucket to destination bucket +- After successful copy, delete source objects + +**Key Generation:** + +- Source: `{PATH_PREFIX}/{mediaId}/main.{ext}` in private bucket +- Destination: `{PATH_PREFIX}/{mediaId}/main.{ext}` in public bucket (for public) or same private bucket (for private) + +--- + +### Stage 3: Cleanup (Background Job) + +**Schedule:** Every N hours (configurable via `TEMP_MEDIA_EXPIRATION_HOURS`) + +**Process:** + +1. Find expired temp media: `temp: true` and `createdAt < cutoff` +2. For each expired media: + + - Delete from private bucket: `{mediaId}/` (all objects with this prefix) + - Delete DB record + +**Implementation:** + +- `deleteFolder()` operates on private bucket only +- Prefix: `{PATH_PREFIX}/{mediaId}/` (if prefix exists) + +--- + +## 6. URL Generation + +### Private Files (Signed URLs) + +**Temp or Private Files:** + +- Source: Private bucket +- Method: Generate signed URL using S3 presigner or CDN signer +- Key: `{PATH_PREFIX}/{mediaId}/main.{ext}` or `{PATH_PREFIX}/{mediaId}/thumb.webp` + +**Implementation:** + +```typescript +// For private bucket +const command = new GetObjectCommand({ + Bucket: CLOUD_BUCKET_NAME, + Key: key, +}); +const url = await getS3SignedUrl(s3Client, command); +``` + +### Public Files (Direct URLs) + +**Public Files and Thumbnails:** + +- Source: Public bucket +- Method: Direct HTTP URL or CDN URL +- Key: `{PATH_PREFIX}/{mediaId}/main.{ext}` or `{PATH_PREFIX}/{mediaId}/thumb.webp` + +**Implementation:** + +```typescript +// For public bucket +const url = `${PUBLIC_ENDPOINT}/${key}`; // or `${CDN_ENDPOINT}/${key}` +``` + +--- + +## 7. S3 Service Layer Changes + +### Current Structure (`apps/api/src/services/s3.ts`) + +**Required Changes:** + +1. **Add Public Bucket Configuration:** + + - Import `CLOUD_PUBLIC_BUCKET_NAME` from constants + - Create separate S3 client instances (or reuse with bucket parameter) + +2. **Update `putObject()`:** + + - Add optional `bucket` parameter (defaults to `CLOUD_BUCKET_NAME`) + - Use specified bucket for PutObjectCommand + +3. **Update `copyObject()`:** + + - Modify to support cross-bucket copies + - Add `sourceBucket` and `destinationBucket` parameters + - Update `CopySource` format: `{sourceBucket}/{sourceKey}` + +4. **Update `deleteObject()` and `deleteFolder()`:** + + - Add optional `bucket` parameter (defaults to `CLOUD_BUCKET_NAME`) + +5. **Update `generateSignedUrl()`:** + + - Add optional `bucket` parameter (defaults to `CLOUD_BUCKET_NAME`) + +6. **Update `getObjectTagging()`:** + + - Add optional `bucket` parameter (defaults to `CLOUD_BUCKET_NAME`) + +--- + +## 8. Provider Compatibility + +### Supported Providers + +| Provider | Bucket Policies | Cross-Bucket Copy | Notes | + +|----------|----------------|-------------------|-------| + +| AWS S3 | ✅ Yes | ✅ Yes (same region) | Standard implementation | + +| Cloudflare R2 | ❌ No | ✅ Yes | Primary use case | + +| DigitalOcean Spaces | ✅ Yes | ✅ Yes | Works with bucket policies or dual-bucket | + +| Backblaze B2 | ✅ Yes | ✅ Yes | Works with bucket policies or dual-bucket | + +| Wasabi | ✅ Yes | ✅ Yes | Works with bucket policies or dual-bucket | + +| MinIO | ✅ Yes | ✅ Yes | Works with bucket policies or dual-bucket | + +### Cross-Bucket Copy Implementation + +For all providers, use `CopyObjectCommand` with: + +```typescript +CopySource: `${sourceBucket}/${sourceKey}`; +Bucket: destinationBucket; +Key: destinationKey; +``` + +**Note**: Some providers may have restrictions on cross-bucket copies (e.g., same region). Document these in deployment guides. + +--- + +## 9. Database Model + +No schema changes required. Existing `MediaSchema` with `temp` field remains valid. + +**Access Control Logic:** + +- `temp: true` → Private bucket, signed URL +- `temp: false` + `accessControl: "private"` → Private bucket, signed URL +- `temp: false` + `accessControl: "public"` → Public bucket, direct URL + +--- + +## 10. Migration Strategy + +### For Existing Deployments + +**Option A: Gradual Migration (Recommended)** + +1. Create new public bucket +2. Update code to use dual-bucket architecture +3. On seal operation, migrate existing files: + + - If public, copy from old location to new public bucket + - If private, keep in private bucket (or move if needed) + +4. Old files remain accessible during migration window + +**Option B: One-Time Migration** + +1. Scan all media records +2. Copy public files to new public bucket +3. Update URL generation logic +4. Remove old path prefixes from keys + +**Migration Script Requirements:** + +- Identify public vs private files from DB records +- Copy files to appropriate buckets +- Update any cached URLs or CDN invalidation + +--- + +## 11. API Changes + +### No Breaking Changes + +All existing API endpoints remain unchanged: + +- `POST /media/create` - Still works (uploads to private bucket) +- `POST /media/seal/{mediaId}` - Still works (moves to appropriate bucket) +- `GET /media/get` - Still works (URLs generated based on bucket) +- `GET /media/{mediaId}` - Still works (URLs generated based on bucket) + +### Internal Changes Only + +- S3 service layer accepts bucket parameters +- Key generation removes path prefixes +- URL generation selects bucket based on access control + +--- + +## 12. Testing Requirements + +### Unit Tests + +- S3 service functions with bucket parameters +- Cross-bucket copy operations +- URL generation for both buckets +- Key generation without path prefixes + +### Integration Tests + +- Upload → Seal → Access flow for public files +- Upload → Seal → Access flow for private files +- Thumbnail always ends up in public bucket +- Cleanup removes from correct bucket + +### Provider-Specific Tests + +- Test with Cloudflare R2 (primary use case) +- Test with AWS S3 (baseline) +- Test with other providers (as needed) + +--- + +## 13. Deployment Checklist + +- [ ] Create `CLOUD_PUBLIC_BUCKET_NAME` bucket in storage provider +- [ ] Configure bucket permissions (public bucket: public read; private bucket: private) +- [ ] Set environment variable `CLOUD_PUBLIC_BUCKET_NAME` +- [ ] Verify `CLOUD_BUCKET_NAME` is set (private bucket) +- [ ] Deploy updated code +- [ ] Run migration script (if applicable) +- [ ] Verify upload → seal → access flow +- [ ] Monitor for any cross-bucket copy errors + +--- + +## 14. Success Metrics + +| Metric | Target | + +|--------|--------| + +| Dual-bucket architecture functional | 100% | + +| Cloudflare R2 compatibility | ✅ Working | + +| All listed providers supported | ✅ Working | + +| Zero breaking API changes | ✅ Maintained | + +| Migration path available | ✅ Documented | + +--- + +## 15. Risks and Mitigations + +| Risk | Mitigation | + +|------|------------| + +| Cross-bucket copy failures | Implement retry logic; fallback to download + upload | + +| Provider-specific restrictions | Document provider limitations; test with each provider | + +| Migration complexity | Provide clear migration scripts; support gradual migration | + +| URL generation errors | Comprehensive testing; fallback to signed URLs if needed | + +--- + +## 16. Deliverables + +1. Updated S3 service layer with bucket parameter support +2. Updated media service with dual-bucket logic +3. Updated key generation (remove path prefixes) +4. Updated URL generation (bucket-aware) +5. Migration documentation and scripts (if applicable) +6. Provider-specific deployment guides +7. Updated tests for dual-bucket architecture + +--- + +## 17. Summary + +This PRD defines a dual-bucket architecture that eliminates path prefixes and uses separate buckets for private and public files. The architecture: + +- ✅ Works with Cloudflare R2 (no bucket policies required) +- ✅ Works with all listed S3-compatible providers +- ✅ Maintains existing API contracts +- ✅ Simplifies path structure (no `/tmp`, `/private`, `/public` prefixes) +- ✅ Preserves temp → seal → cleanup lifecycle +- ✅ Always stores thumbnails in public bucket (for fast CDN delivery) + +The implementation requires changes to the S3 service layer to support bucket parameters and cross-bucket operations, but maintains backward compatibility at the API level. diff --git a/eslint.config.mjs b/eslint.config.mjs index 169a0b94..c408c7b3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -17,7 +17,8 @@ export default defineConfig([ "**/.next/**", ".migrations/**", "apps/docs/.source", - "**/next-env.d.ts", + "apps/docs/out/**", + "**/next-env.d.ts" ]), { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"] }, { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.node } } }, diff --git a/examples/next-app-router/components/TusUploadForm.tsx b/examples/next-app-router/components/TusUploadForm.tsx index 63e4dbbd..7f6f6d31 100644 --- a/examples/next-app-router/components/TusUploadForm.tsx +++ b/examples/next-app-router/components/TusUploadForm.tsx @@ -81,7 +81,6 @@ export default function TusUploadForm() { setUploadProgress(percentage); }, onSuccess: async () => { - console.log("Upload finished!"); setUploading(false); setUploadProgress(100); diff --git a/package.json b/package.json index cb1c4ef7..6edb55bb 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,12 @@ "lint": "eslint --cache --quiet", "prettier": "prettier --check **/*.{ts,tsx,js,css,md}", "lint:fix": "eslint --cache --fix", - "prettier:fix": "prettier --write **/*.{ts,tsx,js,css,md}" + "prettier:fix": "prettier --write **/*.{ts,tsx,js,css,md}", + "release": "./release.sh" }, "lint-staged": { "*.{ts,tsx}": "eslint --cache --fix **/*.{ts,tsx}", "*.{ts,tsx,js,css,md}": "prettier --write" }, "packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808" -} +} \ No newline at end of file diff --git a/packages/medialit/__tests__/index.test.ts b/packages/medialit/__tests__/index.test.ts index e8d0b964..a43116aa 100644 --- a/packages/medialit/__tests__/index.test.ts +++ b/packages/medialit/__tests__/index.test.ts @@ -1,8 +1,8 @@ import { describe, test, mock, beforeEach } from "node:test"; import assert from "node:assert"; import { MediaLit } from "../src"; -import { Media } from "../src/media"; import { Readable } from "stream"; +import { Media } from "@medialit/models"; describe("MediaLit", () => { const mockApiKey = "test-api-key"; @@ -175,31 +175,4 @@ describe("MediaLit", () => { assert.strictEqual(fetchMock.mock.calls.length, 1); }); }); - - describe("other methods", () => { - test("should throw error in browser environment", async () => { - const client = new MediaLit({ apiKey: mockApiKey }); - mockBrowserGlobals(); - - const methods = [ - () => client.delete("test-id"), - () => client.get("test-id"), - () => client.list(), - () => client.getStats(), - () => client.getSettings(), - () => client.updateSettings({ useWebP: true }), - ]; - - for (const method of methods) { - await assert.rejects( - method, - /MediaLit SDK is only meant to be used in a server-side Node.js environment/, - ); - } - - cleanupBrowserGlobals(); - }); - - // ... existing tests for other methods ... - }); }); diff --git a/packages/medialit/src/index.ts b/packages/medialit/src/index.ts index 1ce27363..43cd8ffe 100644 --- a/packages/medialit/src/index.ts +++ b/packages/medialit/src/index.ts @@ -1,4 +1,4 @@ -import { Media } from "./media"; +import type { Media } from "./types"; import { createReadStream } from "fs"; import { Readable } from "stream"; @@ -58,7 +58,6 @@ export class MediaLit { private async createFormData( file: FileInput, ): Promise<{ formData: any; filename: string }> { - this.checkBrowserEnvironment(); const FormData = (await import("form-data")).default; const formData = new FormData(); @@ -79,17 +78,11 @@ export class MediaLit { } async upload(file: FileInput, options: UploadOptions = {}): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const { formData } = await this.createFormData(file); if (options.access) formData.append("access", options.access); if (options.caption) formData.append("caption", options.caption); if (options.group) formData.append("group", options.group); - // formData.append("apikey", this.apiKey); const response = await fetch(`${this.endpoint}/media/create`, { method: "POST", @@ -102,18 +95,13 @@ export class MediaLit { if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "Upload failed"); + throw new Error(error.error || "Upload failed"); } return response.json(); } async delete(mediaId: string): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const response = await fetch( `${this.endpoint}/media/delete/${mediaId}`, { @@ -127,16 +115,28 @@ export class MediaLit { if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "Deletion failed"); + throw new Error(error.error || "Deletion failed"); } } - async get(mediaId: string): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); + async seal(mediaId: string): Promise { + const response = await fetch(`${this.endpoint}/media/seal/${mediaId}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-medialit-apikey": this.apiKey, + }, + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Failed to seal media"); } + return response.json(); + } + + async get(mediaId: string): Promise { const response = await fetch(`${this.endpoint}/media/get/${mediaId}`, { method: "POST", headers: { @@ -147,7 +147,7 @@ export class MediaLit { if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "Failed to get media"); + throw new Error(error.error || "Failed to get media"); } return response.json(); @@ -158,11 +158,6 @@ export class MediaLit { limit: number = 10, filters: { access?: "private" | "public"; group?: string } = {}, ): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const params = new URLSearchParams({ page: page.toString(), limit: limit.toString(), @@ -184,18 +179,13 @@ export class MediaLit { if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "Failed to list media"); + throw new Error(error.error || "Failed to list media"); } return response.json(); } async getSignature(options: { group?: string } = {}): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const response = await fetch( `${this.endpoint}/media/signature/create`, { @@ -220,11 +210,6 @@ export class MediaLit { } async getCount(): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const response = await fetch(`${this.endpoint}/media/get/count`, { method: "POST", headers: { @@ -235,7 +220,7 @@ export class MediaLit { if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "Failed to get count"); + throw new Error(error.error || "Failed to get count"); } const result = await response.json(); @@ -243,11 +228,6 @@ export class MediaLit { } async getStats(): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const response = await fetch(`${this.endpoint}/media/get/size`, { method: "POST", headers: { @@ -258,18 +238,13 @@ export class MediaLit { if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "Failed to get stats"); + throw new Error(error.error || "Failed to get stats"); } return response.json(); } async getSettings(): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const response = await fetch(`${this.endpoint}/settings/media/get`, { method: "POST", headers: { @@ -287,11 +262,6 @@ export class MediaLit { } async updateSettings(settings: MediaSettings): Promise { - this.checkBrowserEnvironment(); - if (!this.apiKey) { - throw new Error(API_KEY_REQUIRED); - } - const response = await fetch(`${this.endpoint}/settings/media/create`, { method: "POST", headers: { @@ -308,34 +278,6 @@ export class MediaLit { throw new Error(error.message || "Failed to update media settings"); } } - - // async signedUpload( - // signature: string, - // file: FileInput, - // options: UploadOptions = {}, - // ): Promise { - // this.checkBrowserEnvironment(); - // const { formData } = await this.createFormData(file); - - // if (options.access) formData.append("access", options.access); - // if (options.caption) formData.append("caption", options.caption); - // if (options.group) formData.append("group", options.group); - - // const response = await fetch(this.endpoint, { - // method: "POST", - // body: formData, - // headers: { - // "x-medialit-signature": signature - // } - // }); - - // if (!response.ok) { - // const error = await response.json(); - // throw new Error( - // error.message || "Upload with signature failed", - // ); - // } - - // return response.json(); - // } } + +export type { Media } from "./types"; diff --git a/packages/medialit/src/media.ts b/packages/medialit/src/types.ts similarity index 70% rename from packages/medialit/src/media.ts rename to packages/medialit/src/types.ts index 3ec60ef8..e51c428d 100644 --- a/packages/medialit/src/media.ts +++ b/packages/medialit/src/types.ts @@ -1,3 +1,5 @@ +export type AccessControl = "private" | "public"; + export interface Media { fileName: string; mediaId: string; @@ -6,8 +8,9 @@ export interface Media { mimeType: string; size: number; thumbnailGenerated: boolean; - accessControl: string; + accessControl: AccessControl; group?: string; caption?: string; file?: string; + temp?: boolean; } diff --git a/packages/models/src/access-control.ts b/packages/models/src/access-control.ts new file mode 100644 index 00000000..e50b1f7f --- /dev/null +++ b/packages/models/src/access-control.ts @@ -0,0 +1,4 @@ +import { AccessControl as AccessControlConstants } from "./constants"; + +export type AccessControl = + (typeof AccessControlConstants)[keyof typeof AccessControlConstants]; diff --git a/packages/models/src/constants.ts b/packages/models/src/constants.ts index 1e6414d2..a4d0c1d9 100644 --- a/packages/models/src/constants.ts +++ b/packages/models/src/constants.ts @@ -6,3 +6,11 @@ export const SubscriptionStatus = { PAUSED: "paused", EXPIRED: "expired", } as const; +export const AccessControl = { + PRIVATE: "private", + PUBLIC: "public", +} as const; +export const PathKey = { + PRIVATE: "i", + PUBLIC: "p", +} as const; diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 2d667051..17e5797b 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -6,5 +6,8 @@ export { default as ApikeySchema } from "./api-key-schema"; export * as Constants from "./constants"; export { Media } from "./media"; export { default as MediaSchema } from "./media-schema"; +export type { MediaWithUserId } from "./media-schema"; export type { SubscriptionStatus } from "./subscription-status"; export * from "./utils"; +export * from "./access-control"; +export * from "./path-key"; diff --git a/packages/models/src/media-schema.ts b/packages/models/src/media-schema.ts index feae1a1c..af7252f4 100644 --- a/packages/models/src/media-schema.ts +++ b/packages/models/src/media-schema.ts @@ -1,7 +1,12 @@ import mongoose from "mongoose"; import { Media } from "./media"; +import { Constants } from "."; -export type MediaWithUserId = Media & { userId: mongoose.Types.ObjectId }; +export type MediaWithUserId = Media & { + userId: mongoose.Types.ObjectId; + temp: boolean; +}; +const accessControlOptions = Object.values(Constants.AccessControl); const MediaSchema = new mongoose.Schema( { @@ -13,9 +18,15 @@ const MediaSchema = new mongoose.Schema( mimeType: { type: String, required: true }, size: { type: Number, required: true }, thumbnailGenerated: { type: Boolean, required: true, default: false }, - accessControl: { type: String, required: true, default: "private" }, + accessControl: { + type: String, + required: true, + enum: accessControlOptions, + default: "private", + }, group: { type: String }, caption: { type: String }, + temp: { type: Boolean, default: true }, }, { timestamps: true, diff --git a/packages/models/src/media.ts b/packages/models/src/media.ts index 3ec60ef8..dc012438 100644 --- a/packages/models/src/media.ts +++ b/packages/models/src/media.ts @@ -1,3 +1,5 @@ +import { AccessControl } from "./access-control"; + export interface Media { fileName: string; mediaId: string; @@ -6,8 +8,9 @@ export interface Media { mimeType: string; size: number; thumbnailGenerated: boolean; - accessControl: string; + accessControl: AccessControl; group?: string; caption?: string; file?: string; + temp?: boolean; } diff --git a/packages/models/src/path-key.ts b/packages/models/src/path-key.ts new file mode 100644 index 00000000..2ae6e790 --- /dev/null +++ b/packages/models/src/path-key.ts @@ -0,0 +1,3 @@ +import { PathKey } from "./constants"; + +export type PathKey = (typeof PathKey)[keyof typeof PathKey]; diff --git a/packages/scripts/.env.example b/packages/scripts/.env.example new file mode 100644 index 00000000..fb0bd539 --- /dev/null +++ b/packages/scripts/.env.example @@ -0,0 +1,18 @@ +# Source bucket to migrate FROM (Required) +SOURCE_BUCKET_NAME=existing-bucket-name + +# Destination buckets (Required) +CLOUD_BUCKET_NAME=new-private-bucket-name +CLOUD_PUBLIC_BUCKET_NAME=new-public-bucket-name + +# AWS / S3 Credentials (Required) +CLOUD_KEY=your-access-key +CLOUD_SECRET=your-secret-key +CLOUD_REGION=us-east-1 +CLOUD_ENDPOINT=https://s3.amazonaws.com + +# Database Connection (Required) +DB_CONNECTION_STRING=mongodb://localhost/medialit + +# Others +PATH_PREFIX=medialit-service diff --git a/packages/scripts/package.json b/packages/scripts/package.json new file mode 100644 index 00000000..0e1323d4 --- /dev/null +++ b/packages/scripts/package.json @@ -0,0 +1,19 @@ +{ + "name": "@medialit/scripts", + "version": "0.0.1", + "private": true, + "scripts": { + "migrate:dual_bucket_architecture": "ts-node src/migrate-to-dual-buckets.ts" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.0.0", + "@medialit/models": "workspace:*", + "dotenv": "^16.0.0", + "mongoose": "^8.0.0" + }, + "devDependencies": { + "ts-node": "^10.9.1", + "typescript": "^5.0.0", + "@types/node": "^20.0.0" + } +} \ No newline at end of file diff --git a/packages/scripts/src/migrate-to-dual-buckets.ts b/packages/scripts/src/migrate-to-dual-buckets.ts new file mode 100644 index 00000000..0148b98e --- /dev/null +++ b/packages/scripts/src/migrate-to-dual-buckets.ts @@ -0,0 +1,232 @@ +/** + * This script migrates the media files from the old bucket structure to the new dual-bucket architecture. + * + * It copies the files from the source bucket to the new private and public buckets. + */ + +import dotenv from "dotenv"; +import path from "path"; +import mongoose from "mongoose"; +import { + S3Client, + CopyObjectCommand, + HeadObjectCommand, +} from "@aws-sdk/client-s3"; +import { MediaSchema, Constants, PathKey } from "@medialit/models"; + +// Load environment variables from local .env config +dotenv.config({ path: path.resolve(__dirname, "../.env") }); + +const MediaModel = mongoose.model("Media", MediaSchema); + +const SOURCE_BUCKET = process.env.SOURCE_BUCKET_NAME; +const PRIVATE_BUCKET = process.env.CLOUD_BUCKET_NAME; +const PUBLIC_BUCKET = process.env.CLOUD_PUBLIC_BUCKET_NAME; +const REGION = process.env.CLOUD_REGION; +const CLOUD_KEY = process.env.CLOUD_KEY; +const CLOUD_SECRET = process.env.CLOUD_SECRET; +const CLOUD_ENDPOINT = process.env.CLOUD_ENDPOINT; +const PATH_PREFIX = process.env.PATH_PREFIX; +const DB_CONNECTION_STRING = process.env.DB_CONNECTION_STRING; + +// Check for dry-run flag +const isDryRun = process.argv.includes("--dry-run"); + +const usage = ` +Usage: + pnpm migrate:dual_bucket_architecture [flags] + +Flags: + --dry-run Simulate the migration without copying files. + +Required Environment Variables: + SOURCE_BUCKET_NAME Name of the source bucket. + CLOUD_BUCKET_NAME Name of the destination private bucket. + CLOUD_PUBLIC_BUCKET_NAME Name of the destination public bucket. + DB_CONNECTION_STRING MongoDB connection string. + CLOUD_KEY, CLOUD_SECRET AWS/S3 credentials. + CLOUD_REGION, CLOUD_ENDPOINT AWS/S3 configuration. +`; + +if ( + !SOURCE_BUCKET || + !PRIVATE_BUCKET || + !PUBLIC_BUCKET || + !CLOUD_KEY || + !CLOUD_SECRET || + !CLOUD_ENDPOINT || + !DB_CONNECTION_STRING +) { + console.error("❌ Error: Missing required environment variables."); + console.error(usage); + process.exit(1); +} + +const s3Client = new S3Client({ + region: REGION, + endpoint: CLOUD_ENDPOINT, + credentials: { + accessKeyId: CLOUD_KEY!, + secretAccessKey: CLOUD_SECRET!, + }, + forcePathStyle: true, // Often needed for non-AWS S3 +}); + +// New key generator (using i/p) +function generateDestKey({ + mediaId, + path: pathKey, + filename, +}: { + mediaId: string; + path: PathKey; + filename: string; +}) { + return `${PATH_PREFIX ? `${PATH_PREFIX}/` : ""}${pathKey}/${mediaId}/${filename}`; +} + +// Old key generator (using private/public) +function generateSourceKey({ + mediaId, + filename, + access, +}: { + mediaId: string; + filename: string; + access: string; +}) { + const pathKey = access === "private" ? "private" : "public"; + return `${PATH_PREFIX ? `${PATH_PREFIX}/` : ""}${pathKey}/${mediaId}/${filename}`; +} + +async function copyFile( + sourceKey: string, + destinationBucket: string, + destinationKey: string, +) { + const filename = path.basename(sourceKey); + if (isDryRun) { + console.log(` ├── 📄 ${filename}`); + console.log(` FROM: ${sourceKey}`); + console.log(` TO: ${destinationKey} (🪣 ${destinationBucket})`); + return; + } + + try { + // Check if source exists + try { + await s3Client.send( + new HeadObjectCommand({ + Bucket: SOURCE_BUCKET!, + Key: sourceKey, + }), + ); + } catch (e: any) { + if (e.name === "NotFound") { + console.log(` ├── ⏭️ [SKIP] Source not found: ${filename}`); + return; + } + throw e; + } + + // Copy + // Note: CopyObjectCommand input requires CopySource to be URL encoded "Bucket/Key" + const copySource = encodeURI(`${SOURCE_BUCKET!}/${sourceKey}`); + + await s3Client.send( + new CopyObjectCommand({ + Bucket: destinationBucket, + CopySource: copySource, + Key: destinationKey, + }), + ); + console.log(` ├── ✅ [OK] Copied ${filename}`); + } catch (error: any) { + console.error( + ` ├── ❌ [ERROR] Failed to copy ${filename}: ${error.message}`, + ); + } +} + +async function migrate() { + console.log("🚀 Starting migration..."); + console.log("🔌 Connecting to Database..."); + await mongoose.connect(DB_CONNECTION_STRING!); + console.log("✅ Connected to Database."); + + if (isDryRun) { + console.log("⚠️ DRY RUN MODE ENABLED - NO CHANGES WILL BE MADE"); + } + + console.log(`ℹ️ Source Bucket: ${SOURCE_BUCKET}`); + console.log(`ℹ️ Target Private: ${PRIVATE_BUCKET}`); + console.log(`ℹ️ Target Public: ${PUBLIC_BUCKET}`); + + const cursor = MediaModel.find({ temp: { $ne: true } }).cursor(); // Iterate non-temp media + + let processed = 0; + for ( + let mediaWait = await cursor.next(); + mediaWait != null; + mediaWait = await cursor.next() + ) { + const media = mediaWait as any; + processed++; + if (processed % 100 === 0) + console.log(`⏳ Processed ${processed} records...`); + + const icon = media.accessControl === "private" ? "🔒" : "🌍"; + console.log(`${icon} Media: ${media.mediaId}`); + + const fileExtension = path.extname(media.fileName).replace(".", ""); + + // 1. Thumbnails + if (media.thumbnailGenerated) { + const oldKey = generateSourceKey({ + mediaId: media.mediaId, + filename: "thumb.webp", + access: "public", + }); + + const newKey = generateDestKey({ + mediaId: media.mediaId, + path: Constants.PathKey.PUBLIC, + filename: "thumb.webp", + }); + + await copyFile(oldKey, PUBLIC_BUCKET!, newKey); + } + + // 2. Main File + const mainFilename = `main.${fileExtension}`; + const oldMainKey = generateSourceKey({ + mediaId: media.mediaId, + filename: mainFilename, + access: media.accessControl, + }); + + if (media.accessControl !== "private") { + const newMainKey = generateDestKey({ + mediaId: media.mediaId, + path: Constants.PathKey.PUBLIC, + filename: mainFilename, + }); + await copyFile(oldMainKey, PUBLIC_BUCKET!, newMainKey); + } else { + const newMainKey = generateDestKey({ + mediaId: media.mediaId, + path: Constants.PathKey.PRIVATE, + filename: mainFilename, + }); + await copyFile(oldMainKey, PRIVATE_BUCKET!, newMainKey); + } + } + + console.log("✨ Migration completed."); + process.exit(0); +} + +migrate().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/scripts/tsconfig.json b/packages/scripts/tsconfig.json new file mode 100644 index 00000000..4fe11501 --- /dev/null +++ b/packages/scripts/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eaf71257..016f9d50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,14 +51,14 @@ importers: apps/api: dependencies: '@aws-sdk/client-s3': - specifier: ^3.55.0 - version: 3.782.0 + specifier: ^3.922.0 + version: 3.922.0 '@aws-sdk/cloudfront-signer': specifier: ^3.572.0 - version: 3.775.0 + version: 3.921.0 '@aws-sdk/s3-request-presigner': - specifier: ^3.55.0 - version: 3.782.0 + specifier: ^3.922.0 + version: 3.922.0 '@medialit/images': specifier: workspace:* version: link:../../packages/images @@ -84,29 +84,29 @@ importers: specifier: ^2.8.5 version: 2.8.5 dotenv: - specifier: ^16.4.7 - version: 16.4.7 + specifier: ^17.2.3 + version: 17.2.3 express: specifier: ^4.2.0 version: 4.21.2 express-fileupload: - specifier: ^1.3.1 - version: 1.5.1 + specifier: ^1.5.2 + version: 1.5.2 joi: specifier: ^17.6.0 version: 17.13.3 mongoose: - specifier: ^8.0.1 - version: 8.13.2 + specifier: ^8.19.3 + version: 8.19.3 passport: - specifier: ^0.5.2 - version: 0.5.3 + specifier: ^0.7.0 + version: 0.7.0 passport-jwt: - specifier: ^4.0.0 + specifier: ^4.0.1 version: 4.0.1 pino: - specifier: ^7.9.1 - version: 7.11.0 + specifier: ^10.1.0 + version: 10.1.0 devDependencies: '@types/cors': specifier: ^2.8.12 @@ -131,25 +131,28 @@ importers: version: 3.0.13 '@typescript-eslint/eslint-plugin': specifier: ^5.17.0 - version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^5.17.0 - version: 5.62.0(eslint@8.57.1)(typescript@5.8.3) + version: 5.62.0(eslint@8.57.1)(typescript@5.9.3) eslint: specifier: ^8.12.0 version: 8.57.1 nodemon: - specifier: ^3.0.3 - version: 3.1.9 + specifier: ^3.1.10 + version: 3.1.10 ts-node: - specifier: ^10.7.0 - version: 10.9.2(@types/node@22.14.1)(typescript@5.8.3) + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.14.1)(typescript@5.9.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 tsx: - specifier: ^4.7.0 - version: 4.19.3 + specifier: ^4.20.6 + version: 4.20.6 typescript: - specifier: ^5.2.2 - version: 5.8.3 + specifier: ^5.9.3 + version: 5.9.3 apps/docs: dependencies: @@ -420,6 +423,31 @@ importers: specifier: ^5.2.2 version: 5.8.3 + packages/scripts: + dependencies: + '@aws-sdk/client-s3': + specifier: ^3.0.0 + version: 3.922.0 + '@medialit/models': + specifier: workspace:* + version: link:../models + dotenv: + specifier: ^16.0.0 + version: 16.6.1 + mongoose: + specifier: ^8.0.0 + version: 8.19.3 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.10.6 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.10.6)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + packages/thumbnail: devDependencies: '@types/node': @@ -492,135 +520,135 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.782.0': - resolution: {integrity: sha512-V6JR2JAGYQY7J8wk5un5n/ja2nfCUyyoRCF8Du8JL91NGI8i41Mdr/TzuOGwTgFl6RSXb/ge1K1jk30OH4MugQ==} + '@aws-sdk/client-s3@3.922.0': + resolution: {integrity: sha512-SZRaZUUAHCWfEyBf4SRSPd29ko4uFoJpfd0E/w1meE68XhFB52FTtz/71UqYcwqZmN+s7oUNFFZT+DE/dnQSEA==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.782.0': - resolution: {integrity: sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg==} + '@aws-sdk/client-sso@3.922.0': + resolution: {integrity: sha512-jdHs7uy7cSpiMvrxhYmqHyJxgK7hyqw4plG8OQ4YTBpq0SbfAxdoOuOkwJ1IVUUQho4otR1xYYjiX/8e8J8qwQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/cloudfront-signer@3.775.0': - resolution: {integrity: sha512-HQuVkDStuirROj+C2Ms0A/aolP3U64Eur7JMO6O7ezcght2rJMfopaYMHZvFmiXcnCcZ5Ju0V0Qsr+oxsYbfng==} + '@aws-sdk/cloudfront-signer@3.921.0': + resolution: {integrity: sha512-U64GbPOoKCvnq/YgHR6WW9vz9J8IglUNSmVl0ZdWGn2TRXh+UU37N8YMbvEdaiSHCtrOwRSmR1r1j1aDdDoduQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.775.0': - resolution: {integrity: sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==} + '@aws-sdk/core@3.922.0': + resolution: {integrity: sha512-EvfP4cqJfpO3L2v5vkIlTkMesPtRwWlMfsaW6Tpfm7iYfBOuTi6jx60pMDMTyJNVfh6cGmXwh/kj1jQdR+w99Q==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.775.0': - resolution: {integrity: sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==} + '@aws-sdk/credential-provider-env@3.922.0': + resolution: {integrity: sha512-WikGQpKkROJSK3D3E7odPjZ8tU7WJp5/TgGdRuZw3izsHUeH48xMv6IznafpRTmvHcjAbDQj4U3CJZNAzOK/OQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.775.0': - resolution: {integrity: sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==} + '@aws-sdk/credential-provider-http@3.922.0': + resolution: {integrity: sha512-i72DgHMK7ydAEqdzU0Duqh60Q8W59EZmRJ73y0Y5oFmNOqnYsAI+UXyOoCsubp+Dkr6+yOwAn1gPt1XGE9Aowg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.782.0': - resolution: {integrity: sha512-wd4KdRy2YjLsE4Y7pz00470Iip06GlRHkG4dyLW7/hFMzEO2o7ixswCWp6J2VGZVAX64acknlv2Q0z02ebjmhw==} + '@aws-sdk/credential-provider-ini@3.922.0': + resolution: {integrity: sha512-bVF+pI5UCLNkvbiZr/t2fgTtv84s8FCdOGAPxQiQcw5qOZywNuuCCY3wIIchmQr6GJr8YFkEp5LgDCac5EC5aQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.782.0': - resolution: {integrity: sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw==} + '@aws-sdk/credential-provider-node@3.922.0': + resolution: {integrity: sha512-agCwaD6mBihToHkjycL8ObIS2XOnWypWZZWhJSoWyHwFrhEKz1zGvgylK9Dc711oUfU+zU6J8e0JPKNJMNb3BQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.775.0': - resolution: {integrity: sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==} + '@aws-sdk/credential-provider-process@3.922.0': + resolution: {integrity: sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.782.0': - resolution: {integrity: sha512-1y1ucxTtTIGDSNSNxriQY8msinilhe9gGvQpUDYW9gboyC7WQJPDw66imy258V6osdtdi+xoHzVCbCz3WhosMQ==} + '@aws-sdk/credential-provider-sso@3.922.0': + resolution: {integrity: sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.782.0': - resolution: {integrity: sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ==} + '@aws-sdk/credential-provider-web-identity@3.922.0': + resolution: {integrity: sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-bucket-endpoint@3.775.0': - resolution: {integrity: sha512-qogMIpVChDYr4xiUNC19/RDSw/sKoHkAhouS6Skxiy6s27HBhow1L3Z1qVYXuBmOZGSWPU0xiyZCvOyWrv9s+Q==} + '@aws-sdk/middleware-bucket-endpoint@3.922.0': + resolution: {integrity: sha512-Dpr2YeOaLFqt3q1hocwBesynE3x8/dXZqXZRuzSX/9/VQcwYBFChHAm4mTAl4zuvArtDbLrwzWSxmOWYZGtq5w==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-expect-continue@3.775.0': - resolution: {integrity: sha512-Apd3owkIeUW5dnk3au9np2IdW2N0zc9NjTjHiH+Mx3zqwSrc+m+ANgJVgk9mnQjMzU/vb7VuxJ0eqdEbp5gYsg==} + '@aws-sdk/middleware-expect-continue@3.922.0': + resolution: {integrity: sha512-xmnLWMtmHJHJBupSWMUEW1gyxuRIeQ1Ov2xa8Tqq77fPr4Ft2AluEwiDMaZIMHoAvpxWKEEt9Si59Li7GIA+bQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.775.0': - resolution: {integrity: sha512-OmHLfRIb7IIXsf9/X/pMOlcSV3gzW/MmtPSZTkrz5jCTKzWXd7eRoyOJqewjsaC6KMAxIpNU77FoAd16jOZ21A==} + '@aws-sdk/middleware-flexible-checksums@3.922.0': + resolution: {integrity: sha512-G363np7YcJhf+gBucskdv8cOTbs2TRwocEzRupuqDIooGDlLBlfJrvwehdgtWR8l53yjJR3zcHvGrVPTe2h8Nw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.775.0': - resolution: {integrity: sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==} + '@aws-sdk/middleware-host-header@3.922.0': + resolution: {integrity: sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-location-constraint@3.775.0': - resolution: {integrity: sha512-8TMXEHZXZTFTckQLyBT5aEI8fX11HZcwZseRifvBKKpj0RZDk4F0EEYGxeNSPpUQ7n+PRWyfAEnnZNRdAj/1NQ==} + '@aws-sdk/middleware-location-constraint@3.922.0': + resolution: {integrity: sha512-T4iqd7WQ2DDjCH/0s50mnhdoX+IJns83ZE+3zj9IDlpU0N2aq8R91IG890qTfYkUEdP9yRm0xir/CNed+v6Dew==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.775.0': - resolution: {integrity: sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==} + '@aws-sdk/middleware-logger@3.922.0': + resolution: {integrity: sha512-AkvYO6b80FBm5/kk2E636zNNcNgjztNNUxpqVx+huyGn9ZqGTzS4kLqW2hO6CBe5APzVtPCtiQsXL24nzuOlAg==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.775.0': - resolution: {integrity: sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==} + '@aws-sdk/middleware-recursion-detection@3.922.0': + resolution: {integrity: sha512-TtSCEDonV/9R0VhVlCpxZbp/9sxQvTTRKzIf8LxW3uXpby6Wl8IxEciBJlxmSkoqxh542WRcko7NYODlvL/gDA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.775.0': - resolution: {integrity: sha512-zsvcu7cWB28JJ60gVvjxPCI7ZU7jWGcpNACPiZGyVtjYXwcxyhXbYEVDSWKsSA6ERpz9XrpLYod8INQWfW3ECg==} + '@aws-sdk/middleware-sdk-s3@3.922.0': + resolution: {integrity: sha512-ygg8lME1oFAbsH42ed2wtGqfHLoT5irgx6VC4X98j79fV1qXEwwwbqMsAiMQ/HJehpjqAFRVsHox3MHLN48Z5A==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-ssec@3.775.0': - resolution: {integrity: sha512-Iw1RHD8vfAWWPzBBIKaojO4GAvQkHOYIpKdAfis/EUSUmSa79QsnXnRqsdcE0mCB0Ylj23yi+ah4/0wh9FsekA==} + '@aws-sdk/middleware-ssec@3.922.0': + resolution: {integrity: sha512-eHvSJZTSRJO+/tjjGD6ocnPc8q9o3m26+qbwQTu/4V6yOJQ1q+xkDZNqwJQphL+CodYaQ7uljp8g1Ji/AN3D9w==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.782.0': - resolution: {integrity: sha512-i32H2R6IItX+bQ2p4+v2gGO2jA80jQoJO2m1xjU9rYWQW3+ErWy4I5YIuQHTBfb6hSdAHbaRfqPDgbv9J2rjEg==} + '@aws-sdk/middleware-user-agent@3.922.0': + resolution: {integrity: sha512-N4Qx/9KP3oVQBJOrSghhz8iZFtUC2NNeSZt88hpPhbqAEAtuX8aD8OzVcpnAtrwWqy82Yd2YTxlkqMGkgqnBsQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.782.0': - resolution: {integrity: sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA==} + '@aws-sdk/nested-clients@3.922.0': + resolution: {integrity: sha512-uYvKCF1TGh/MuJ4TMqmUM0Csuao02HawcseG4LUDyxdUsd/EFuxalWq1Cx4fKZQ2K8F504efZBjctMAMNY+l7A==} engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.775.0': - resolution: {integrity: sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==} + '@aws-sdk/region-config-resolver@3.922.0': + resolution: {integrity: sha512-44Y/rNNwhngR2KHp6gkx//TOr56/hx6s4l+XLjOqH7EBCHL7XhnrT1y92L+DLiroVr1tCSmO8eHQwBv0Y2+mvw==} engines: {node: '>=18.0.0'} - '@aws-sdk/s3-request-presigner@3.782.0': - resolution: {integrity: sha512-Er8hdjc9zkxTh15MjdnMYggtUrGknDiuD1FwdW035kn/kwWop587G9rnRa1crhmyKRjLMn0Ki3fsyFUm/943XA==} + '@aws-sdk/s3-request-presigner@3.922.0': + resolution: {integrity: sha512-x/WZXOMAN10X/hbjHnaXjtU34RmV3/eJMiHoJsohquSgz8+pfRN1DeK65oa/XPoKCMPfV31RfHSzCduligHfsQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.775.0': - resolution: {integrity: sha512-cnGk8GDfTMJ8p7+qSk92QlIk2bmTmFJqhYxcXZ9PysjZtx0xmfCMxnG3Hjy1oU2mt5boPCVSOptqtWixayM17g==} + '@aws-sdk/signature-v4-multi-region@3.922.0': + resolution: {integrity: sha512-mmsgEEL5pE+A7gFYiJMDBCLVciaXq4EFI5iAP7bPpnHvOplnNOYxVy2IreKMllGvrfjVyLnwxzZYlo5zZ65FWg==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.782.0': - resolution: {integrity: sha512-4tPuk/3+THPrzKaXW4jE2R67UyGwHLFizZ47pcjJWbhb78IIJAy94vbeqEQ+veS84KF5TXcU7g5jGTXC0D70Wg==} + '@aws-sdk/token-providers@3.922.0': + resolution: {integrity: sha512-/inmPnjZE0ZBE16zaCowAvouSx05FJ7p6BQYuzlJ8vxEU0sS0Hf8fvhuiRnN9V9eDUPIBY+/5EjbMWygXL4wlQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.775.0': - resolution: {integrity: sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==} + '@aws-sdk/types@3.922.0': + resolution: {integrity: sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-arn-parser@3.723.0': - resolution: {integrity: sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==} + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.782.0': - resolution: {integrity: sha512-/RJOAO7o7HI6lEa4ASbFFLHGU9iPK876BhsVfnl54MvApPVYWQ9sHO0anOUim2S5lQTwd/6ghuH3rFYSq/+rdw==} + '@aws-sdk/util-endpoints@3.922.0': + resolution: {integrity: sha512-4ZdQCSuNMY8HMlR1YN4MRDdXuKd+uQTeKIr5/pIM+g3TjInZoj8imvXudjcrFGA63UF3t92YVTkBq88mg58RXQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-format-url@3.775.0': - resolution: {integrity: sha512-Nw4nBeyCbWixoGh8NcVpa/i8McMA6RXJIjQFyloJLaPr7CPquz7ZbSl0MUWMFVwP/VHaJ7B+lNN3Qz1iFCEP/Q==} + '@aws-sdk/util-format-url@3.922.0': + resolution: {integrity: sha512-UYLWPvZEd6TYilNkrQrIeXh2bXZsY3ighYErSEjD24f3JQhg0XdXoR/QHIE8licHu2qFrTRM6yi9LH1GY6X0cg==} engines: {node: '>=18.0.0'} '@aws-sdk/util-locate-window@3.723.0': resolution: {integrity: sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-browser@3.775.0': - resolution: {integrity: sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==} + '@aws-sdk/util-user-agent-browser@3.922.0': + resolution: {integrity: sha512-qOJAERZ3Plj1st7M4Q5henl5FRpE30uLm6L9edZqZXGR6c7ry9jzexWamWVpQ4H4xVAVmiO9dIEBAfbq4mduOA==} - '@aws-sdk/util-user-agent-node@3.782.0': - resolution: {integrity: sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA==} + '@aws-sdk/util-user-agent-node@3.922.0': + resolution: {integrity: sha512-NrPe/Rsr5kcGunkog0eBV+bY0inkRELsD2SacC4lQZvZiXf8VJ2Y7j+Yq1tB+h+FPLsdt3v9wItIvDf/laAm0Q==} engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -628,8 +656,12 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.775.0': - resolution: {integrity: sha512-b9NGO6FKJeLGYnV7Z1yvcP1TNU4dkD5jNsLWOF1/sygZoASaQhNOlaiJ/1OH331YQ1R1oWk38nBb0frsYkDsOQ==} + '@aws-sdk/xml-builder@3.921.0': + resolution: {integrity: sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q==} + engines: {node: '>=18.0.0'} + + '@aws/lambda-invoke-store@0.1.1': + resolution: {integrity: sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==} engines: {node: '>=18.0.0'} '@babel/code-frame@7.26.2': @@ -1254,6 +1286,9 @@ packages: '@mongodb-js/saslprep@1.2.2': resolution: {integrity: sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==} + '@mongodb-js/saslprep@1.3.2': + resolution: {integrity: sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==} + '@napi-rs/wasm-runtime@0.2.8': resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==} @@ -1337,6 +1372,9 @@ packages: '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2234,216 +2272,220 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - '@smithy/abort-controller@4.0.2': - resolution: {integrity: sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==} + '@smithy/abort-controller@4.2.4': + resolution: {integrity: sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader-native@4.0.0': - resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==} + '@smithy/chunked-blob-reader-native@4.2.1': + resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader@5.0.0': - resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==} + '@smithy/chunked-blob-reader@5.2.0': + resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} engines: {node: '>=18.0.0'} - '@smithy/config-resolver@4.1.0': - resolution: {integrity: sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==} + '@smithy/config-resolver@4.4.2': + resolution: {integrity: sha512-4Jys0ni2tB2VZzgslbEgszZyMdTkPOFGA8g+So/NjR8oy6Qwaq4eSwsrRI+NMtb0Dq4kqCzGUu/nGUx7OM/xfw==} engines: {node: '>=18.0.0'} - '@smithy/core@3.2.0': - resolution: {integrity: sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==} + '@smithy/core@3.17.2': + resolution: {integrity: sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.0.2': - resolution: {integrity: sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==} + '@smithy/credential-provider-imds@4.2.4': + resolution: {integrity: sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@4.0.2': - resolution: {integrity: sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==} + '@smithy/eventstream-codec@4.2.4': + resolution: {integrity: sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-browser@4.0.2': - resolution: {integrity: sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==} + '@smithy/eventstream-serde-browser@4.2.4': + resolution: {integrity: sha512-d5T7ZS3J/r8P/PDjgmCcutmNxnSRvPH1U6iHeXjzI50sMr78GLmFcrczLw33Ap92oEKqa4CLrkAPeSSOqvGdUA==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-config-resolver@4.1.0': - resolution: {integrity: sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==} + '@smithy/eventstream-serde-config-resolver@4.3.4': + resolution: {integrity: sha512-lxfDT0UuSc1HqltOGsTEAlZ6H29gpfDSdEPTapD5G63RbnYToZ+ezjzdonCCH90j5tRRCw3aLXVbiZaBW3VRVg==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-node@4.0.2': - resolution: {integrity: sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==} + '@smithy/eventstream-serde-node@4.2.4': + resolution: {integrity: sha512-TPhiGByWnYyzcpU/K3pO5V7QgtXYpE0NaJPEZBCa1Y5jlw5SjqzMSbFiLb+ZkJhqoQc0ImGyVINqnq1ze0ZRcQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-universal@4.0.2': - resolution: {integrity: sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==} + '@smithy/eventstream-serde-universal@4.2.4': + resolution: {integrity: sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.0.2': - resolution: {integrity: sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==} + '@smithy/fetch-http-handler@5.3.5': + resolution: {integrity: sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ==} engines: {node: '>=18.0.0'} - '@smithy/hash-blob-browser@4.0.2': - resolution: {integrity: sha512-3g188Z3DyhtzfBRxpZjU8R9PpOQuYsbNnyStc/ZVS+9nVX1f6XeNOa9IrAh35HwwIZg+XWk8bFVtNINVscBP+g==} + '@smithy/hash-blob-browser@4.2.5': + resolution: {integrity: sha512-kCdgjD2J50qAqycYx0imbkA9tPtyQr1i5GwbK/EOUkpBmJGSkJe4mRJm+0F65TUSvvui1HZ5FFGFCND7l8/3WQ==} engines: {node: '>=18.0.0'} - '@smithy/hash-node@4.0.2': - resolution: {integrity: sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==} + '@smithy/hash-node@4.2.4': + resolution: {integrity: sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw==} engines: {node: '>=18.0.0'} - '@smithy/hash-stream-node@4.0.2': - resolution: {integrity: sha512-POWDuTznzbIwlEXEvvXoPMS10y0WKXK790soe57tFRfvf4zBHyzE529HpZMqmDdwG9MfFflnyzndUQ8j78ZdSg==} + '@smithy/hash-stream-node@4.2.4': + resolution: {integrity: sha512-amuh2IJiyRfO5MV0X/YFlZMD6banjvjAwKdeJiYGUbId608x+oSNwv3vlyW2Gt6AGAgl3EYAuyYLGRX/xU8npQ==} engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@4.0.2': - resolution: {integrity: sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==} + '@smithy/invalid-dependency@4.2.4': + resolution: {integrity: sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw==} engines: {node: '>=18.0.0'} '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} - '@smithy/is-array-buffer@4.0.0': - resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==} + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} - '@smithy/md5-js@4.0.2': - resolution: {integrity: sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA==} + '@smithy/md5-js@4.2.4': + resolution: {integrity: sha512-h7kzNWZuMe5bPnZwKxhVbY1gan5+TZ2c9JcVTHCygB14buVGOZxLl+oGfpY2p2Xm48SFqEWdghpvbBdmaz3ncQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@4.0.2': - resolution: {integrity: sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==} + '@smithy/middleware-content-length@4.2.4': + resolution: {integrity: sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.1.0': - resolution: {integrity: sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==} + '@smithy/middleware-endpoint@4.3.6': + resolution: {integrity: sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.1.0': - resolution: {integrity: sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==} + '@smithy/middleware-retry@4.4.6': + resolution: {integrity: sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.0.3': - resolution: {integrity: sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==} + '@smithy/middleware-serde@4.2.4': + resolution: {integrity: sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg==} engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@4.0.2': - resolution: {integrity: sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==} + '@smithy/middleware-stack@4.2.4': + resolution: {integrity: sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.0.2': - resolution: {integrity: sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==} + '@smithy/node-config-provider@4.3.4': + resolution: {integrity: sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.0.4': - resolution: {integrity: sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==} + '@smithy/node-http-handler@4.4.4': + resolution: {integrity: sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.0.2': - resolution: {integrity: sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==} + '@smithy/property-provider@4.2.4': + resolution: {integrity: sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@5.1.0': - resolution: {integrity: sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==} + '@smithy/protocol-http@5.3.4': + resolution: {integrity: sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw==} engines: {node: '>=18.0.0'} - '@smithy/querystring-builder@4.0.2': - resolution: {integrity: sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==} + '@smithy/querystring-builder@4.2.4': + resolution: {integrity: sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig==} engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@4.0.2': - resolution: {integrity: sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==} + '@smithy/querystring-parser@4.2.4': + resolution: {integrity: sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.0.2': - resolution: {integrity: sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==} + '@smithy/service-error-classification@4.2.4': + resolution: {integrity: sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng==} engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.0.2': - resolution: {integrity: sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==} + '@smithy/shared-ini-file-loader@4.3.4': + resolution: {integrity: sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.0.2': - resolution: {integrity: sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==} + '@smithy/signature-v4@5.3.4': + resolution: {integrity: sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.2.0': - resolution: {integrity: sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==} + '@smithy/smithy-client@4.9.2': + resolution: {integrity: sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg==} engines: {node: '>=18.0.0'} - '@smithy/types@4.2.0': - resolution: {integrity: sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==} + '@smithy/types@4.8.1': + resolution: {integrity: sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA==} engines: {node: '>=18.0.0'} - '@smithy/url-parser@4.0.2': - resolution: {integrity: sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==} + '@smithy/url-parser@4.2.4': + resolution: {integrity: sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg==} engines: {node: '>=18.0.0'} - '@smithy/util-base64@4.0.0': - resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==} + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} engines: {node: '>=18.0.0'} - '@smithy/util-body-length-browser@4.0.0': - resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==} + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} engines: {node: '>=18.0.0'} - '@smithy/util-body-length-node@4.0.0': - resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==} + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} engines: {node: '>=18.0.0'} '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} - '@smithy/util-buffer-from@4.0.0': - resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==} + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} engines: {node: '>=18.0.0'} - '@smithy/util-config-provider@4.0.0': - resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.0.8': - resolution: {integrity: sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==} + '@smithy/util-defaults-mode-browser@4.3.5': + resolution: {integrity: sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.0.8': - resolution: {integrity: sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==} + '@smithy/util-defaults-mode-node@4.2.8': + resolution: {integrity: sha512-gIoTf9V/nFSIZ0TtgDNLd+Ws59AJvijmMDYrOozoMHPJaG9cMRdqNO50jZTlbM6ydzQYY8L/mQ4tKSw/TB+s6g==} engines: {node: '>=18.0.0'} - '@smithy/util-endpoints@3.0.2': - resolution: {integrity: sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==} + '@smithy/util-endpoints@3.2.4': + resolution: {integrity: sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg==} engines: {node: '>=18.0.0'} - '@smithy/util-hex-encoding@4.0.0': - resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==} + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} - '@smithy/util-middleware@4.0.2': - resolution: {integrity: sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==} + '@smithy/util-middleware@4.2.4': + resolution: {integrity: sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.0.2': - resolution: {integrity: sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==} + '@smithy/util-retry@4.2.4': + resolution: {integrity: sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.2.0': - resolution: {integrity: sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==} + '@smithy/util-stream@4.5.5': + resolution: {integrity: sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w==} engines: {node: '>=18.0.0'} - '@smithy/util-uri-escape@4.0.0': - resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==} + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@smithy/util-utf8@4.0.0': - resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==} + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} engines: {node: '>=18.0.0'} - '@smithy/util-waiter@4.0.3': - resolution: {integrity: sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==} + '@smithy/util-waiter@4.2.4': + resolution: {integrity: sha512-roKXtXIC6fopFvVOju8VYHtguc/jAcMlK8IlDOHsrQn0ayMkHynjm/D2DCMRf7MJFXzjHhlzg2edr3QPEakchQ==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} '@standard-schema/spec@1.0.0': @@ -3238,6 +3280,10 @@ packages: resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==} engines: {node: '>=16.20.1'} + bson@6.10.4: + resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} + engines: {node: '>=16.20.1'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -3648,17 +3694,18 @@ packages: resolution: {integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==} deprecated: Use your platform's native DOMException instead - dotenv@16.4.7: - resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexify@4.1.3: - resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3999,8 +4046,8 @@ packages: resolution: {integrity: sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==} engines: {node: '>= 6'} - express-fileupload@1.5.1: - resolution: {integrity: sha512-LsYG1ALXEB7vlmjuSw8ABeOctMp8a31aUC5ZF55zuz7O2jLFnmJYrCv10py357ky48aEoBQ/9bVXgFynjvaPmA==} + express-fileupload@1.5.2: + resolution: {integrity: sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg==} engines: {node: '>=12.0.0'} express@4.21.2: @@ -4050,12 +4097,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-redact@3.5.0: - resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} - engines: {node: '>=6'} - - fast-xml-parser@4.4.1: - resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true fastq@1.19.1: @@ -5503,10 +5546,41 @@ packages: socks: optional: true + mongodb@6.20.0: + resolution: {integrity: sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 || ^2.0.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.3.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + mongoose@8.13.2: resolution: {integrity: sha512-riCBqZmNkYBWjXpM3qWLDQw7QmTKsVZDPhLXFJqC87+OjocEVpvS3dA2BPPUiLAu+m0/QmEj5pSXKhH+/DgerQ==} engines: {node: '>=16.20.1'} + mongoose@8.19.3: + resolution: {integrity: sha512-fTAGaIohkk8wCggMuBuqTVD4YrM1/J8cBr1ekqzFqtz65qkLjtX2dcy3NH1e+2rk2365dyrrsPAnt4YTxBhEiQ==} + engines: {node: '>=16.20.1'} + mpath@0.9.0: resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} engines: {node: '>=4.0.0'} @@ -5620,8 +5694,8 @@ packages: resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==} engines: {node: '>=6.0.0'} - nodemon@3.1.9: - resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==} + nodemon@3.1.10: + resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} engines: {node: '>=10'} hasBin: true @@ -5709,8 +5783,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - on-exit-leak-free@0.2.0: - resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -5831,8 +5906,8 @@ packages: resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} engines: {node: '>= 0.4.0'} - passport@0.5.3: - resolution: {integrity: sha512-gGc+70h4gGdBWNsR3FuV3byLDY6KBTJAIExGFXTpQaYfbbcHCBlRRKx7RBQSpqEqc5Hh2qVzRs7ssvSfOpkUEA==} + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} engines: {node: '>= 0.4.0'} path-exists@3.0.0: @@ -5911,14 +5986,14 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pino-abstract-transport@0.5.0: - resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - pino-std-serializers@4.0.0: - resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - pino@7.11.0: - resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} + pino@10.1.0: + resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==} hasBin: true pirates@4.0.7: @@ -6040,8 +6115,8 @@ packages: pretty-format@3.8.0: resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} - process-warning@1.0.0: - resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} @@ -6177,10 +6252,6 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -6189,8 +6260,8 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - real-require@0.1.0: - resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} realpath-native@1.1.0: @@ -6562,8 +6633,8 @@ packages: resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} engines: {node: '>=0.10.0'} - sonic-boom@2.8.0: - resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} sonner@1.7.4: resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} @@ -6667,9 +6738,6 @@ packages: resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} engines: {node: '>=0.10.0'} - stream-shift@1.0.3: - resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -6721,9 +6789,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -6763,8 +6828,8 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strnum@1.1.2: - resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + strnum@2.1.1: + resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} style-to-js@1.1.16: resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} @@ -6850,8 +6915,8 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thread-stream@0.15.2: - resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} throat@4.1.0: resolution: {integrity: sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA==} @@ -6941,6 +7006,10 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -6977,6 +7046,11 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsx@4.20.6: + resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} + engines: {node: '>=18.0.0'} + hasBin: true + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -7031,6 +7105,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -7150,10 +7229,6 @@ packages: resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==} hasBin: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -7341,20 +7416,20 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.922.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.922.0 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.922.0 '@aws-sdk/util-locate-window': 3.723.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -7364,7 +7439,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.922.0 '@aws-sdk/util-locate-window': 3.723.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -7372,7 +7447,7 @@ snapshots: '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.922.0 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -7381,444 +7456,454 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.922.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.782.0': + '@aws-sdk/client-s3@3.922.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/credential-provider-node': 3.782.0 - '@aws-sdk/middleware-bucket-endpoint': 3.775.0 - '@aws-sdk/middleware-expect-continue': 3.775.0 - '@aws-sdk/middleware-flexible-checksums': 3.775.0 - '@aws-sdk/middleware-host-header': 3.775.0 - '@aws-sdk/middleware-location-constraint': 3.775.0 - '@aws-sdk/middleware-logger': 3.775.0 - '@aws-sdk/middleware-recursion-detection': 3.775.0 - '@aws-sdk/middleware-sdk-s3': 3.775.0 - '@aws-sdk/middleware-ssec': 3.775.0 - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/region-config-resolver': 3.775.0 - '@aws-sdk/signature-v4-multi-region': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@aws-sdk/util-user-agent-browser': 3.775.0 - '@aws-sdk/util-user-agent-node': 3.782.0 - '@aws-sdk/xml-builder': 3.775.0 - '@smithy/config-resolver': 4.1.0 - '@smithy/core': 3.2.0 - '@smithy/eventstream-serde-browser': 4.0.2 - '@smithy/eventstream-serde-config-resolver': 4.1.0 - '@smithy/eventstream-serde-node': 4.0.2 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/hash-blob-browser': 4.0.2 - '@smithy/hash-node': 4.0.2 - '@smithy/hash-stream-node': 4.0.2 - '@smithy/invalid-dependency': 4.0.2 - '@smithy/md5-js': 4.0.2 - '@smithy/middleware-content-length': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-retry': 4.1.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/middleware-stack': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.8 - '@smithy/util-defaults-mode-node': 4.0.8 - '@smithy/util-endpoints': 3.0.2 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 - '@smithy/util-waiter': 4.0.3 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/credential-provider-node': 3.922.0 + '@aws-sdk/middleware-bucket-endpoint': 3.922.0 + '@aws-sdk/middleware-expect-continue': 3.922.0 + '@aws-sdk/middleware-flexible-checksums': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-location-constraint': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-sdk-s3': 3.922.0 + '@aws-sdk/middleware-ssec': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/signature-v4-multi-region': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@aws-sdk/xml-builder': 3.921.0 + '@smithy/config-resolver': 4.4.2 + '@smithy/core': 3.17.2 + '@smithy/eventstream-serde-browser': 4.2.4 + '@smithy/eventstream-serde-config-resolver': 4.3.4 + '@smithy/eventstream-serde-node': 4.2.4 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-blob-browser': 4.2.5 + '@smithy/hash-node': 4.2.4 + '@smithy/hash-stream-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/md5-js': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.8 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 + '@smithy/util-stream': 4.5.5 + '@smithy/util-utf8': 4.2.0 + '@smithy/util-waiter': 4.2.4 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.782.0': + '@aws-sdk/client-sso@3.922.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/middleware-host-header': 3.775.0 - '@aws-sdk/middleware-logger': 3.775.0 - '@aws-sdk/middleware-recursion-detection': 3.775.0 - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/region-config-resolver': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@aws-sdk/util-user-agent-browser': 3.775.0 - '@aws-sdk/util-user-agent-node': 3.782.0 - '@smithy/config-resolver': 4.1.0 - '@smithy/core': 3.2.0 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/hash-node': 4.0.2 - '@smithy/invalid-dependency': 4.0.2 - '@smithy/middleware-content-length': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-retry': 4.1.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/middleware-stack': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.8 - '@smithy/util-defaults-mode-node': 4.0.8 - '@smithy/util-endpoints': 3.0.2 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@smithy/config-resolver': 4.4.2 + '@smithy/core': 3.17.2 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.8 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/cloudfront-signer@3.775.0': + '@aws-sdk/cloudfront-signer@3.921.0': dependencies: - '@smithy/url-parser': 4.0.2 + '@smithy/core': 3.17.2 + '@smithy/url-parser': 4.2.4 tslib: 2.8.1 - '@aws-sdk/core@3.775.0': - dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/core': 3.2.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/signature-v4': 5.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-middleware': 4.0.2 - fast-xml-parser: 4.4.1 + '@aws-sdk/core@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@aws-sdk/xml-builder': 3.921.0 + '@smithy/core': 3.17.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/property-provider': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/signature-v4': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.775.0': + '@aws-sdk/credential-provider-env@3.922.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.775.0': - dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/property-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-stream': 4.2.0 + '@aws-sdk/credential-provider-http@3.922.0': + dependencies: + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/node-http-handler': 4.4.4 + '@smithy/property-provider': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/util-stream': 4.5.5 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.782.0': - dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/credential-provider-env': 3.775.0 - '@aws-sdk/credential-provider-http': 3.775.0 - '@aws-sdk/credential-provider-process': 3.775.0 - '@aws-sdk/credential-provider-sso': 3.782.0 - '@aws-sdk/credential-provider-web-identity': 3.782.0 - '@aws-sdk/nested-clients': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/credential-provider-imds': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/credential-provider-ini@3.922.0': + dependencies: + '@aws-sdk/core': 3.922.0 + '@aws-sdk/credential-provider-env': 3.922.0 + '@aws-sdk/credential-provider-http': 3.922.0 + '@aws-sdk/credential-provider-process': 3.922.0 + '@aws-sdk/credential-provider-sso': 3.922.0 + '@aws-sdk/credential-provider-web-identity': 3.922.0 + '@aws-sdk/nested-clients': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/credential-provider-imds': 4.2.4 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.782.0': - dependencies: - '@aws-sdk/credential-provider-env': 3.775.0 - '@aws-sdk/credential-provider-http': 3.775.0 - '@aws-sdk/credential-provider-ini': 3.782.0 - '@aws-sdk/credential-provider-process': 3.775.0 - '@aws-sdk/credential-provider-sso': 3.782.0 - '@aws-sdk/credential-provider-web-identity': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/credential-provider-imds': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/credential-provider-node@3.922.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.922.0 + '@aws-sdk/credential-provider-http': 3.922.0 + '@aws-sdk/credential-provider-ini': 3.922.0 + '@aws-sdk/credential-provider-process': 3.922.0 + '@aws-sdk/credential-provider-sso': 3.922.0 + '@aws-sdk/credential-provider-web-identity': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/credential-provider-imds': 4.2.4 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.775.0': + '@aws-sdk/credential-provider-process@3.922.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.782.0': + '@aws-sdk/credential-provider-sso@3.922.0': dependencies: - '@aws-sdk/client-sso': 3.782.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/token-providers': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/client-sso': 3.922.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/token-providers': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.782.0': + '@aws-sdk/credential-provider-web-identity@3.922.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/nested-clients': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/nested-clients': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/middleware-bucket-endpoint@3.775.0': + '@aws-sdk/middleware-bucket-endpoint@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-arn-parser': 3.723.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-config-provider': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-expect-continue@3.775.0': + '@aws-sdk/middleware-expect-continue@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.775.0': + '@aws-sdk/middleware-flexible-checksums@3.922.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/is-array-buffer': 4.0.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-stream': 4.5.5 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.775.0': + '@aws-sdk/middleware-host-header@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-location-constraint@3.775.0': + '@aws-sdk/middleware-location-constraint@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.775.0': + '@aws-sdk/middleware-logger@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.775.0': + '@aws-sdk/middleware-recursion-detection@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@aws/lambda-invoke-store': 0.1.1 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.775.0': - dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-arn-parser': 3.723.0 - '@smithy/core': 3.2.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/signature-v4': 5.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/middleware-sdk-s3@3.922.0': + dependencies: + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.17.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/signature-v4': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-stream': 4.5.5 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.775.0': + '@aws-sdk/middleware-ssec@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.782.0': + '@aws-sdk/middleware-user-agent@3.922.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@smithy/core': 3.2.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@smithy/core': 3.17.2 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.782.0': + '@aws-sdk/nested-clients@3.922.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/middleware-host-header': 3.775.0 - '@aws-sdk/middleware-logger': 3.775.0 - '@aws-sdk/middleware-recursion-detection': 3.775.0 - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/region-config-resolver': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@aws-sdk/util-user-agent-browser': 3.775.0 - '@aws-sdk/util-user-agent-node': 3.782.0 - '@smithy/config-resolver': 4.1.0 - '@smithy/core': 3.2.0 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/hash-node': 4.0.2 - '@smithy/invalid-dependency': 4.0.2 - '@smithy/middleware-content-length': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-retry': 4.1.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/middleware-stack': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.8 - '@smithy/util-defaults-mode-node': 4.0.8 - '@smithy/util-endpoints': 3.0.2 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@smithy/config-resolver': 4.4.2 + '@smithy/core': 3.17.2 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.8 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/region-config-resolver@3.775.0': + '@aws-sdk/region-config-resolver@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.0.2 + '@aws-sdk/types': 3.922.0 + '@smithy/config-resolver': 4.4.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/s3-request-presigner@3.782.0': + '@aws-sdk/s3-request-presigner@3.922.0': dependencies: - '@aws-sdk/signature-v4-multi-region': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-format-url': 3.775.0 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 + '@aws-sdk/signature-v4-multi-region': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-format-url': 3.922.0 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.775.0': + '@aws-sdk/signature-v4-multi-region@3.922.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/signature-v4': 5.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/middleware-sdk-s3': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/signature-v4': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.782.0': + '@aws-sdk/token-providers@3.922.0': dependencies: - '@aws-sdk/nested-clients': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/nested-clients': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/types@3.775.0': + '@aws-sdk/types@3.922.0': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/util-arn-parser@3.723.0': + '@aws-sdk/util-arn-parser@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.782.0': + '@aws-sdk/util-endpoints@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 - '@smithy/util-endpoints': 3.0.2 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-endpoints': 3.2.4 tslib: 2.8.1 - '@aws-sdk/util-format-url@3.775.0': + '@aws-sdk/util-format-url@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/querystring-builder': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/querystring-builder': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@aws-sdk/util-locate-window@3.723.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.775.0': + '@aws-sdk/util-user-agent-browser@3.922.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 bowser: 2.11.0 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.782.0': + '@aws-sdk/util-user-agent-node@3.922.0': dependencies: - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.775.0': + '@aws-sdk/xml-builder@3.921.0': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 + fast-xml-parser: 5.2.5 tslib: 2.8.1 + '@aws/lambda-invoke-store@0.1.1': {} + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -8609,6 +8694,10 @@ snapshots: dependencies: sparse-bitfield: 3.0.3 + '@mongodb-js/saslprep@1.3.2': + dependencies: + sparse-bitfield: 3.0.3 + '@napi-rs/wasm-runtime@0.2.8': dependencies: '@emnapi/core': 1.4.0 @@ -8668,6 +8757,8 @@ snapshots: '@panva/hkdf@1.2.1': {} + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -9642,250 +9733,254 @@ snapshots: '@sideway/pinpoint@2.0.0': {} - '@smithy/abort-controller@4.0.2': + '@smithy/abort-controller@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/chunked-blob-reader-native@4.0.0': + '@smithy/chunked-blob-reader-native@4.2.1': dependencies: - '@smithy/util-base64': 4.0.0 + '@smithy/util-base64': 4.3.0 tslib: 2.8.1 - '@smithy/chunked-blob-reader@5.0.0': + '@smithy/chunked-blob-reader@5.2.0': dependencies: tslib: 2.8.1 - '@smithy/config-resolver@4.1.0': + '@smithy/config-resolver@4.4.2': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.0.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 tslib: 2.8.1 - '@smithy/core@3.2.0': - dependencies: - '@smithy/middleware-serde': 4.0.3 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/core@3.17.2': + dependencies: + '@smithy/middleware-serde': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-stream': 4.5.5 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.0.2': + '@smithy/credential-provider-imds@4.2.4': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/property-provider': 4.2.4 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 tslib: 2.8.1 - '@smithy/eventstream-codec@4.0.2': + '@smithy/eventstream-codec@4.2.4': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-hex-encoding': 4.0.0 + '@smithy/types': 4.8.1 + '@smithy/util-hex-encoding': 4.2.0 tslib: 2.8.1 - '@smithy/eventstream-serde-browser@4.0.2': + '@smithy/eventstream-serde-browser@4.2.4': dependencies: - '@smithy/eventstream-serde-universal': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/eventstream-serde-universal': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/eventstream-serde-config-resolver@4.1.0': + '@smithy/eventstream-serde-config-resolver@4.3.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/eventstream-serde-node@4.0.2': + '@smithy/eventstream-serde-node@4.2.4': dependencies: - '@smithy/eventstream-serde-universal': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/eventstream-serde-universal': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/eventstream-serde-universal@4.0.2': + '@smithy/eventstream-serde-universal@4.2.4': dependencies: - '@smithy/eventstream-codec': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/eventstream-codec': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.0.2': + '@smithy/fetch-http-handler@5.3.5': dependencies: - '@smithy/protocol-http': 5.1.0 - '@smithy/querystring-builder': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/util-base64': 4.0.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/querystring-builder': 4.2.4 + '@smithy/types': 4.8.1 + '@smithy/util-base64': 4.3.0 tslib: 2.8.1 - '@smithy/hash-blob-browser@4.0.2': + '@smithy/hash-blob-browser@4.2.5': dependencies: - '@smithy/chunked-blob-reader': 5.0.0 - '@smithy/chunked-blob-reader-native': 4.0.0 - '@smithy/types': 4.2.0 + '@smithy/chunked-blob-reader': 5.2.0 + '@smithy/chunked-blob-reader-native': 4.2.1 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/hash-node@4.0.2': + '@smithy/hash-node@4.2.4': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-buffer-from': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/types': 4.8.1 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/hash-stream-node@4.0.2': + '@smithy/hash-stream-node@4.2.4': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/types': 4.8.1 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/invalid-dependency@4.0.2': + '@smithy/invalid-dependency@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 - '@smithy/is-array-buffer@4.0.0': + '@smithy/is-array-buffer@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/md5-js@4.0.2': + '@smithy/md5-js@4.2.4': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/types': 4.8.1 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/middleware-content-length@4.0.2': + '@smithy/middleware-content-length@4.2.4': dependencies: - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.1.0': + '@smithy/middleware-endpoint@4.3.6': dependencies: - '@smithy/core': 3.2.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/node-config-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-middleware': 4.0.2 + '@smithy/core': 3.17.2 + '@smithy/middleware-serde': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-middleware': 4.2.4 tslib: 2.8.1 - '@smithy/middleware-retry@4.1.0': + '@smithy/middleware-retry@4.4.6': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/service-error-classification': 4.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/service-error-classification': 4.2.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 - uuid: 9.0.1 - '@smithy/middleware-serde@4.0.3': + '@smithy/middleware-serde@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/middleware-stack@4.0.2': + '@smithy/middleware-stack@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/node-config-provider@4.0.2': + '@smithy/node-config-provider@4.3.4': dependencies: - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.0.4': + '@smithy/node-http-handler@4.4.4': dependencies: - '@smithy/abort-controller': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/querystring-builder': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/abort-controller': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/querystring-builder': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/property-provider@4.0.2': + '@smithy/property-provider@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/protocol-http@5.1.0': + '@smithy/protocol-http@5.3.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/querystring-builder@4.0.2': + '@smithy/querystring-builder@4.2.4': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-uri-escape': 4.0.0 + '@smithy/types': 4.8.1 + '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 - '@smithy/querystring-parser@4.0.2': + '@smithy/querystring-parser@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/service-error-classification@4.0.2': + '@smithy/service-error-classification@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 - '@smithy/shared-ini-file-loader@4.0.2': + '@smithy/shared-ini-file-loader@4.3.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/signature-v4@5.0.2': + '@smithy/signature-v4@5.3.4': dependencies: - '@smithy/is-array-buffer': 4.0.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-hex-encoding': 4.0.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-uri-escape': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/smithy-client@4.2.0': + '@smithy/smithy-client@4.9.2': dependencies: - '@smithy/core': 3.2.0 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-stack': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-stream': 4.2.0 + '@smithy/core': 3.17.2 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-stack': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-stream': 4.5.5 tslib: 2.8.1 - '@smithy/types@4.2.0': + '@smithy/types@4.8.1': dependencies: tslib: 2.8.1 - '@smithy/url-parser@4.0.2': + '@smithy/url-parser@4.2.4': dependencies: - '@smithy/querystring-parser': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/querystring-parser': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-base64@4.0.0': + '@smithy/util-base64@4.3.0': dependencies: - '@smithy/util-buffer-from': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-body-length-browser@4.0.0': + '@smithy/util-body-length-browser@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-body-length-node@4.0.0': + '@smithy/util-body-length-node@4.2.1': dependencies: tslib: 2.8.1 @@ -9894,66 +9989,65 @@ snapshots: '@smithy/is-array-buffer': 2.2.0 tslib: 2.8.1 - '@smithy/util-buffer-from@4.0.0': + '@smithy/util-buffer-from@4.2.0': dependencies: - '@smithy/is-array-buffer': 4.0.0 + '@smithy/is-array-buffer': 4.2.0 tslib: 2.8.1 - '@smithy/util-config-provider@4.0.0': + '@smithy/util-config-provider@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.0.8': + '@smithy/util-defaults-mode-browser@4.3.5': dependencies: - '@smithy/property-provider': 4.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - bowser: 2.11.0 + '@smithy/property-provider': 4.2.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.0.8': + '@smithy/util-defaults-mode-node@4.2.8': dependencies: - '@smithy/config-resolver': 4.1.0 - '@smithy/credential-provider-imds': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 + '@smithy/config-resolver': 4.4.2 + '@smithy/credential-provider-imds': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/property-provider': 4.2.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-endpoints@3.0.2': + '@smithy/util-endpoints@3.2.4': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-hex-encoding@4.0.0': + '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@4.0.2': + '@smithy/util-middleware@4.2.4': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-retry@4.0.2': + '@smithy/util-retry@4.2.4': dependencies: - '@smithy/service-error-classification': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/service-error-classification': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-stream@4.2.0': + '@smithy/util-stream@4.5.5': dependencies: - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/types': 4.2.0 - '@smithy/util-base64': 4.0.0 - '@smithy/util-buffer-from': 4.0.0 - '@smithy/util-hex-encoding': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/node-http-handler': 4.4.4 + '@smithy/types': 4.8.1 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-uri-escape@4.0.0': + '@smithy/util-uri-escape@4.2.0': dependencies: tslib: 2.8.1 @@ -9962,15 +10056,19 @@ snapshots: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@smithy/util-utf8@4.0.0': + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.4': dependencies: - '@smithy/util-buffer-from': 4.0.0 + '@smithy/abort-controller': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-waiter@4.0.3': + '@smithy/uuid@1.1.0': dependencies: - '@smithy/abort-controller': 4.0.2 - '@smithy/types': 4.2.0 tslib: 2.8.1 '@standard-schema/spec@1.0.0': {} @@ -10186,7 +10284,7 @@ snapshots: '@types/mongoose@5.11.97': dependencies: - mongoose: 8.13.2 + mongoose: 8.19.3 transitivePeerDependencies: - '@aws-sdk/credential-providers' - '@mongodb-js/zstd' @@ -10298,6 +10396,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.0(supports-color@5.5.0) + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare-lite: 1.4.0 + semver: 7.7.1 + tsutils: 3.21.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.29.1(@typescript-eslint/parser@5.62.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -10344,6 +10461,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) + debug: 4.4.0(supports-color@5.5.0) + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@5.62.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 @@ -10390,6 +10519,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.0(supports-color@5.5.0) + eslint: 8.57.1 + tsutils: 3.21.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/type-utils@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.8.3) @@ -10419,6 +10560,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.0(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.1 + tsutils: 3.21.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@8.29.1(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.29.1 @@ -10448,6 +10603,21 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.0 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) + eslint: 8.57.1 + eslint-scope: 5.1.1 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/utils@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0(jiti@2.4.2)) @@ -10887,6 +11057,8 @@ snapshots: bson@6.10.3: {} + bson@6.10.4: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -11257,7 +11429,9 @@ snapshots: dependencies: webidl-conversions: 4.0.2 - dotenv@16.4.7: {} + dotenv@16.6.1: {} + + dotenv@17.2.3: {} dunder-proto@1.0.1: dependencies: @@ -11265,13 +11439,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexify@4.1.3: - dependencies: - end-of-stream: 1.4.4 - inherits: 2.0.4 - readable-stream: 3.6.2 - stream-shift: 1.0.3 - eastasianwidth@0.2.0: {} ecc-jsbn@0.1.2: @@ -11956,7 +12123,7 @@ snapshots: transitivePeerDependencies: - supports-color - express-fileupload@1.5.1: + express-fileupload@1.5.2: dependencies: busboy: 1.6.0 @@ -12052,11 +12219,9 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-redact@3.5.0: {} - - fast-xml-parser@4.4.1: + fast-xml-parser@5.2.5: dependencies: - strnum: 1.1.2 + strnum: 2.1.1 fastq@1.19.1: dependencies: @@ -14053,6 +14218,12 @@ snapshots: bson: 6.10.3 mongodb-connection-string-url: 3.0.2 + mongodb@6.20.0: + dependencies: + '@mongodb-js/saslprep': 1.3.2 + bson: 6.10.4 + mongodb-connection-string-url: 3.0.2 + mongoose@8.13.2: dependencies: bson: 6.10.3 @@ -14072,6 +14243,25 @@ snapshots: - socks - supports-color + mongoose@8.19.3: + dependencies: + bson: 6.10.4 + kareem: 2.6.3 + mongodb: 6.20.0 + mpath: 0.9.0 + mquery: 5.0.0 + ms: 2.1.3 + sift: 17.1.3 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + mpath@0.9.0: {} mquery@5.0.0: @@ -14179,7 +14369,7 @@ snapshots: nodemailer@6.10.0: {} - nodemon@3.1.9: + nodemon@3.1.10: dependencies: chokidar: 3.6.0 debug: 4.4.0(supports-color@5.5.0) @@ -14289,7 +14479,7 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - on-exit-leak-free@0.2.0: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: dependencies: @@ -14418,10 +14608,11 @@ snapshots: passport-strategy@1.0.0: {} - passport@0.5.3: + passport@0.7.0: dependencies: passport-strategy: 1.0.0 pause: 0.0.1 + utils-merge: 1.0.1 path-exists@3.0.0: {} @@ -14468,26 +14659,25 @@ snapshots: pify@4.0.1: {} - pino-abstract-transport@0.5.0: + pino-abstract-transport@2.0.0: dependencies: - duplexify: 4.1.3 split2: 4.2.0 - pino-std-serializers@4.0.0: {} + pino-std-serializers@7.0.0: {} - pino@7.11.0: + pino@10.1.0: dependencies: + '@pinojs/redact': 0.4.0 atomic-sleep: 1.0.0 - fast-redact: 3.5.0 - on-exit-leak-free: 0.2.0 - pino-abstract-transport: 0.5.0 - pino-std-serializers: 4.0.0 - process-warning: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 quick-format-unescaped: 4.0.4 - real-require: 0.1.0 + real-require: 0.2.0 safe-stable-stringify: 2.5.0 - sonic-boom: 2.8.0 - thread-stream: 0.15.2 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 pirates@4.0.7: {} @@ -14583,7 +14773,7 @@ snapshots: pretty-format@3.8.0: {} - process-warning@1.0.0: {} + process-warning@5.0.0: {} prompts@2.4.2: dependencies: @@ -14743,19 +14933,13 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 readdirp@4.1.2: {} - real-require@0.1.0: {} + real-require@0.2.0: {} realpath-native@1.1.0: dependencies: @@ -15285,7 +15469,7 @@ snapshots: transitivePeerDependencies: - supports-color - sonic-boom@2.8.0: + sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 @@ -15386,8 +15570,6 @@ snapshots: stealthy-require@1.1.1: {} - stream-shift@1.0.3: {} - streamsearch@1.1.0: {} string-argv@0.3.2: {} @@ -15471,10 +15653,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -15506,7 +15684,7 @@ snapshots: strip-json-comments@3.1.1: {} - strnum@1.1.2: {} + strnum@2.1.1: {} style-to-js@1.1.16: dependencies: @@ -15605,9 +15783,9 @@ snapshots: dependencies: any-promise: 1.3.0 - thread-stream@0.15.2: + thread-stream@3.1.0: dependencies: - real-require: 0.1.0 + real-require: 0.2.0 throat@4.1.0: {} @@ -15692,7 +15870,25 @@ snapshots: yn: 3.1.1 optional: true - ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3): + ts-node@10.9.2(@types/node@20.10.6)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.10.6 + acorn: 8.14.1 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + ts-node@10.9.2(@types/node@22.14.1)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -15706,7 +15902,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.8.3 + typescript: 5.9.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -15717,6 +15913,12 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@1.14.1: {} tslib@2.8.1: {} @@ -15753,6 +15955,11 @@ snapshots: tslib: 1.14.1 typescript: 5.8.3 + tsutils@3.21.0(typescript@5.9.3): + dependencies: + tslib: 1.14.1 + typescript: 5.9.3 + tsx@4.19.3: dependencies: esbuild: 0.25.2 @@ -15760,6 +15967,13 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsx@4.20.6: + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -15836,6 +16050,8 @@ snapshots: typescript@5.8.3: {} + typescript@5.9.3: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -16005,8 +16221,6 @@ snapshots: uuid@8.0.0: {} - uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: {} validate-npm-package-license@3.0.4: diff --git a/release.sh b/release.sh new file mode 100755 index 00000000..37ff5b8e --- /dev/null +++ b/release.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Check if an argument is provided +if [ $# -eq 0 ]; then + echo "USAGE: ./release.sh " + exit 1 +fi + +# check if jq is installed +if ! command -v jq &> /dev/null; then + echo "jq could not be found. Please install it." + exit 1 +fi + +VERSION=$1 + +# Update version in apps/web/package.json +jq --arg version "$VERSION" '.version = $version' apps/api/package.json > tmp.json && mv tmp.json apps/api/package.json + +# commit, tag and push +git add . +git commit -m v$VERSION +git push +git tag -a "v${VERSION}" -m "v${VERSION}" +git push origin --tags \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d14cdae1..1a29b1ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ // "incremental": true, /* Enable incremental compilation */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ + "lib": ["ES2020"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */