From f55dec9a6026eea77137828c54440af678b7d53c Mon Sep 17 00:00:00 2001 From: Christomatt Date: Sun, 1 Feb 2026 02:51:52 +0100 Subject: [PATCH] feat: user-owned S3-compatible storage with credential verification --- Dockerfile | 57 ++ drizzle/0011_add_user_storage.sql | 3 + package-lock.json | 595 +----------------- package.json | 3 +- scripts/migrate-dids.ts | 102 +++ src/app/api/account/export/route.ts | 9 +- src/app/api/account/import/route.ts | 71 ++- src/app/api/auth/register/route.ts | 36 +- src/app/api/bots/route.ts | 27 +- src/app/api/chat/receive/route.ts | 161 ++++- src/app/api/chat/send/route.ts | 36 +- src/app/api/handles/resolve/route.ts | 23 +- src/app/api/media/upload/route.ts | 78 +-- src/app/api/posts/[id]/like/route.ts | 41 +- src/app/api/posts/[id]/repost/route.ts | 29 +- src/app/api/posts/[id]/route.ts | 28 +- src/app/api/posts/route.ts | 33 +- .../swarm/chat/conversations/[id]/route.ts | 29 +- src/app/api/swarm/chat/messages/route.ts | 47 +- .../api/swarm/interactions/follow/route.ts | 30 +- src/app/api/swarm/interactions/like/route.ts | 22 +- .../api/swarm/interactions/repost/route.ts | 20 +- .../api/swarm/interactions/unfollow/route.ts | 4 +- .../api/swarm/interactions/unrepost/route.ts | 4 +- src/app/api/swarm/posts/[id]/likes/route.ts | 37 +- src/app/api/swarm/posts/[id]/route.ts | 18 +- src/app/api/users/[handle]/follow/route.ts | 22 +- src/app/chat/page.tsx | 27 +- src/app/login/page.tsx | 345 +++++++--- src/db/schema.ts | 7 + src/lib/auth/index.ts | 71 ++- src/lib/auth/verify-signature.ts | 17 +- src/lib/bots/botManager.ts | 27 +- src/lib/crypto/key-persistence.ts | 6 +- src/lib/rate-limit.ts | 138 ++++ src/lib/storage/ipfs.ts | 134 ++++ src/lib/storage/s3.ts | 214 +++++++ src/lib/swarm/identity-cache.ts | 196 ++++++ 38 files changed, 1845 insertions(+), 902 deletions(-) create mode 100644 Dockerfile create mode 100644 drizzle/0011_add_user_storage.sql create mode 100644 scripts/migrate-dids.ts create mode 100644 src/lib/rate-limit.ts create mode 100644 src/lib/storage/ipfs.ts create mode 100644 src/lib/storage/s3.ts create mode 100644 src/lib/swarm/identity-cache.ts diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ded0ad8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +# Synapsis Docker Image +# Multi-stage build for production + +# Stage 1: Dependencies +FROM node:20-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json* ./ +RUN npm ci --only=production + +# Stage 2: Builder +FROM node:20-alpine AS builder +WORKDIR /app + +# Copy deps from previous stage +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build the application +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production +RUN npm run build + +# Stage 3: Runner +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Create non-root user +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy necessary files +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Switch to non-root user +USER nextjs + +# Expose port +EXPOSE 3000 + +# Set environment variables (can be overridden at runtime) +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/api/node || exit 1 + +# Start the application +CMD ["node", "server.js"] diff --git a/drizzle/0011_add_user_storage.sql b/drizzle/0011_add_user_storage.sql new file mode 100644 index 0000000..262ca57 --- /dev/null +++ b/drizzle/0011_add_user_storage.sql @@ -0,0 +1,3 @@ +-- Migration: Add user storage columns +ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "storage_provider" text; +ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "storage_api_key_encrypted" text; diff --git a/package-lock.json b/package-lock.json index c90f2a7..89ab714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,22 +10,6 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-s3": "^3.972.0", - "@upstash/redis": "^1.34.3", - "bcryptjs": "^2.4.3", - "crypto-js": "^4.2.0", - "drizzle-orm": "^0.44.1", - "jose": "^6.0.11", - "libsodium-wrappers-sumo": "^0.8.2", - "lucide-react": "^0.562.0", - "next": "16.1.4", - "next-auth": "^5.0.0-beta.25", - "pg": "^8.17.2", - "react": "19.2.3", - "react-dom": "19.2.3", - "uuid": "^11.1.0", - "zod": "^4.3.5" - }, - "devDependencies": { "@tailwindcss/postcss": "^4", "@types/bcryptjs": "^2.4.6", "@types/node": "^20.19.30", @@ -33,23 +17,37 @@ "@types/react": "^19", "@types/react-dom": "^19", "@types/uuid": "^10.0.0", + "@upstash/redis": "^1.34.3", + "bcryptjs": "^2.4.3", + "crypto-js": "^4.2.0", "dotenv": "^17.2.3", "dotenv-cli": "^11.0.0", "drizzle-kit": "^0.31.8", + "drizzle-orm": "^0.44.1", "eslint": "^9", "eslint-config-next": "16.1.4", "fast-check": "^4.5.3", + "jose": "^6.0.11", + "libsodium-wrappers-sumo": "^0.8.2", + "lucide-react": "^0.562.0", + "multiformats": "^13.4.2", + "next": "16.1.4", + "next-auth": "^5.0.0-beta.25", + "pg": "^8.17.2", + "react": "19.2.3", + "react-dom": "19.2.3", "tailwindcss": "^4", "tsx": "^4.21.0", "typescript": "^5", - "vitest": "^4.0.18" + "uuid": "^11.1.0", + "vitest": "^4.0.18", + "zod": "^4.3.5" } }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -989,7 +987,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -1004,7 +1001,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1014,7 +1010,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -1045,7 +1040,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.6", @@ -1062,7 +1056,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", @@ -1079,7 +1072,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1089,7 +1081,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", @@ -1103,7 +1094,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", @@ -1121,7 +1111,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1131,7 +1120,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1141,7 +1129,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1151,7 +1138,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", @@ -1165,7 +1151,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.6" @@ -1181,7 +1166,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -1196,7 +1180,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -1215,7 +1198,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1229,14 +1211,12 @@ "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", - "dev": true, "license": "Apache-2.0" }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1258,7 +1238,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1270,7 +1249,6 @@ "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.18.20", @@ -1284,7 +1262,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1301,7 +1278,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1318,7 +1294,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1335,7 +1310,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1352,7 +1326,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1369,7 +1342,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1386,7 +1358,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1403,7 +1374,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1420,7 +1390,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1437,7 +1406,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1454,7 +1422,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1471,7 +1438,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1488,7 +1454,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1505,7 +1470,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1522,7 +1486,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1539,7 +1502,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1556,7 +1518,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1573,7 +1534,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1590,7 +1550,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1607,7 +1566,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1624,7 +1582,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1641,7 +1598,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1655,7 +1611,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1694,7 +1649,6 @@ "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, "license": "MIT", "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", @@ -1708,7 +1662,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1725,7 +1678,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1742,7 +1694,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1759,7 +1710,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1776,7 +1726,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1793,7 +1742,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1810,7 +1758,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1827,7 +1774,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1844,7 +1790,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1861,7 +1806,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1878,7 +1822,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1895,7 +1838,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1912,7 +1854,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1929,7 +1870,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1946,7 +1886,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1963,7 +1902,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1980,7 +1918,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1997,7 +1934,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2014,7 +1950,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2031,7 +1966,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2048,7 +1982,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2065,7 +1998,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2082,7 +2014,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2099,7 +2030,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2116,7 +2046,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2133,7 +2062,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2147,7 +2075,6 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2166,7 +2093,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2179,7 +2105,6 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2189,7 +2114,6 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", @@ -2204,7 +2128,6 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" @@ -2217,7 +2140,6 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2230,7 +2152,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2254,7 +2175,6 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2267,7 +2187,6 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2277,7 +2196,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", @@ -2291,7 +2209,6 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2301,7 +2218,6 @@ "version": "0.16.7", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2315,7 +2231,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2329,7 +2244,6 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -2809,7 +2723,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -2820,7 +2733,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2831,7 +2743,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2841,14 +2752,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2859,7 +2768,6 @@ "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2878,7 +2786,6 @@ "version": "16.1.4", "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.4.tgz", "integrity": "sha512-38WMjGP8y+1MN4bcZFs+GTcBe0iem5GGTzFE5GWW/dWdRKde7LOXH3lQT2QuoquVWyfl2S0fQRchGmeacGZ4Wg==", - "dev": true, "license": "MIT", "dependencies": { "fast-glob": "3.3.1" @@ -3016,7 +2923,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -3030,7 +2936,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3040,7 +2945,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -3054,7 +2958,6 @@ "version": "1.0.39", "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.4.0" @@ -3076,7 +2979,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3090,7 +2992,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3104,7 +3005,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3118,7 +3018,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3132,7 +3031,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3146,7 +3044,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3160,7 +3057,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3174,7 +3070,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3188,7 +3083,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3202,7 +3096,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3216,7 +3109,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3230,7 +3122,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3244,7 +3135,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3258,7 +3148,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3272,7 +3161,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3286,7 +3174,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3300,7 +3187,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3314,7 +3200,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3328,7 +3213,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3342,7 +3226,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3356,7 +3239,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3370,7 +3252,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3384,7 +3265,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3398,7 +3278,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3412,7 +3291,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3423,7 +3301,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, "license": "MIT" }, "node_modules/@smithy/abort-controller": { @@ -4162,7 +4039,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, "license": "MIT" }, "node_modules/@swc/helpers": { @@ -4178,7 +4054,6 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -4194,7 +4069,6 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -4221,7 +4095,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4238,7 +4111,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4255,7 +4127,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4272,7 +4143,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4289,7 +4159,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4306,7 +4175,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4323,7 +4191,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4340,7 +4207,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4357,7 +4223,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4382,7 +4247,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4404,7 +4268,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4421,7 +4284,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4435,7 +4297,6 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -4449,7 +4310,6 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4460,14 +4320,12 @@ "version": "2.4.6", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, "license": "MIT", "dependencies": { "@types/deep-eql": "*", @@ -4478,35 +4336,30 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "20.19.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4516,7 +4369,6 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", - "devOptional": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4528,7 +4380,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "devOptional": true, "license": "MIT", "dependencies": { "pg-int8": "1.0.1", @@ -4545,7 +4396,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -4555,7 +4405,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4565,7 +4414,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4575,7 +4423,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "devOptional": true, "license": "MIT", "dependencies": { "xtend": "^4.0.0" @@ -4588,7 +4435,6 @@ "version": "19.2.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -4598,7 +4444,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -4608,14 +4453,12 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", @@ -4644,7 +4487,6 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4654,7 +4496,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", @@ -4679,7 +4520,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.53.1", @@ -4701,7 +4541,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.53.1", @@ -4719,7 +4558,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", - "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4736,7 +4574,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.53.1", @@ -4761,7 +4598,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", - "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4775,7 +4611,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.53.1", @@ -4803,7 +4638,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4813,7 +4647,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4829,7 +4662,6 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4842,7 +4674,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -4866,7 +4697,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.53.1", @@ -4887,7 +4717,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4901,7 +4730,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4915,7 +4743,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4929,7 +4756,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4943,7 +4769,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4957,7 +4782,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4971,7 +4795,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4985,7 +4808,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4999,7 +4821,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5013,7 +4834,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5027,7 +4847,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5041,7 +4860,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5055,7 +4873,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5069,7 +4886,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5083,7 +4899,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5097,7 +4912,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5114,7 +4928,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5128,7 +4941,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5142,7 +4954,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5162,7 +4973,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", - "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -5180,7 +4990,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", - "dev": true, "license": "MIT", "dependencies": { "@vitest/spy": "4.0.18", @@ -5207,7 +5016,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", - "dev": true, "license": "MIT", "dependencies": { "tinyrainbow": "^3.0.3" @@ -5220,7 +5028,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", - "dev": true, "license": "MIT", "dependencies": { "@vitest/utils": "4.0.18", @@ -5234,7 +5041,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", - "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "4.0.18", @@ -5249,7 +5055,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", - "dev": true, "license": "MIT", "funding": { "url": "https://opencollective.com/vitest" @@ -5259,7 +5064,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", - "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "4.0.18", @@ -5273,7 +5077,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5286,7 +5089,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5296,7 +5098,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5313,7 +5114,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5329,14 +5129,12 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -5346,7 +5144,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5363,7 +5160,6 @@ "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5386,7 +5182,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -5407,7 +5202,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5429,7 +5223,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5448,7 +5241,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5467,7 +5259,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -5484,7 +5275,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -5506,7 +5296,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5516,14 +5305,12 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, "license": "MIT" }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5533,7 +5320,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -5549,7 +5335,6 @@ "version": "4.11.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", - "dev": true, "license": "MPL-2.0", "engines": { "node": ">=4" @@ -5559,7 +5344,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -5569,7 +5353,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -5597,7 +5380,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5608,7 +5390,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -5621,7 +5402,6 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5655,14 +5435,12 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -5681,7 +5459,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5695,7 +5472,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5712,7 +5488,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5742,7 +5517,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5752,7 +5526,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5775,7 +5548,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5788,28 +5560,24 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5830,21 +5598,18 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5862,7 +5627,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5880,7 +5644,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5898,7 +5661,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5916,14 +5678,12 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, "license": "MIT" }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -5941,7 +5701,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -5959,7 +5718,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -5969,7 +5727,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -5982,7 +5739,6 @@ "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5995,7 +5751,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", - "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", @@ -6011,7 +5766,6 @@ "version": "12.0.3", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" @@ -6027,7 +5781,6 @@ "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -6040,7 +5793,6 @@ "version": "0.31.8", "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.8.tgz", "integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==", - "dev": true, "license": "MIT", "dependencies": { "@drizzle-team/brocli": "^0.10.2", @@ -6181,7 +5933,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -6196,21 +5947,18 @@ "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -6224,7 +5972,6 @@ "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", - "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -6293,7 +6040,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6303,7 +6049,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6313,7 +6058,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -6341,14 +6085,12 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6361,7 +6103,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6377,7 +6118,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -6390,7 +6130,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7", @@ -6408,7 +6147,6 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -6450,7 +6188,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.4" @@ -6463,7 +6200,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6473,7 +6209,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -6486,7 +6221,6 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", @@ -6546,7 +6280,6 @@ "version": "16.1.4", "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.4.tgz", "integrity": "sha512-iCrrNolUPpn/ythx0HcyNRfUBgTkaNBXByisKUbusPGCl8DMkDXXAu7exlSTSLGTIsH9lFE/c4s/3Qiyv2qwdA==", - "dev": true, "license": "MIT", "dependencies": { "@next/eslint-plugin-next": "16.1.4", @@ -6573,7 +6306,6 @@ "version": "16.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6586,7 +6318,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", @@ -6598,7 +6329,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -6608,7 +6338,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", - "dev": true, "license": "ISC", "dependencies": { "@nolyfill/is-core-module": "1.0.39", @@ -6643,7 +6372,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7" @@ -6661,7 +6389,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -6671,7 +6398,6 @@ "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", @@ -6705,7 +6431,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -6715,7 +6440,6 @@ "version": "6.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "dev": true, "license": "MIT", "dependencies": { "aria-query": "^5.3.2", @@ -6745,7 +6469,6 @@ "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, "license": "MIT", "dependencies": { "array-includes": "^3.1.8", @@ -6778,7 +6501,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", @@ -6798,7 +6520,6 @@ "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -6816,7 +6537,6 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6833,7 +6553,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6846,7 +6565,6 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -6864,7 +6582,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6877,7 +6594,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -6890,7 +6606,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -6900,7 +6615,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -6910,7 +6624,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -6920,7 +6633,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" @@ -6930,7 +6642,6 @@ "version": "4.5.3", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.5.3.tgz", "integrity": "sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA==", - "dev": true, "funding": [ { "type": "individual", @@ -6953,14 +6664,12 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -6977,7 +6686,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -6990,14 +6698,12 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, "license": "MIT" }, "node_modules/fast-xml-parser": { @@ -7022,7 +6728,6 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -7032,7 +6737,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7045,7 +6749,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -7058,7 +6761,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7075,7 +6777,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7089,14 +6790,12 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, "license": "ISC" }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -7112,7 +6811,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7127,7 +6825,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7137,7 +6834,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -7158,7 +6854,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7168,7 +6863,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7178,7 +6872,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -7188,7 +6881,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7213,7 +6905,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7227,7 +6918,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -7245,7 +6935,6 @@ "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -7258,7 +6947,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7271,7 +6959,6 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7284,7 +6971,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -7301,7 +6987,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7314,14 +6999,12 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7334,7 +7017,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7344,7 +7026,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -7357,7 +7038,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" @@ -7373,7 +7053,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7386,7 +7065,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -7402,7 +7080,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7415,14 +7092,12 @@ "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, "license": "MIT", "dependencies": { "hermes-estree": "0.25.1" @@ -7432,7 +7107,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7442,7 +7116,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -7459,7 +7132,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -7469,7 +7141,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7484,7 +7155,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -7502,7 +7172,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, "license": "MIT", "dependencies": { "async-function": "^1.0.0", @@ -7522,7 +7191,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" @@ -7538,7 +7206,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -7555,7 +7222,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, "license": "MIT", "dependencies": { "semver": "^7.7.1" @@ -7565,7 +7231,6 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7578,7 +7243,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7591,7 +7255,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -7607,7 +7270,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7625,7 +7287,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7642,7 +7303,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7652,7 +7312,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -7668,7 +7327,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.4", @@ -7688,7 +7346,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -7701,7 +7358,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7714,7 +7370,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7727,7 +7382,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -7737,7 +7391,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -7754,7 +7407,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7773,7 +7425,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7786,7 +7437,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -7802,7 +7452,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -7819,7 +7468,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7837,7 +7485,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -7853,7 +7500,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7866,7 +7512,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -7882,7 +7527,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -7899,21 +7543,18 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -7931,7 +7572,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -7950,14 +7590,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7970,7 +7608,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -7983,28 +7620,24 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8017,7 +7650,6 @@ "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, "license": "MIT", "dependencies": { "array-includes": "^3.1.6", @@ -8033,7 +7665,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -8043,14 +7674,12 @@ "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, "license": "CC0-1.0" }, "node_modules/language-tags": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, "license": "MIT", "dependencies": { "language-subtag-registry": "^0.3.20" @@ -8063,7 +7692,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -8092,7 +7720,6 @@ "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -8125,7 +7752,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8146,7 +7772,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8167,7 +7792,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8188,7 +7812,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8209,7 +7832,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8230,7 +7852,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8251,7 +7872,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8272,7 +7892,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8293,7 +7912,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8314,7 +7932,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8335,7 +7952,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8353,7 +7969,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -8369,14 +7984,12 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -8389,7 +8002,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -8408,7 +8020,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -8418,7 +8029,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8428,7 +8038,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -8438,7 +8047,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -8452,7 +8060,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -8465,7 +8072,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8475,9 +8081,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, + "node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -8500,7 +8111,6 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -8516,7 +8126,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, "license": "MIT" }, "node_modules/next": { @@ -8631,7 +8240,6 @@ "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, "license": "MIT" }, "node_modules/oauth4webapi": { @@ -8647,7 +8255,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8657,7 +8264,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8670,7 +8276,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8680,7 +8285,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8701,7 +8305,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8717,7 +8320,6 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -8736,7 +8338,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -8751,7 +8352,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8770,7 +8370,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" @@ -8781,7 +8380,6 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -8799,7 +8397,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", @@ -8817,7 +8414,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -8833,7 +8429,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -8849,7 +8444,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -8862,7 +8456,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8872,7 +8465,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8882,14 +8474,12 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/pg": { @@ -9030,7 +8620,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -9043,7 +8632,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9053,7 +8641,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -9101,7 +8688,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -9111,7 +8697,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -9123,7 +8708,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9133,7 +8717,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, "funding": [ { "type": "individual", @@ -9150,7 +8733,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -9192,14 +8774,12 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9222,7 +8802,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9243,7 +8822,6 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -9264,7 +8842,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9274,7 +8851,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -9284,7 +8860,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -9295,7 +8870,6 @@ "version": "4.56.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -9340,7 +8914,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -9364,7 +8937,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9384,7 +8956,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9401,7 +8972,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9425,7 +8995,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9435,7 +9004,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -9453,7 +9021,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -9469,7 +9036,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -9542,7 +9108,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -9555,7 +9120,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9565,7 +9129,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9585,7 +9148,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9602,7 +9164,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9621,7 +9182,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9641,14 +9201,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, "license": "ISC" }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -9667,7 +9225,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -9687,28 +9244,24 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "dev": true, "license": "MIT" }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, "license": "MIT" }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9722,7 +9275,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -9737,7 +9289,6 @@ "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9765,7 +9316,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.1.3", @@ -9776,7 +9326,6 @@ "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9798,7 +9347,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9817,7 +9365,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -9835,7 +9382,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9845,7 +9391,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9893,7 +9438,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -9906,7 +9450,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9919,14 +9462,12 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9940,14 +9481,12 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -9957,7 +9496,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -9974,7 +9512,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -9992,7 +9529,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -10005,7 +9541,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -10015,7 +9550,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -10028,7 +9562,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -10041,7 +9574,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", @@ -10054,7 +9586,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.0" @@ -10073,7 +9604,6 @@ "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.27.0", @@ -10096,7 +9626,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10113,7 +9642,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10130,7 +9658,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10147,7 +9674,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10164,7 +9690,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10181,7 +9706,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10198,7 +9722,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10215,7 +9738,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10232,7 +9754,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10249,7 +9770,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10266,7 +9786,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10283,7 +9802,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10300,7 +9818,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10317,7 +9834,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10334,7 +9850,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10351,7 +9866,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10368,7 +9882,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10385,7 +9898,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10402,7 +9914,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10419,7 +9930,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10436,7 +9946,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10453,7 +9962,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10470,7 +9978,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10487,7 +9994,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10504,7 +10010,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10521,7 +10026,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10535,7 +10039,6 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -10577,7 +10080,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -10590,7 +10092,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -10605,7 +10106,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10625,7 +10125,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -10647,7 +10146,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -10668,7 +10166,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10682,7 +10179,6 @@ "version": "8.53.1", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.1", @@ -10706,7 +10202,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -10731,14 +10226,12 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -10773,7 +10266,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10804,7 +10296,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -10827,7 +10318,6 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0", @@ -10905,7 +10395,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10922,7 +10411,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10939,7 +10427,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10956,7 +10443,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10973,7 +10459,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10990,7 +10475,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11007,7 +10491,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11024,7 +10507,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11041,7 +10523,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11058,7 +10539,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11075,7 +10555,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11092,7 +10571,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11109,7 +10587,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11126,7 +10603,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11143,7 +10619,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11160,7 +10635,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11177,7 +10651,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11194,7 +10667,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11211,7 +10683,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11228,7 +10699,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11245,7 +10715,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11262,7 +10731,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11279,7 +10747,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11296,7 +10763,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11313,7 +10779,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11330,7 +10795,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11344,7 +10808,6 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -11386,7 +10849,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -11404,7 +10866,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11417,7 +10878,6 @@ "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", - "dev": true, "license": "MIT", "dependencies": { "@vitest/expect": "4.0.18", @@ -11495,7 +10955,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11508,7 +10967,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -11524,7 +10982,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", @@ -11544,7 +11001,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11572,7 +11028,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, "license": "MIT", "dependencies": { "is-map": "^2.0.3", @@ -11591,7 +11046,6 @@ "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -11613,7 +11067,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, "license": "MIT", "dependencies": { "siginfo": "^2.0.0", @@ -11630,7 +11083,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11649,14 +11101,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -11678,7 +11128,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/package.json b/package.json index b9b7100..552e75c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "jose": "^6.0.11", "libsodium-wrappers-sumo": "^0.8.2", "lucide-react": "^0.562.0", + "multiformats": "^13.4.2", "next": "16.1.4", "next-auth": "^5.0.0-beta.25", "pg": "^8.17.2", @@ -53,4 +54,4 @@ "vitest": "^4.0.18", "zod": "^4.3.5" } -} \ No newline at end of file +} diff --git a/scripts/migrate-dids.ts b/scripts/migrate-dids.ts new file mode 100644 index 0000000..032c0bc --- /dev/null +++ b/scripts/migrate-dids.ts @@ -0,0 +1,102 @@ +/** + * DID Migration Script + * + * Converts users from legacy did:synapsis: format to new did:key: format + * The new DID is derived from the user's public key + */ + +import { db, users } from '../src/db'; +import { eq } from 'drizzle-orm'; + +// Simple base58btc encoder (Bitcoin alphabet) +const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + +function base58Encode(buffer: Uint8Array): string { + const alphabet = BASE58_ALPHABET; + let carry: number; + let digits: number[] = [0]; + + for (let i = 0; i < buffer.length; i++) { + carry = buffer[i]; + for (let j = 0; j < digits.length; j++) { + carry += digits[j] << 8; + digits[j] = carry % 58; + carry = (carry / 58) | 0; + } + while (carry) { + digits.push(carry % 58); + carry = (carry / 58) | 0; + } + } + + // Leading zeros + for (let i = 0; i < buffer.length && buffer[i] === 0; i++) { + digits.push(0); + } + + return digits.reverse().map(d => alphabet[d]).join(''); +} + +async function migrateDIDs() { + console.log('Starting DID migration...\n'); + + if (!db) { + console.error('Database not available'); + process.exit(1); + } + + // Find all users with legacy did:synapsis: format + const legacyUsers = await db.query.users.findMany({ + where: (users, { like }) => like(users.did, 'did:synapsis:%'), + }); + + console.log(`Found ${legacyUsers.length} users with legacy DID format\n`); + + let migrated = 0; + let failed = 0; + + for (const user of legacyUsers) { + try { + console.log(`Migrating: @${user.handle} (${user.did})`); + + if (!user.publicKey) { + console.error(` ❌ No public key for @${user.handle}, skipping`); + failed++; + continue; + } + + // Generate new did:key from public key + const publicKeyBytes = Buffer.from(user.publicKey, 'base64'); + const encoded = base58Encode(new Uint8Array(publicKeyBytes)); + const newDID = `did:key:z${encoded}`; + + console.log(` → New DID: ${newDID}`); + + // Update user record + await db.update(users) + .set({ did: newDID }) + .where(eq(users.id, user.id)); + + console.log(` ✅ Migrated\n`); + migrated++; + + } catch (err) { + console.error(` ❌ Failed to migrate @${user.handle}:`, err); + failed++; + } + } + + console.log('\n═══════════════════════════════════════'); + console.log('Migration complete!'); + console.log(` Migrated: ${migrated}`); + console.log(` Failed: ${failed}`); + console.log(` Total: ${legacyUsers.length}`); + console.log('═══════════════════════════════════════'); + + process.exit(0); +} + +migrateDIDs().catch(err => { + console.error('Migration failed:', err); + process.exit(1); +}); diff --git a/src/app/api/account/export/route.ts b/src/app/api/account/export/route.ts index 195ddf7..98785c9 100644 --- a/src/app/api/account/export/route.ts +++ b/src/app/api/account/export/route.ts @@ -21,6 +21,7 @@ interface ExportManifest { handle: string; sourceNode: string; exportedAt: string; + expiresAt: string; // Export expiration timestamp publicKey: string; privateKeyEncrypted: string; // Encrypted with user's password salt: string; // For key derivation @@ -40,7 +41,7 @@ interface ExportPost { content: string; createdAt: string; replyToApId: string | null; - media: { filename: string; url: string; altText: string | null }[]; + media: { filename: string; url: string; altText: string | null; isIPFS?: boolean }[]; } interface ExportFollowing { @@ -209,6 +210,7 @@ export async function POST(req: NextRequest) { filename: `${post.id}_${idx}${getExtension(m.url)}`, url: m.url, altText: m.altText, + isIPFS: m.url?.startsWith('ipfs://') || false, })), })); @@ -314,12 +316,15 @@ export async function POST(req: NextRequest) { const { encrypted, salt, iv } = encryptPrivateKey(privateKey, password); // Build manifest (without signature first) + const exportedAt = new Date(); + const expiresAt = new Date(exportedAt.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days const manifestData: Omit = { version: '1.0', did: user.did, handle: user.handle, sourceNode: nodeDomain, - exportedAt: new Date().toISOString(), + exportedAt: exportedAt.toISOString(), + expiresAt: expiresAt.toISOString(), publicKey: user.publicKey, privateKeyEncrypted: encrypted, salt, diff --git a/src/app/api/account/import/route.ts b/src/app/api/account/import/route.ts index c85481f..8554d8d 100644 --- a/src/app/api/account/import/route.ts +++ b/src/app/api/account/import/route.ts @@ -6,8 +6,8 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { db, users, posts, media, follows, nodes, chatConversations, chatMessages, bots, botContentSources, botActivityLogs } from '@/db'; -import { eq } from 'drizzle-orm'; +import { db, users, posts, media, follows, remoteFollows, nodes, chatConversations, chatMessages, bots, botContentSources, botActivityLogs } from '@/db'; +import { eq, sql } from 'drizzle-orm'; import * as crypto from 'crypto'; import { encryptApiKey, serializeEncryptedData } from '@/lib/bots/encryption'; import { v4 as uuid } from 'uuid'; @@ -19,6 +19,7 @@ interface ImportManifest { handle: string; sourceNode: string; exportedAt: string; + expiresAt?: string; // Optional for backward compatibility publicKey: string; privateKeyEncrypted: string; salt: string; @@ -38,12 +39,15 @@ interface ImportPost { content: string; createdAt: string; replyToApId: string | null; - media: { filename: string; url: string; altText: string | null }[]; + media: { filename: string; url: string; altText: string | null; isIPFS?: boolean }[]; } interface ImportFollowing { actorUrl: string; handle: string; + isRemote?: boolean; + inboxUrl?: string; + activityId?: string; } interface ImportDMConversation { @@ -167,6 +171,16 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: 'Unsupported export version' }, { status: 400 }); } + // Check if export has expired + if (manifest.expiresAt) { + const expiresAt = new Date(manifest.expiresAt); + if (expiresAt < new Date()) { + return NextResponse.json({ + error: 'Export has expired. Please create a new export from your old node.' + }, { status: 400 }); + } + } + const { dms: importDMs = [], bots: importBots = [] } = exportData; // Verify signature @@ -263,12 +277,14 @@ export async function POST(req: NextRequest) { apUrl: `https://${nodeDomain}/posts/${uuid()}`, }).returning(); - // Import media references (URLs point to old location for now) + // Import media references + // For IPFS media (ipfs://hash), the URL works on any node + // For legacy S3 media, URL points to old node (may break if old node goes down) for (const mediaItem of post.media) { await db.insert(media).values({ userId: newUser.id, postId: newPost.id, - url: mediaItem.url, // Original URL - might need re-uploading + url: mediaItem.url, // IPFS URLs are portable, S3 URLs are not altText: mediaItem.altText, }); } @@ -287,6 +303,49 @@ export async function POST(req: NextRequest) { updatedAt: new Date().toISOString(), }]); + // Import following list + let importedFollowing = 0; + for (const follow of following) { + try { + if (follow.isRemote) { + // Remote follow - add to remoteFollows table + await db.insert(remoteFollows).values({ + followerId: newUser.id, + targetHandle: follow.handle, + targetActorUrl: follow.actorUrl || `https://${follow.handle.split('@')[1]}/users/${follow.handle.split('@')[0]}`, + inboxUrl: follow.inboxUrl || `https://${follow.handle.split('@')[1]}/inbox`, + activityId: follow.activityId || `migrate-${uuid()}`, + }); + } else { + // Local follow - look up user and add to follows table + const targetUser = await db.query.users.findFirst({ + where: eq(users.handle, follow.handle.toLowerCase()), + }); + if (targetUser) { + await db.insert(follows).values({ + followerId: newUser.id, + followingId: targetUser.id, + }); + // Increment following count on target user + await db.update(users) + .set({ followersCount: sql`${users.followersCount} + 1` }) + .where(eq(users.id, targetUser.id)); + } else { + // Local user not found, convert to remote follow + console.log(`[Import] Local user @${follow.handle} not found, skipping follow`); + } + } + importedFollowing++; + } catch (error) { + console.error(`[Import] Failed to restore follow for @${follow.handle}:`, error); + } + } + + // Update user's following count + await db.update(users) + .set({ followingCount: importedFollowing }) + .where(eq(users.id, newUser.id)); + // Import DMs for (const conv of importDMs) { try { @@ -404,7 +463,7 @@ export async function POST(req: NextRequest) { }, stats: { postsImported: importedPosts, - followingToRestore: following.length, + followingImported: importedFollowing, dmsImported: importDMs.length, botsImported: importBots.length, }, diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index da7f23f..1f07db1 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -3,6 +3,7 @@ import { registerUser, createSession } from '@/lib/auth'; import { db, nodes, users } from '@/db'; import { eq } from 'drizzle-orm'; import { verifyTurnstileToken } from '@/lib/turnstile'; +import { testS3Credentials } from '@/lib/storage/s3'; import { z } from 'zod'; const registerSchema = z.object({ @@ -11,6 +12,13 @@ const registerSchema = z.object({ password: z.string().min(8), displayName: z.string().optional(), turnstileToken: z.string().nullable().optional(), + // S3-compatible storage credentials + storageProvider: z.string().min(1), + storageEndpoint: z.string().nullable().optional(), + storageRegion: z.string().min(1), + storageBucket: z.string().min(1), + storageAccessKey: z.string().min(10), + storageSecretKey: z.string().min(10), }); export async function POST(request: Request) { @@ -36,11 +44,37 @@ export async function POST(request: Request) { } } + // Test S3 credentials before creating account + console.log('[Register] Testing S3 credentials...'); + const s3Test = await testS3Credentials( + data.storageEndpoint || null, + data.storageRegion, + data.storageBucket, + data.storageAccessKey, + data.storageSecretKey + ); + + if (!s3Test.success) { + console.error('[Register] S3 credential test failed:', s3Test.error); + return NextResponse.json( + { error: `Storage connection failed: ${s3Test.error}` }, + { status: 400 } + ); + } + + console.log('[Register] S3 credentials verified successfully'); + const user = await registerUser( data.handle, data.email, data.password, - data.displayName + data.displayName, + data.storageProvider, + data.storageEndpoint || null, + data.storageRegion, + data.storageBucket, + data.storageAccessKey, + data.storageSecretKey ); // Check if this is an NSFW node and auto-enable NSFW settings diff --git a/src/app/api/bots/route.ts b/src/app/api/bots/route.ts index 0ce4887..1839c87 100644 --- a/src/app/api/bots/route.ts +++ b/src/app/api/bots/route.ts @@ -17,6 +17,7 @@ import { BotHandleTakenError, BotValidationError, } from '@/lib/bots/botManager'; +import { generateAndUploadAvatarToUserStorage } from '@/lib/storage/s3'; // Schema for creating a bot const createBotSchema = z.object({ @@ -42,6 +43,8 @@ const createBotSchema = z.object({ timezone: z.string().optional(), }).optional(), autonomousMode: z.boolean().optional(), + // Optional: password to generate avatar using owner's S3 storage + ownerPassword: z.string().optional(), }); /** @@ -58,11 +61,33 @@ export async function POST(request: Request) { const body = await request.json(); const data = createBotSchema.parse(body); + // Generate bot avatar using owner's S3 storage if password provided and no avatar URL + let botAvatarUrl = data.avatarUrl; + if (!botAvatarUrl && data.ownerPassword && user.storageAccessKeyEncrypted && user.storageSecretKeyEncrypted && user.storageBucket) { + try { + const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; + const botHandle = `${data.handle.toLowerCase()}@${nodeDomain}`; + + botAvatarUrl = await generateAndUploadAvatarToUserStorage( + botHandle, + user.storageEndpoint, + user.storageRegion || 'auto', + user.storageBucket, + user.storageAccessKeyEncrypted, + user.storageSecretKeyEncrypted, + data.ownerPassword + ); + } catch (err) { + console.error('[Bot API] Failed to generate bot avatar:', err); + // Continue without avatar - user can set it later + } + } + const bot = await createBot(user.id, { name: data.name, handle: data.handle, bio: data.bio, - avatarUrl: data.avatarUrl, + avatarUrl: botAvatarUrl, headerUrl: data.headerUrl, personality: data.personality, llmProvider: data.llmProvider, diff --git a/src/app/api/chat/receive/route.ts b/src/app/api/chat/receive/route.ts index 6e8021d..f21fb26 100644 --- a/src/app/api/chat/receive/route.ts +++ b/src/app/api/chat/receive/route.ts @@ -1,26 +1,93 @@ - import { NextRequest, NextResponse } from 'next/server'; import { db } from '@/db'; import { chatConversations, chatMessages, users, handleRegistry } from '@/db/schema'; import { eq, and } from 'drizzle-orm'; import { verifyActionSignature, type SignedAction } from '@/lib/auth/verify-signature'; +import { verifySwarmRequest } from '@/lib/swarm/signature'; +import { fetchAndCacheRemoteKey, logKeyChange } from '@/lib/swarm/identity-cache'; +import { z } from 'zod'; + +// Schema for direct signed action (legacy) +const chatReceiveSchema = z.object({ + did: z.string().regex(/^did:/, 'Must be a valid DID'), + handle: z.string().min(3).max(30), + data: z.object({ + recipientDid: z.string().regex(/^did:/, 'Must be a valid DID'), + content: z.string().min(1).max(5000), + }), + signature: z.string(), + timestamp: z.number().optional(), +}); + +// Schema for federated envelope +const federatedEnvelopeSchema = z.object({ + userAction: chatReceiveSchema, + fullSenderHandle: z.string().min(3).max(60), + sourceDomain: z.string().min(1), + ts: z.number(), +}); /** * POST /api/chat/receive * Endpoint for receiving federated chat messages from other nodes. - * Expects a SignedAction payload from the sender. + * Expects either: + * 1. A SignedAction payload from the sender (legacy, for backward compatibility) + * 2. A federated envelope with node's signature and user's signed action */ export async function POST(request: NextRequest) { try { - const signedAction: SignedAction = await request.json(); + const body = await request.json(); + + // Check if this is a federated envelope (node-signed) + const swarmSignature = request.headers.get('X-Swarm-Signature'); + const sourceDomain = request.headers.get('X-Swarm-Source-Domain'); + + let signedAction: SignedAction; + let fullSenderHandle: string | null = null; + + if (swarmSignature && sourceDomain && body.userAction) { + // Federated envelope format - validate and verify node signature + const envelopeValidation = federatedEnvelopeSchema.safeParse(body); + if (!envelopeValidation.success) { + return NextResponse.json( + { error: 'Invalid envelope payload', details: envelopeValidation.error.issues }, + { status: 400 } + ); + } + + const isValidNodeSig = await verifySwarmRequest( + { userAction: body.userAction, fullSenderHandle: body.fullSenderHandle, sourceDomain: body.sourceDomain, ts: body.ts }, + swarmSignature, + sourceDomain + ); + + if (!isValidNodeSig) { + console.error('[Chat Receive] Invalid node signature from:', sourceDomain); + return NextResponse.json({ error: 'Invalid node signature' }, { status: 403 }); + } + + // Extract user's signed action and full handle from envelope + signedAction = body.userAction; + fullSenderHandle = body.fullSenderHandle; + console.log(`[Chat Receive] Federated envelope from node: ${sourceDomain}, full handle: ${fullSenderHandle}`); + } else { + // Legacy format - direct user signed action + const actionValidation = chatReceiveSchema.safeParse(body); + if (!actionValidation.success) { + return NextResponse.json( + { error: 'Invalid action payload', details: actionValidation.error.issues }, + { status: 400 } + ); + } + signedAction = body; + } + const { did, handle, data } = signedAction; const { recipientDid, content } = data || {}; - if (!did || !handle || !recipientDid || !content) { - return NextResponse.json({ error: 'Invalid payload' }, { status: 400 }); - } - - console.log(`[Chat Receive] From: ${handle} (DID: ${did}), To: ${recipientDid}`); + // Use full handle if provided in envelope, otherwise fall back to signed handle + const senderHandle = fullSenderHandle || handle; + console.log(`[Chat Receive] From: ${senderHandle} (DID: ${did}), To: ${recipientDid}`); // 1. Resolve Sender Public Key let senderUser = await db.query.users.findFirst({ @@ -28,15 +95,15 @@ export async function POST(request: NextRequest) { }); let publicKey = senderUser?.publicKey; - let senderDisplayName = senderUser?.displayName || handle; + let senderDisplayName = senderUser?.displayName || senderHandle; let senderAvatarUrl = senderUser?.avatarUrl; let senderNodeDomain: string | null = null; if (!senderUser) { // Unknown user - likely remote. We need to fetch their profile to get the public key. - // Derive domain from handle if possible - if (handle.includes('@')) { - const parts = handle.split('@'); + // Derive domain from full sender handle if possible + if (senderHandle.includes('@')) { + const parts = senderHandle.split('@'); senderNodeDomain = parts[parts.length - 1]; } else { // Try to get from header first @@ -55,26 +122,44 @@ export async function POST(request: NextRequest) { if (senderNodeDomain) { try { const protocol = senderNodeDomain.includes('localhost') ? 'http' : 'https'; - // Fetch profile from remote node - // Assuming /api/users/:handle convention - const remoteHandle = handle.includes('@') ? handle.split('@')[0] : handle; - const res = await fetch(`${protocol}://${senderNodeDomain}/api/users/${remoteHandle}`); + const remoteHandle = senderHandle.includes('@') ? senderHandle.split('@')[0] : senderHandle; - if (res.ok) { - const profileData = await res.json(); - const remoteProfile = profileData.user; - if (remoteProfile && remoteProfile.publicKey) { - if (remoteProfile.did !== did) { - console.error('DID mismatch for remote user'); - } else { - publicKey = remoteProfile.publicKey; - senderDisplayName = remoteProfile.displayName || handle; - senderAvatarUrl = remoteProfile.avatarUrl; + // Fetch public key with TOFU validation + const { publicKey: cachedOrFreshKey, fromCache, keyChanged } = await fetchAndCacheRemoteKey( + did, + senderHandle, + senderNodeDomain, + async () => { + // Fetch profile from remote node + const res = await fetch(`${protocol}://${senderNodeDomain}/api/users/${remoteHandle}`); + if (!res.ok) return null; + const profileData = await res.json(); + const remoteProfile = profileData.user; + if (remoteProfile?.did !== did) { + console.error('[Chat Receive] DID mismatch for remote user'); + return null; + } + return remoteProfile?.publicKey || null; + } + ); + + if (cachedOrFreshKey) { + publicKey = cachedOrFreshKey; + console.log(`[Chat Receive] Using ${fromCache ? 'cached' : 'fetched'} public key for ${senderHandle}${keyChanged ? ' (KEY CHANGED!)' : ''}`); + + // Also fetch display name/avatar if not from cache (or if we need fresh data) + if (!fromCache || keyChanged) { + const res = await fetch(`${protocol}://${senderNodeDomain}/api/users/${remoteHandle}`); + if (res.ok) { + const profileData = await res.json(); + const remoteProfile = profileData.user; + senderDisplayName = remoteProfile?.displayName || senderHandle; + senderAvatarUrl = remoteProfile?.avatarUrl; // CACHE: Upsert the remote user into our local database const { upsertRemoteUser } = await import('@/lib/swarm/user-cache'); await upsertRemoteUser({ - handle: handle, // already full handle if remote + handle: senderHandle, // use full handle (user@domain) displayName: senderDisplayName, avatarUrl: senderAvatarUrl || null, did: did || '', @@ -83,7 +168,7 @@ export async function POST(request: NextRequest) { } } } else { - console.error('Failed to fetch remote profile:', res.status); + console.error('Failed to fetch remote profile: no key returned'); } } catch (e) { console.error('Remote profile fetch failed:', e); @@ -112,20 +197,20 @@ export async function POST(request: NextRequest) { // 4. Find or Create Conversation // For the RECIPIENT, the conversation is with the SENDER - // Use full handle - const fullSenderHandle = handle.includes('@') ? handle : (senderNodeDomain ? `${handle}@${senderNodeDomain}` : handle); + // Use full handle from envelope if available, otherwise construct from handle + domain + const computedFullSenderHandle = senderHandle.includes('@') ? senderHandle : (senderNodeDomain ? `${senderHandle}@${senderNodeDomain}` : senderHandle); let conversation = await db.query.chatConversations.findFirst({ where: and( eq(chatConversations.participant1Id, recipientUser.id), - eq(chatConversations.participant2Handle, fullSenderHandle) + eq(chatConversations.participant2Handle, computedFullSenderHandle) ) }); if (!conversation) { const [newConv] = await db.insert(chatConversations).values({ participant1Id: recipientUser.id, - participant2Handle: fullSenderHandle, + participant2Handle: computedFullSenderHandle, lastMessageAt: new Date(), lastMessagePreview: content.slice(0, 50) }).returning(); @@ -144,7 +229,7 @@ export async function POST(request: NextRequest) { // 5. Store Message await db.insert(chatMessages).values({ conversationId: conversation.id, - senderHandle: fullSenderHandle, + senderHandle: computedFullSenderHandle, senderDisplayName: senderDisplayName, senderAvatarUrl: senderAvatarUrl, senderNodeDomain: senderNodeDomain, @@ -156,7 +241,7 @@ export async function POST(request: NextRequest) { // 6. Update Registry (to ensure we can reply efficiently) if (senderNodeDomain) { await db.insert(handleRegistry).values({ - handle: fullSenderHandle, // user@domain + handle: computedFullSenderHandle, // user@domain did: did, nodeDomain: senderNodeDomain }).onConflictDoUpdate({ @@ -172,6 +257,14 @@ export async function POST(request: NextRequest) { } catch (error: any) { console.error('Federated Receive Failed:', error); + + if (error instanceof z.ZodError) { + return NextResponse.json( + { error: 'Invalid input', details: error.issues }, + { status: 400 } + ); + } + return NextResponse.json({ error: error.message }, { status: 500 }); } } diff --git a/src/app/api/chat/send/route.ts b/src/app/api/chat/send/route.ts index da1b837..be5f907 100644 --- a/src/app/api/chat/send/route.ts +++ b/src/app/api/chat/send/route.ts @@ -4,10 +4,13 @@ import { chatConversations, chatMessages, users, handleRegistry, follows } from import { requireSignedAction } from '@/lib/auth/verify-signature'; import { eq, and } from 'drizzle-orm'; import { z } from 'zod'; +import { createSignedPayload } from '@/lib/swarm/signature'; + +const handleRegex = /^[a-zA-Z0-9_]{3,20}$/; const chatSendSchema = z.object({ - recipientDid: z.string(), - recipientHandle: z.string(), + recipientDid: z.string().min(1).regex(/^did:/, 'Must be a valid DID (did:key:... or did:synapsis:...)'), + recipientHandle: z.string().min(3).max(30).regex(handleRegex, 'Handle must be 3-20 characters, alphanumeric and underscores only'), content: z.string().min(1).max(5000), }); @@ -33,7 +36,14 @@ export async function POST(request: NextRequest) { where: eq(users.did, recipientDid) }); - if (recipientUser) { + // Check if recipient is a local user (not remote/swarm cached) + // Remote users have handles with @domain or IDs starting with "swarm:" + const isRemoteUser = recipientUser && ( + recipientUser.handle.includes('@') || + recipientUser.id.startsWith('swarm:') + ); + + if (recipientUser && !isRemoteUser) { // Reject if recipient is a bot if (recipientUser.isBot) { return NextResponse.json({ error: 'Cannot DM a bot account' }, { status: 400 }); @@ -165,19 +175,35 @@ export async function POST(request: NextRequest) { const protocol = targetDomain.includes('localhost') ? 'http' : 'https'; const sourceDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || ''; + // Construct full sender handle for federation + const fullSenderHandle = `${user.handle}@${sourceDomain}`; + console.log(`[Remote Send] Debug:`, { targetDomain, targetUrl: `${protocol}://${targetDomain}/api/chat/receive`, sourceDomainEnv: sourceDomain, + fullSenderHandle, }); + // Create a federated envelope with node's signature + // This wraps the user's signed action with the full handle + const federatedPayload = { + userAction: signedAction, + fullSenderHandle, + sourceDomain, + ts: Date.now(), + }; + + const { payload, signature } = await createSignedPayload(federatedPayload); + const res = await fetch(`${protocol}://${targetDomain}/api/chat/receive`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-Swarm-Source-Domain': sourceDomain + 'X-Swarm-Source-Domain': sourceDomain, + 'X-Swarm-Signature': signature, }, - body: JSON.stringify(signedAction) // Forward the user's signed intent + body: JSON.stringify(payload) }); if (!res.ok) { diff --git a/src/app/api/handles/resolve/route.ts b/src/app/api/handles/resolve/route.ts index 6233151..b2025cd 100644 --- a/src/app/api/handles/resolve/route.ts +++ b/src/app/api/handles/resolve/route.ts @@ -2,6 +2,9 @@ import { NextResponse } from 'next/server'; import { db, handleRegistry } from '@/db'; import { eq } from 'drizzle-orm'; import { normalizeHandle, upsertHandleEntries } from '@/lib/federation/handles'; +import { z } from 'zod'; + +const handleParamSchema = z.string().min(3).max(40).regex(/^[a-zA-Z0-9_]+(@[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,})?$/, 'Invalid handle format'); const parseHandleWithDomain = (handle: string) => { const clean = normalizeHandle(handle); @@ -19,12 +22,22 @@ export async function GET(request: Request) { } const { searchParams } = new URL(request.url); - const handleParam = searchParams.get('handle'); + const handleParamRaw = searchParams.get('handle'); - if (!handleParam) { + if (!handleParamRaw) { return NextResponse.json({ error: 'Handle is required' }, { status: 400 }); } + // Validate handle format + const handleValidation = handleParamSchema.safeParse(handleParamRaw); + if (!handleValidation.success) { + return NextResponse.json( + { error: 'Invalid handle format', details: handleValidation.error.issues }, + { status: 400 } + ); + } + const handleParam = handleValidation.data; + const parsed = parseHandleWithDomain(handleParam); const lookupHandle = parsed ? parsed.handle : normalizeHandle(handleParam); const localEntry = await db.query.handleRegistry.findFirst({ @@ -63,6 +76,12 @@ export async function GET(request: Request) { return NextResponse.json(entry); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { error: 'Invalid input', details: error.issues }, + { status: 400 } + ); + } console.error('Handle resolve error:', error); return NextResponse.json({ error: 'Failed to resolve handle' }, { status: 500 }); } diff --git a/src/app/api/media/upload/route.ts b/src/app/api/media/upload/route.ts index 7962d88..47c9f44 100644 --- a/src/app/api/media/upload/route.ts +++ b/src/app/api/media/upload/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, media } from '@/db'; import { requireAuth } from '@/lib/auth'; -import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +import { uploadToUserStorage } from '@/lib/storage/s3'; import { v4 as uuidv4 } from 'uuid'; const MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10MB for images @@ -16,6 +16,7 @@ export async function POST(req: NextRequest) { const formData = await req.formData(); const file = formData.get('file') as File | null; const altText = (formData.get('alt') as string | null) || null; + const password = formData.get('password') as string | null; if (!file) { return NextResponse.json({ error: 'No file provided' }, { status: 400 }); @@ -39,47 +40,43 @@ export async function POST(req: NextRequest) { }, { status: 400 }); } - const buffer = Buffer.from(await file.arrayBuffer()); - // Sanitize filename to be safe for S3 keys - const filename = `${uuidv4()}-${file.name.replace(/[^a-zA-Z0-9.-]/g, '')}`; - - // S3 Configuration - const s3 = new S3Client({ - region: process.env.STORAGE_REGION || 'us-east-1', - endpoint: process.env.STORAGE_ENDPOINT, - credentials: { - accessKeyId: process.env.STORAGE_ACCESS_KEY || '', - secretAccessKey: process.env.STORAGE_SECRET_KEY || '', - }, - forcePathStyle: true, // Needed for many S3-compatible providers - }); - - const bucket = process.env.STORAGE_BUCKET || 'synapsis'; - - await s3.send(new PutObjectCommand({ - Bucket: bucket, - Key: filename, - Body: buffer, - ContentType: file.type, - ACL: 'public-read', - })); - - // Construct Public URL - let url = ''; - if (process.env.STORAGE_PUBLIC_BASE_URL) { - url = `${process.env.STORAGE_PUBLIC_BASE_URL}/${filename}`; - } else if (process.env.STORAGE_ENDPOINT) { - url = `${process.env.STORAGE_ENDPOINT}/${bucket}/${filename}`; - } else { - return NextResponse.json({ error: 'Storage not configured - missing STORAGE_PUBLIC_BASE_URL or STORAGE_ENDPOINT' }, { status: 500 }); + // Check if user has S3 storage configured + if (!user.storageProvider || !user.storageAccessKeyEncrypted || !user.storageSecretKeyEncrypted) { + return NextResponse.json({ + error: 'Storage not configured. Please set up S3-compatible storage in your settings.' + }, { status: 400 }); } - // Store media record + // Require password to decrypt storage credentials + if (!password) { + return NextResponse.json({ + error: 'Password required to upload media. Your storage credentials are encrypted and need your password to decrypt.' + }, { status: 401 }); + } + + const buffer = Buffer.from(await file.arrayBuffer()); + const filename = `${uuidv4()}-${file.name.replace(/[^a-zA-Z0-9.-]/g, '')}`; + + // Upload to user's own S3-compatible storage + const uploadResult = await uploadToUserStorage( + buffer, + filename, + file.type, + user.storageProvider as any, + user.storageEndpoint, + user.storageRegion || 'us-east-1', + user.storageBucket || '', + user.storageAccessKeyEncrypted, + user.storageSecretKeyEncrypted, + password + ); + + // Store media record with S3 URL if (db) { const [mediaRecord] = await db.insert(media).values({ userId: user.id, postId: null, - url, + url: uploadResult.url, altText, mimeType: file.type, width: 0, // TODO: Get actual dimensions @@ -89,19 +86,24 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: true, media: mediaRecord, - url, + url: uploadResult.url, + key: uploadResult.key, }); } return NextResponse.json({ success: true, - url, + url: uploadResult.url, + key: uploadResult.key, }); } catch (error) { if (error instanceof Error && error.message === 'Authentication required') { return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); } + if (error instanceof Error && error.message.includes('Storage')) { + return NextResponse.json({ error: error.message }, { status: 400 }); + } console.error('Upload error:', error); return NextResponse.json({ error: 'Upload failed' }, { status: 500 }); } diff --git a/src/app/api/posts/[id]/like/route.ts b/src/app/api/posts/[id]/like/route.ts index f194324..0b53f43 100644 --- a/src/app/api/posts/[id]/like/route.ts +++ b/src/app/api/posts/[id]/like/route.ts @@ -2,11 +2,18 @@ import { NextResponse } from 'next/server'; import { db, posts, likes, users, notifications } from '@/db'; import { requireAuth } from '@/lib/auth'; import { requireSignedAction, type SignedAction } from '@/lib/auth/verify-signature'; -import { eq, and } from 'drizzle-orm'; +import { eq, and, sql } from 'drizzle-orm'; +import { z } from 'zod'; import crypto from 'crypto'; type RouteContext = { params: Promise<{ id: string }> }; +// UUID or swarm post ID format (swarm:domain:uuid) +const postIdSchema = z.union([ + z.string().uuid(), + z.string().regex(/^swarm:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, 'Invalid swarm post ID format'), +]); + /** * Extract domain from a swarm post ID (swarm:domain:postId) */ @@ -43,9 +50,10 @@ export async function POST(request: Request, context: RouteContext) { // Verify the signature and get the user const user = await requireSignedAction(signedAction); - // Extract postId from the signed action data + // Extract and validate postId from the signed action data const { postId: rawId } = signedAction.data; - const postId = decodeURIComponent(rawId); + const decodedId = decodeURIComponent(rawId); + const postId = postIdSchema.parse(decodedId); const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; if (user.isSuspended || user.isSilenced) { @@ -118,9 +126,9 @@ export async function POST(request: Request, context: RouteContext) { postId, }); - // Update post's like count + // Update post's like count (atomic increment) await db.update(posts) - .set({ likesCount: post.likesCount + 1 }) + .set({ likesCount: sql`${posts.likesCount} + 1` }) .where(eq(posts.id, postId)); if (post.userId !== user.id) { @@ -187,7 +195,9 @@ export async function POST(request: Request, context: RouteContext) { console.warn(`[Swarm] Like delivery failed: ${result.error}`); } } catch (err) { - console.error('[Swarm] Error delivering like:', err); + // Log error with context but don't fail the request - swarm delivery is best-effort + console.error('[Like] Error delivering like to swarm:', err); + console.error('[Like] Context:', { postId: originalPostId, userId: user.id, targetDomain }); } })(); } @@ -197,6 +207,9 @@ export async function POST(request: Request, context: RouteContext) { return NextResponse.json({ success: true, liked: true }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid post ID', details: error.issues }, { status: 400 }); + } if (error instanceof Error) { // Handle signature verification errors if (error.message === 'User not found' || @@ -222,9 +235,10 @@ export async function DELETE(request: Request, context: RouteContext) { // Verify the signature and get the user const user = await requireSignedAction(signedAction); - // Extract postId from the signed action data + // Extract and validate postId from the signed action data const { postId: rawId } = signedAction.data; - const postId = decodeURIComponent(rawId); + const decodedId = decodeURIComponent(rawId); + const postId = postIdSchema.parse(decodedId); const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; if (user.isSuspended || user.isSilenced) { @@ -289,9 +303,9 @@ export async function DELETE(request: Request, context: RouteContext) { // Remove like await db.delete(likes).where(eq(likes.id, existingLike.id)); - // Update post's like count + // Update post's like count (atomic decrement, clamped to 0) await db.update(posts) - .set({ likesCount: Math.max(0, post.likesCount - 1) }) + .set({ likesCount: sql`GREATEST(0, ${posts.likesCount} - 1)` }) .where(eq(posts.id, postId)); // SWARM-FIRST: Deliver unlike to swarm node @@ -320,7 +334,9 @@ export async function DELETE(request: Request, context: RouteContext) { console.warn(`[Swarm] Unlike delivery failed: ${result.error}`); } } catch (err) { - console.error('[Swarm] Error delivering unlike:', err); + // Log error with context but don't fail the request - swarm delivery is best-effort + console.error('[Like] Error delivering unlike to swarm:', err); + console.error('[Like] Context:', { postId: originalPostId, userId: user.id, targetDomain }); } })(); } @@ -328,6 +344,9 @@ export async function DELETE(request: Request, context: RouteContext) { return NextResponse.json({ success: true, liked: false }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid post ID', details: error.issues }, { status: 400 }); + } if (error instanceof Error) { // Handle signature verification errors if (error.message === 'User not found' || diff --git a/src/app/api/posts/[id]/repost/route.ts b/src/app/api/posts/[id]/repost/route.ts index e5455d0..8b9e922 100644 --- a/src/app/api/posts/[id]/repost/route.ts +++ b/src/app/api/posts/[id]/repost/route.ts @@ -1,11 +1,18 @@ import { NextResponse } from 'next/server'; import { db, posts, users, notifications } from '@/db'; import { requireAuth } from '@/lib/auth'; -import { eq, and } from 'drizzle-orm'; +import { eq, and, sql } from 'drizzle-orm'; +import { z } from 'zod'; import crypto from 'crypto'; type RouteContext = { params: Promise<{ id: string }> }; +// UUID or swarm post ID format (swarm:domain:uuid) +const postIdSchema = z.union([ + z.string().uuid(), + z.string().regex(/^swarm:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, 'Invalid swarm post ID format'), +]); + /** * Extract domain from a swarm post ID (swarm:domain:postId) */ @@ -38,7 +45,8 @@ export async function POST(request: Request, context: RouteContext) { try { const user = await requireAuth(); const { id: rawId } = await context.params; - const postId = decodeURIComponent(rawId); + const decodedId = decodeURIComponent(rawId); + const postId = postIdSchema.parse(decodedId); const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; if (user.isSuspended || user.isSilenced) { @@ -116,12 +124,12 @@ export async function POST(request: Request, context: RouteContext) { // Update original post's repost count await db.update(posts) - .set({ repostsCount: originalPost.repostsCount + 1 }) + .set({ repostsCount: sql`${posts.repostsCount} + 1` }) .where(eq(posts.id, postId)); // Update user's post count await db.update(users) - .set({ postsCount: user.postsCount + 1 }) + .set({ postsCount: sql`${users.postsCount} + 1` }) .where(eq(users.id, user.id)); if (originalPost.userId !== user.id) { @@ -196,6 +204,9 @@ export async function POST(request: Request, context: RouteContext) { return NextResponse.json({ success: true, repost, reposted: true }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid post ID', details: error.issues }, { status: 400 }); + } if (error instanceof Error && error.message === 'Authentication required') { return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); } @@ -208,7 +219,8 @@ export async function DELETE(request: Request, context: RouteContext) { try { const user = await requireAuth(); const { id: rawId } = await context.params; - const postId = decodeURIComponent(rawId); + const decodedId = decodeURIComponent(rawId); + const postId = postIdSchema.parse(decodedId); const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; if (user.isSuspended || user.isSilenced) { @@ -272,17 +284,20 @@ export async function DELETE(request: Request, context: RouteContext) { // Update original post's repost count if (originalPost) { await db.update(posts) - .set({ repostsCount: Math.max(0, originalPost.repostsCount - 1) }) + .set({ repostsCount: sql`GREATEST(0, ${posts.repostsCount} - 1)` }) .where(eq(posts.id, postId)); } // Update user's post count await db.update(users) - .set({ postsCount: Math.max(0, user.postsCount - 1) }) + .set({ postsCount: sql`GREATEST(0, ${users.postsCount} - 1)` }) .where(eq(users.id, user.id)); return NextResponse.json({ success: true, reposted: false }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid post ID', details: error.issues }, { status: 400 }); + } if (error instanceof Error && error.message === 'Authentication required') { return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); } diff --git a/src/app/api/posts/[id]/route.ts b/src/app/api/posts/[id]/route.ts index 55800b4..45c0cd2 100644 --- a/src/app/api/posts/[id]/route.ts +++ b/src/app/api/posts/[id]/route.ts @@ -1,6 +1,19 @@ import { NextResponse } from 'next/server'; import { db, posts, users, media, remotePosts } from '@/db'; -import { eq, desc, and } from 'drizzle-orm'; +import { eq, desc, and, sql } from 'drizzle-orm'; +import { z } from 'zod'; + +// Schema for local post ID (UUID) +const localPostIdSchema = z.string().uuid('Invalid post ID format'); + +// Schema for swarm post ID (swarm:domain:uuid) +const swarmPostIdSchema = z.string().regex( + /^swarm:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, + 'Invalid swarm post ID format' +); + +// Combined schema that accepts either format +const postIdSchema = z.union([localPostIdSchema, swarmPostIdSchema]); export async function GET( request: Request, @@ -424,15 +437,10 @@ export async function DELETE( // 3. Delete the post (cascades to media, likes, notifications) await db.delete(posts).where(eq(posts.id, id)); - // 4. Decrement the post author's postsCount - const postAuthor = await db.query.users.findFirst({ - where: eq(users.id, post.userId), - }); - if (postAuthor && postAuthor.postsCount > 0) { - await db.update(users) - .set({ postsCount: postAuthor.postsCount - 1 }) - .where(eq(users.id, post.userId)); - } + // 4. Decrement the post author's postsCount (atomic decrement, clamped to 0) + await db.update(users) + .set({ postsCount: sql`GREATEST(0, ${users.postsCount} - 1)` }) + .where(eq(users.id, post.userId)); return NextResponse.json({ success: true }); } catch (error) { diff --git a/src/app/api/posts/route.ts b/src/app/api/posts/route.ts index da6c9c0..f05840c 100644 --- a/src/app/api/posts/route.ts +++ b/src/app/api/posts/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from 'next/server'; import { db, posts, users, media, follows, mutes, blocks, likes, remoteFollows, remotePosts } from '@/db'; import { requireAuth } from '@/lib/auth'; import { requireSignedAction, type SignedAction } from '@/lib/auth/verify-signature'; -import { eq, desc, and, inArray, isNull, isNotNull, or, lt } from 'drizzle-orm'; +import { eq, desc, and, inArray, isNull, isNotNull, or, lt, sql } from 'drizzle-orm'; import type { SQL } from 'drizzle-orm'; import { z } from 'zod'; @@ -19,7 +19,7 @@ const buildWhere = (...conditions: Array) => { const createPostSchema = z.object({ content: z.string().min(1).max(POST_MAX_LENGTH), - replyToId: z.string().optional(), // Can be UUID or swarm:domain:uuid + replyToId: z.string().uuid().optional(), // Must be UUID (swarm replies use separate field) swarmReplyTo: z.object({ postId: z.string(), nodeDomain: z.string(), @@ -105,21 +105,16 @@ export async function POST(request: Request) { }); } - // Update user's post count + // Update user's post count (atomic increment to prevent race conditions) await db.update(users) - .set({ postsCount: user.postsCount + 1 }) + .set({ postsCount: sql`${users.postsCount} + 1` }) .where(eq(users.id, user.id)); - // If this is a reply, update the parent's reply count + // If this is a reply, update the parent's reply count (atomic increment) if (data.replyToId) { - const parentPost = await db.query.posts.findFirst({ - where: eq(posts.id, data.replyToId), - }); - if (parentPost) { - await db.update(posts) - .set({ repliesCount: parentPost.repliesCount + 1 }) - .where(eq(posts.id, data.replyToId)); - } + await db.update(posts) + .set({ repliesCount: sql`${posts.repliesCount} + 1` }) + .where(eq(posts.id, data.replyToId)); } // DEPRECATED: Push-based federation disabled @@ -213,7 +208,9 @@ export async function POST(request: Request) { } } } catch (err) { - console.error('[Local] Error creating mention notifications:', err); + // Log error with context but don't fail the request - mention notifications are best-effort + console.error('[Posts] Error creating mention notifications:', err); + console.error('[Posts] Context:', { postId: post.id, userId: user.id, content: data.content?.slice(0, 100) }); } })(); @@ -237,7 +234,9 @@ export async function POST(request: Request) { console.log(`[Swarm] Delivered ${result.delivered} mentions (${result.failed} failed)`); } } catch (err) { - console.error('[Swarm] Error delivering mentions:', err); + // Log error with context but don't fail the request - swarm delivery is best-effort + console.error('[Posts] Error delivering swarm mentions:', err); + console.error('[Posts] Context:', { postId: post.id, userId: user.id, nodeDomain }); } })(); @@ -264,7 +263,9 @@ export async function POST(request: Request) { console.log(`[Swarm] Post ${post.id} delivered to ${swarmResult.delivered} swarm nodes (${swarmResult.failed} failed)`); } } catch (err) { - console.error('[Swarm] Error delivering post:', err); + // Log error with context but don't fail the request - swarm delivery is best-effort + console.error('[Posts] Error delivering post to swarm followers:', err); + console.error('[Posts] Context:', { postId: post.id, userId: user.id, nodeDomain }); } })(); diff --git a/src/app/api/swarm/chat/conversations/[id]/route.ts b/src/app/api/swarm/chat/conversations/[id]/route.ts index 865edf1..133e71a 100644 --- a/src/app/api/swarm/chat/conversations/[id]/route.ts +++ b/src/app/api/swarm/chat/conversations/[id]/route.ts @@ -8,6 +8,15 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, chatConversations, chatMessages, users } from '@/db'; import { eq, and } from 'drizzle-orm'; import { getSession } from '@/lib/auth'; +import { z } from 'zod'; + +// Schema for conversation ID parameter +const conversationIdSchema = z.string().uuid('Invalid conversation ID format'); + +// Schema for delete query parameter +const deleteQuerySchema = z.object({ + deleteFor: z.enum(['self', 'both']).optional(), +}); export async function DELETE( request: NextRequest, @@ -24,8 +33,23 @@ export async function DELETE( } const { id } = await params; + + // Validate conversation ID + const idResult = conversationIdSchema.safeParse(id); + if (!idResult.success) { + return NextResponse.json({ error: 'Invalid conversation ID', details: idResult.error.issues }, { status: 400 }); + } + const { searchParams } = new URL(request.url); - const deleteFor = searchParams.get('deleteFor'); // 'self' or 'both' + const deleteForRaw = searchParams.get('deleteFor'); // 'self' or 'both' + + // Validate deleteFor parameter + const deleteForResult = deleteQuerySchema.safeParse({ deleteFor: deleteForRaw || undefined }); + if (!deleteForResult.success) { + return NextResponse.json({ error: 'Invalid deleteFor parameter', details: deleteForResult.error.issues }, { status: 400 }); + } + + const { deleteFor } = deleteForResult.data; // Verify the conversation belongs to this user const conversation = await db.query.chatConversations.findFirst({ @@ -118,6 +142,9 @@ export async function DELETE( }); } } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 }); + } console.error('Delete conversation error:', error); return NextResponse.json({ error: 'Failed to delete conversation' }, { status: 500 }); } diff --git a/src/app/api/swarm/chat/messages/route.ts b/src/app/api/swarm/chat/messages/route.ts index 9618b1c..15baaba 100644 --- a/src/app/api/swarm/chat/messages/route.ts +++ b/src/app/api/swarm/chat/messages/route.ts @@ -9,6 +9,19 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, chatConversations, chatMessages, users } from '@/db'; import { eq, desc, and, lt, isNull, sql, inArray } from 'drizzle-orm'; import { getSession } from '@/lib/auth'; +import { z } from 'zod'; + +// Schema for query parameters +const messagesQuerySchema = z.object({ + conversationId: z.string().uuid(), + cursor: z.string().datetime().optional(), + limit: z.number().min(1).max(100).default(50), +}); + +// Schema for PATCH request body +const markReadSchema = z.object({ + conversationId: z.string().uuid(), +}); export async function GET(request: NextRequest) { @@ -23,14 +36,20 @@ export async function GET(request: NextRequest) { } const { searchParams } = new URL(request.url); - const conversationId = searchParams.get('conversationId'); - const cursor = searchParams.get('cursor'); // For pagination - const limit = Math.min(parseInt(searchParams.get('limit') || '50'), 100); + + // Validate query parameters + const queryResult = messagesQuerySchema.safeParse({ + conversationId: searchParams.get('conversationId'), + cursor: searchParams.get('cursor') || undefined, + limit: parseInt(searchParams.get('limit') || '50'), + }); - if (!conversationId) { - return NextResponse.json({ error: 'conversationId required' }, { status: 400 }); + if (!queryResult.success) { + return NextResponse.json({ error: 'Invalid query parameters', details: queryResult.error.issues }, { status: 400 }); } + const { conversationId, cursor, limit } = queryResult.data; + // Verify user has access to this conversation const conversation = await db.query.chatConversations.findFirst({ where: and( @@ -114,6 +133,9 @@ export async function GET(request: NextRequest) { nextCursor: messages.length === limit ? messages[messages.length - 1].createdAt.toISOString() : null, }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 }); + } console.error('Get messages error:', error); return NextResponse.json({ error: 'Failed to get messages' }, { status: 500 }); } @@ -130,11 +152,15 @@ export async function PATCH(request: NextRequest) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const { conversationId } = await request.json(); - - if (!conversationId) { - return NextResponse.json({ error: 'conversationId required' }, { status: 400 }); + const body = await request.json(); + + // Validate request body + const bodyResult = markReadSchema.safeParse(body); + if (!bodyResult.success) { + return NextResponse.json({ error: 'Invalid request body', details: bodyResult.error.issues }, { status: 400 }); } + + const { conversationId } = bodyResult.data; // Verify user has access to this conversation const conversation = await db.query.chatConversations.findFirst({ @@ -160,6 +186,9 @@ export async function PATCH(request: NextRequest) { return NextResponse.json({ success: true }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 }); + } console.error('Mark as read error:', error); return NextResponse.json({ error: 'Failed to mark as read' }, { status: 500 }); } diff --git a/src/app/api/swarm/interactions/follow/route.ts b/src/app/api/swarm/interactions/follow/route.ts index f207fdb..1afe8a1 100644 --- a/src/app/api/swarm/interactions/follow/route.ts +++ b/src/app/api/swarm/interactions/follow/route.ts @@ -11,22 +11,22 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, users, notifications, remoteFollowers } from '@/db'; -import { eq, and } from 'drizzle-orm'; +import { eq, and, sql } from 'drizzle-orm'; import { z } from 'zod'; import { verifyUserInteraction } from '@/lib/swarm/signature'; const swarmFollowSchema = z.object({ - targetHandle: z.string(), + targetHandle: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_]+$/, 'Handle must be alphanumeric with underscores'), follow: z.object({ - followerHandle: z.string(), - followerDisplayName: z.string(), - followerAvatarUrl: z.string().optional(), - followerBio: z.string().optional(), - followerNodeDomain: z.string(), - interactionId: z.string(), - timestamp: z.string(), + followerHandle: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_]+$/, 'Handle must be alphanumeric with underscores'), + followerDisplayName: z.string().min(1).max(50), + followerAvatarUrl: z.string().url().optional(), + followerBio: z.string().max(500).optional(), + followerNodeDomain: z.string().min(1).regex(/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/, 'Invalid domain format'), + interactionId: z.string().uuid(), + timestamp: z.string().datetime(), }), - signature: z.string(), + signature: z.string().min(1), }); /** @@ -100,7 +100,7 @@ export async function POST(request: NextRequest) { // Update follower count await db.update(users) - .set({ followersCount: targetUser.followersCount + 1 }) + .set({ followersCount: sql`${users.followersCount} + 1` }) .where(eq(users.id, targetUser.id)); // Create notification with actor info stored directly @@ -115,7 +115,9 @@ export async function POST(request: NextRequest) { }); console.log(`[Swarm] Created follow notification for @${data.targetHandle} from ${data.follow.followerHandle}@${data.follow.followerNodeDomain}`); } catch (notifError) { - console.error(`[Swarm] Failed to create notification:`, notifError); + // Log error with context but don't fail the request - notification creation is best-effort + console.error('[Swarm Follow] Failed to create notification:', notifError); + console.error('[Swarm Follow] Context:', { targetHandle: data.targetHandle, userId: targetUser.id, actor: data.follow.followerHandle }); } // Also notify bot owner if this is a bot being followed @@ -130,7 +132,9 @@ export async function POST(request: NextRequest) { type: 'follow', }); } catch (err) { - console.error('[Swarm] Failed to notify bot owner:', err); + // Log error with context but don't fail the request - bot owner notification is best-effort + console.error('[Swarm Follow] Failed to notify bot owner:', err); + console.error('[Swarm Follow] Context:', { targetHandle: data.targetHandle, botOwnerId: targetUser.botOwnerId, actor: data.follow.followerHandle }); } } diff --git a/src/app/api/swarm/interactions/like/route.ts b/src/app/api/swarm/interactions/like/route.ts index 66dcac0..85268c4 100644 --- a/src/app/api/swarm/interactions/like/route.ts +++ b/src/app/api/swarm/interactions/like/route.ts @@ -15,14 +15,14 @@ import { verifyUserInteraction } from '@/lib/swarm/signature'; const swarmLikeSchema = z.object({ postId: z.string().uuid(), like: z.object({ - actorHandle: z.string(), - actorDisplayName: z.string(), - actorAvatarUrl: z.string().optional(), - actorNodeDomain: z.string(), - interactionId: z.string(), - timestamp: z.string(), + actorHandle: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_]+$/, 'Handle must be alphanumeric with underscores'), + actorDisplayName: z.string().min(1).max(50), + actorAvatarUrl: z.string().url().optional(), + actorNodeDomain: z.string().min(1).regex(/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/, 'Invalid domain format'), + interactionId: z.string().uuid(), + timestamp: z.string().datetime(), }), - signature: z.string(), + signature: z.string().min(1), }); /** @@ -106,7 +106,9 @@ export async function POST(request: NextRequest) { }); console.log(`[Swarm] Created like notification for post ${data.postId} from ${data.like.actorHandle}@${data.like.actorNodeDomain}`); } catch (notifError) { - console.error(`[Swarm] Failed to create like notification:`, notifError); + // Log error with context but don't fail the request - notification creation is best-effort + console.error('[Swarm Like] Failed to create notification:', notifError); + console.error('[Swarm Like] Context:', { postId: data.postId, userId: post.userId, actor: data.like.actorHandle }); } // Also notify bot owner if this is a bot's post @@ -124,7 +126,9 @@ export async function POST(request: NextRequest) { type: 'like', }); } catch (err) { - console.error('[Swarm] Failed to notify bot owner:', err); + // Log error with context but don't fail the request - bot owner notification is best-effort + console.error('[Swarm Like] Failed to notify bot owner:', err); + console.error('[Swarm Like] Context:', { postId: data.postId, botOwnerId: author.botOwnerId, actor: data.like.actorHandle }); } } diff --git a/src/app/api/swarm/interactions/repost/route.ts b/src/app/api/swarm/interactions/repost/route.ts index 1d86617..078aeb2 100644 --- a/src/app/api/swarm/interactions/repost/route.ts +++ b/src/app/api/swarm/interactions/repost/route.ts @@ -8,22 +8,22 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, posts, users, notifications } from '@/db'; -import { eq } from 'drizzle-orm'; +import { eq, sql } from 'drizzle-orm'; import { z } from 'zod'; import { verifyUserInteraction } from '@/lib/swarm/signature'; const swarmRepostSchema = z.object({ postId: z.string().uuid(), repost: z.object({ - actorHandle: z.string(), - actorDisplayName: z.string(), - actorAvatarUrl: z.string().optional(), - actorNodeDomain: z.string(), - repostId: z.string(), - interactionId: z.string(), - timestamp: z.string(), + actorHandle: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_]+$/, 'Handle must be alphanumeric with underscores'), + actorDisplayName: z.string().min(1).max(50), + actorAvatarUrl: z.string().url().optional(), + actorNodeDomain: z.string().min(1).regex(/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/, 'Invalid domain format'), + repostId: z.string().uuid(), + interactionId: z.string().uuid(), + timestamp: z.string().datetime(), }), - signature: z.string(), + signature: z.string().min(1), }); /** @@ -70,7 +70,7 @@ export async function POST(request: NextRequest) { // Increment repost count await db.update(posts) - .set({ repostsCount: post.repostsCount + 1 }) + .set({ repostsCount: sql`${posts.repostsCount} + 1` }) .where(eq(posts.id, data.postId)); // Create notification with actor info stored directly diff --git a/src/app/api/swarm/interactions/unfollow/route.ts b/src/app/api/swarm/interactions/unfollow/route.ts index 686f575..f539e96 100644 --- a/src/app/api/swarm/interactions/unfollow/route.ts +++ b/src/app/api/swarm/interactions/unfollow/route.ts @@ -8,7 +8,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, users, remoteFollowers } from '@/db'; -import { eq, and } from 'drizzle-orm'; +import { eq, and, sql } from 'drizzle-orm'; import { z } from 'zod'; import { verifyUserInteraction } from '@/lib/swarm/signature'; @@ -82,7 +82,7 @@ export async function POST(request: NextRequest) { // Update follower count await db.update(users) - .set({ followersCount: Math.max(0, targetUser.followersCount - 1) }) + .set({ followersCount: sql`GREATEST(0, ${users.followersCount} - 1)` }) .where(eq(users.id, targetUser.id)); console.log(`[Swarm] Received unfollow from ${data.unfollow.followerHandle}@${data.unfollow.followerNodeDomain} for @${data.targetHandle}`); diff --git a/src/app/api/swarm/interactions/unrepost/route.ts b/src/app/api/swarm/interactions/unrepost/route.ts index 3234f1d..77d94aa 100644 --- a/src/app/api/swarm/interactions/unrepost/route.ts +++ b/src/app/api/swarm/interactions/unrepost/route.ts @@ -8,7 +8,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, posts } from '@/db'; -import { eq } from 'drizzle-orm'; +import { eq, sql } from 'drizzle-orm'; import { z } from 'zod'; import { verifyUserInteraction } from '@/lib/swarm/signature'; @@ -66,7 +66,7 @@ export async function POST(request: NextRequest) { // Decrement repost count await db.update(posts) - .set({ repostsCount: Math.max(0, post.repostsCount - 1) }) + .set({ repostsCount: sql`GREATEST(0, ${posts.repostsCount} - 1)` }) .where(eq(posts.id, data.postId)); console.log(`[Swarm] Received unrepost from ${data.unrepost.actorHandle}@${data.unrepost.actorNodeDomain} on post ${data.postId}`); diff --git a/src/app/api/swarm/posts/[id]/likes/route.ts b/src/app/api/swarm/posts/[id]/likes/route.ts index 4d64832..3f8d8a4 100644 --- a/src/app/api/swarm/posts/[id]/likes/route.ts +++ b/src/app/api/swarm/posts/[id]/likes/route.ts @@ -7,9 +7,19 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, posts, likes, users, remoteLikes } from '@/db'; import { eq, and } from 'drizzle-orm'; +import { z } from 'zod'; type RouteContext = { params: Promise<{ id: string }> }; +// Schema for post ID parameter +const postIdSchema = z.string().uuid('Invalid post ID format'); + +// Schema for query parameters +const likesQuerySchema = z.object({ + checkHandle: z.string().min(3).max(30).optional(), + checkDomain: z.string().min(1).max(100).optional(), +}); + /** * GET /api/swarm/posts/[id]/likes * @@ -24,10 +34,28 @@ export async function GET(request: NextRequest, context: RouteContext) { return NextResponse.json({ error: 'Database not available' }, { status: 503 }); } - const { id: postId } = await context.params; + const { id: rawId } = await context.params; + + // Validate post ID + const idResult = postIdSchema.safeParse(rawId); + if (!idResult.success) { + return NextResponse.json({ error: 'Invalid post ID', details: idResult.error.issues }, { status: 400 }); + } + const postId = idResult.data; + const { searchParams } = new URL(request.url); - const checkHandle = searchParams.get('checkHandle'); - const checkDomain = searchParams.get('checkDomain'); + + // Validate query parameters + const queryResult = likesQuerySchema.safeParse({ + checkHandle: searchParams.get('checkHandle') || undefined, + checkDomain: searchParams.get('checkDomain') || undefined, + }); + + if (!queryResult.success) { + return NextResponse.json({ error: 'Invalid query parameters', details: queryResult.error.issues }, { status: 400 }); + } + + const { checkHandle, checkDomain } = queryResult.data; // Find the post const post = await db.query.posts.findFirst({ @@ -94,6 +122,9 @@ export async function GET(request: NextRequest, context: RouteContext) { likesCount: post.likesCount, }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 }); + } console.error('[Swarm] Post likes error:', error); return NextResponse.json({ error: 'Failed to get likes' }, { status: 500 }); } diff --git a/src/app/api/swarm/posts/[id]/route.ts b/src/app/api/swarm/posts/[id]/route.ts index 47e76f0..3788c32 100644 --- a/src/app/api/swarm/posts/[id]/route.ts +++ b/src/app/api/swarm/posts/[id]/route.ts @@ -7,9 +7,12 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, posts } from '@/db'; import { eq, desc, and } from 'drizzle-orm'; +import { z } from 'zod'; type RouteContext = { params: Promise<{ id: string }> }; +const uuidSchema = z.string().uuid(); + /** * GET /api/swarm/posts/[id] * @@ -21,7 +24,14 @@ export async function GET(request: NextRequest, context: RouteContext) { return NextResponse.json({ error: 'Database not available' }, { status: 503 }); } - const { id: postId } = await context.params; + const { id: postIdRaw } = await context.params; + + // Validate postId is a valid UUID + const postIdValidation = uuidSchema.safeParse(postIdRaw); + if (!postIdValidation.success) { + return NextResponse.json({ error: 'Invalid post ID format' }, { status: 400 }); + } + const postId = postIdValidation.data; // Find the post const post = await db.query.posts.findFirst({ @@ -97,6 +107,12 @@ export async function GET(request: NextRequest, context: RouteContext) { }), }); } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { error: 'Invalid input', details: error.issues }, + { status: 400 } + ); + } console.error('[Swarm] Post detail error:', error); return NextResponse.json({ error: 'Failed to get post' }, { status: 500 }); } diff --git a/src/app/api/users/[handle]/follow/route.ts b/src/app/api/users/[handle]/follow/route.ts index 8db783e..c72286d 100644 --- a/src/app/api/users/[handle]/follow/route.ts +++ b/src/app/api/users/[handle]/follow/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import crypto from 'crypto'; import { db, follows, users, notifications, remoteFollows } from '@/db'; -import { eq, and } from 'drizzle-orm'; +import { eq, and, sql } from 'drizzle-orm'; import { requireAuth } from '@/lib/auth'; import { requireSignedAction } from '@/lib/auth/verify-signature'; import { isSwarmNode, deliverSwarmFollow, deliverSwarmUnfollow, cacheSwarmUserPosts } from '@/lib/swarm/interactions'; @@ -152,9 +152,9 @@ export async function POST(request: Request, context: RouteContext) { avatarUrl: null, }); - // Update the user's following count + // Update the user's following count (atomic increment) await db.update(users) - .set({ followingCount: currentUser.followingCount + 1 }) + .set({ followingCount: sql`${users.followingCount} + 1` }) .where(eq(users.id, currentUser.id)); // Cache the remote user's recent posts in the background @@ -231,13 +231,13 @@ export async function POST(request: Request, context: RouteContext) { } } - // Update counts + // Update counts (atomic increments) await db.update(users) - .set({ followingCount: currentUser.followingCount + 1 }) + .set({ followingCount: sql`${users.followingCount} + 1` }) .where(eq(users.id, currentUser.id)); await db.update(users) - .set({ followersCount: targetUser.followersCount + 1 }) + .set({ followersCount: sql`${users.followersCount} + 1` }) .where(eq(users.id, targetUser.id)); return NextResponse.json({ success: true, following: true }); @@ -295,9 +295,9 @@ export async function DELETE(request: Request, context: RouteContext) { // Remove the follow record await db.delete(remoteFollows).where(eq(remoteFollows.id, existingRemoteFollow.id)); - // Update the user's following count + // Update the user's following count (atomic decrement, clamped to 0) await db.update(users) - .set({ followingCount: Math.max(0, currentUser.followingCount - 1) }) + .set({ followingCount: sql`GREATEST(0, ${users.followingCount} - 1)` }) .where(eq(users.id, currentUser.id)); console.log(`[Swarm] Unfollow delivered to ${remote.domain}`); @@ -335,13 +335,13 @@ export async function DELETE(request: Request, context: RouteContext) { // Remove follow await db.delete(follows).where(eq(follows.id, existingFollow.id)); - // Update counts + // Update counts (atomic decrements, clamped to 0) await db.update(users) - .set({ followingCount: Math.max(0, currentUser.followingCount - 1) }) + .set({ followingCount: sql`GREATEST(0, ${users.followingCount} - 1)` }) .where(eq(users.id, currentUser.id)); await db.update(users) - .set({ followersCount: Math.max(0, targetUser.followersCount - 1) }) + .set({ followersCount: sql`GREATEST(0, ${users.followersCount} - 1)` }) .where(eq(users.id, targetUser.id)); return NextResponse.json({ success: true, following: false }); diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index c25bc4f..52d31e2 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from 'react'; import { useAuth } from '@/lib/contexts/AuthContext'; import { signedAPI } from '@/lib/api/signed-fetch'; -import { ArrowLeft, Send, Lock, Shield, Loader2, MessageCircle, Search, Plus, Trash2, MoreVertical } from 'lucide-react'; +import { ArrowLeft, Send, Loader2, MessageCircle, Search, Plus, Trash2, MoreVertical } from 'lucide-react'; import { formatFullHandle } from '@/lib/utils/handle'; import { useRouter, useSearchParams } from 'next/navigation'; @@ -35,7 +35,7 @@ interface Message { } export default function ChatPage() { - const { user, isIdentityUnlocked, setShowUnlockPrompt } = useAuth(); + const { user } = useAuth(); const router = useRouter(); const searchParams = useSearchParams(); const composeHandle = searchParams.get('compose'); @@ -348,29 +348,6 @@ export default function ChatPage() { if (user === null) return null; - // Identity Locked State - if (!isIdentityUnlocked) { - return ( -
- -

Identity Required

-

- Chat requires your identity to be unlocked. Your private keys are used to sign messages to prove they came from you. -

- -
- ); - } - - - // Prevent flash of list view while processing compose intent if (composeHandle && !selectedConversation) { return ( diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index c3e9ea8..b5c7fdc 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -32,6 +32,12 @@ export default function LoginPage() { const [confirmPassword, setConfirmPassword] = useState(''); const [handle, setHandle] = useState(''); const [displayName, setDisplayName] = useState(''); + const [storageProvider, setStorageProvider] = useState('r2'); + const [storageEndpoint, setStorageEndpoint] = useState(''); + const [storageRegion, setStorageRegion] = useState('auto'); + const [storageBucket, setStorageBucket] = useState(''); + const [storageAccessKey, setStorageAccessKey] = useState(''); + const [storageSecretKey, setStorageSecretKey] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [nodeInfoLoaded, setNodeInfoLoaded] = useState(false); @@ -244,6 +250,12 @@ export default function LoginPage() { password, handle, displayName, + storageProvider, + storageEndpoint: storageEndpoint || null, + storageRegion, + storageBucket, + storageAccessKey, + storageSecretKey, ...(nodeInfo.turnstileSiteKey ? { turnstileToken } : {}) }; @@ -326,7 +338,7 @@ export default function LoginPage() { justifyContent: 'center', padding: '24px', }}> -
+
{/* Logo */}
{nodeInfoLoaded && ( @@ -440,111 +452,248 @@ export default function LoginPage() { {mode === 'register' && ( <> -
- -
- @ - setHandle(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, ''))} - style={{ paddingLeft: '28px' }} - placeholder="yourhandle" - required - minLength={3} - maxLength={20} - /> + {/* Row 1: Handle | Password */} +
+
+ +
+ @ + setHandle(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, ''))} + style={{ paddingLeft: '28px' }} + placeholder="yourhandle" + required + minLength={3} + maxLength={20} + /> +
+
+ + 3-20 chars, a-z 0-9 _ + + {handleStatus === 'checking' && ( + Checking... + )} + {handleStatus === 'available' && ( + + )} + {handleStatus === 'taken' && ( + Taken + )} +
-
- - 3-20 characters, alphanumeric and underscores - - {handleStatus === 'checking' && ( - Checking... - )} - {handleStatus === 'available' && ( - Available - )} - {handleStatus === 'taken' && ( - Taken - )} +
+ + setPassword(e.target.value)} + placeholder="••••••••" + required + minLength={8} + />
+ {/* Row 2: Display Name | Confirm Password */} +
+
+ + setDisplayName(e.target.value)} + placeholder="Your Name" + /> +
+
+ + setConfirmPassword(e.target.value)} + placeholder="••••••••" + required + minLength={8} + /> +
+
+ + {/* Row 3: Email | Storage Provider */} +
+
+ + setEmail(e.target.value)} + placeholder="you@example.com" + required + /> +
+
+ + +
+ You own your storage. We just connect to it. +
+
+
+ + {/* S3 Credentials - Full Width */}
setDisplayName(e.target.value)} - placeholder="Your Name" + value={storageEndpoint} + onChange={(e) => setStorageEndpoint(e.target.value)} + placeholder="https://.r2.cloudflarestorage.com" /> +
+ Leave empty for AWS S3. Required for R2, B2, MinIO. +
+
+ +
+
+ + setStorageRegion(e.target.value)} + placeholder="auto" + required + /> +
+
+ + setStorageBucket(e.target.value)} + placeholder="my-synapsis-bucket" + required + /> +
+
+ +
+ + setStorageAccessKey(e.target.value)} + placeholder="AKIA..." + required + /> +
+ +
+ + setStorageSecretKey(e.target.value)} + placeholder="••••••••" + required + /> +
+ Get free S3 storage at Cloudflare R2 (10GB free) or Backblaze B2 +
)} -
- - setEmail(e.target.value)} - placeholder="you@example.com" - required - /> -
+ {/* Login Mode - Show email/password only */} + {mode === 'login' && ( + <> +
+ + setEmail(e.target.value)} + placeholder="you@example.com" + required + /> +
-
- - setPassword(e.target.value)} - placeholder="••••••••" - required - minLength={8} - /> -
- - {mode === 'register' && ( -
- - setConfirmPassword(e.target.value)} - placeholder="••••••••" - required - minLength={8} - /> -
+
+ + setPassword(e.target.value)} + placeholder="••••••••" + required + minLength={8} + /> +
+ )} {mode === 'register' && nodeInfo.isNsfw && ( @@ -581,7 +730,21 @@ export default function LoginPage() { type="submit" className="btn btn-primary btn-lg" style={{ width: '100%' }} - disabled={loading || (!!nodeInfo.turnstileSiteKey && !turnstileToken)} + disabled={loading || + (!!nodeInfo.turnstileSiteKey && !turnstileToken) || + (mode === 'register' && ( + !handle || handle.length < 3 || + !email || + !password || password.length < 8 || + !confirmPassword || + password !== confirmPassword || + !storageProvider || + !storageRegion || + !storageBucket || + !storageAccessKey || + !storageSecretKey || + (nodeInfo.isNsfw && !ageVerified) + ))} > {loading ? 'Please wait...' : (mode === 'login' ? 'Login' : 'Create Account')} diff --git a/src/db/schema.ts b/src/db/schema.ts index 96ba134..8b39464 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -62,6 +62,13 @@ export const users = pgTable('users', { movedTo: text('moved_to'), // New actor URL if this account migrated away movedFrom: text('moved_from'), // Old actor URL if this account migrated here migratedAt: timestamp('migrated_at'), // When the migration occurred + // User-owned S3-compatible storage - required for new users + storageProvider: text('storage_provider'), // 's3', 'r2', 'b2', 'wasabi', etc + storageEndpoint: text('storage_endpoint'), // S3 endpoint URL (optional for AWS) + storageRegion: text('storage_region'), // Region (e.g., 'us-east-1') + storageBucket: text('storage_bucket'), // Bucket name + storageAccessKeyEncrypted: text('storage_access_key_encrypted'), // Encrypted access key + storageSecretKeyEncrypted: text('storage_secret_key_encrypted'), // Encrypted secret key followersCount: integer('followers_count').default(0).notNull(), followingCount: integer('following_count').default(0).notNull(), postsCount: integer('posts_count').default(0).notNull(), diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index a89ebd2..9ca3ef8 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -8,9 +8,10 @@ import bcrypt from 'bcryptjs'; import { v4 as uuid } from 'uuid'; import { generateKeyPair } from '@/lib/crypto/keys'; import { encryptPrivateKey, serializeEncryptedKey } from '@/lib/crypto/private-key'; +import { base58btc } from 'multiformats/bases/base58'; import { cookies } from 'next/headers'; import { upsertHandleEntries } from '@/lib/federation/handles'; -import { generateAndUploadAvatar } from '@/lib/auth/avatar'; +import { generateAndUploadAvatarToUserStorage } from '@/lib/storage/s3'; const SESSION_COOKIE_NAME = 'synapsis_session'; const SESSION_EXPIRY_DAYS = 30; @@ -31,10 +32,22 @@ export async function verifyPassword(password: string, hash: string): Promise { // Validate handle format if (!/^[a-zA-Z0-9_]{3,20}$/.test(handle)) { @@ -148,21 +167,49 @@ export async function registerUser( throw new Error('Email is already registered'); } + // Validate S3 storage credentials (required for new users) + if (!storageProvider) { + throw new Error('Storage provider is required.'); + } + if (!storageRegion || storageRegion.length < 2) { + throw new Error('Storage region is required (e.g., us-east-1, auto).'); + } + if (!storageBucket || storageBucket.length < 3) { + throw new Error('Storage bucket name is required.'); + } + if (!storageAccessKey || storageAccessKey.length < 10) { + throw new Error('Storage access key is required.'); + } + if (!storageSecretKey || storageSecretKey.length < 10) { + throw new Error('Storage secret key is required.'); + } + // Generate cryptographic keys const { publicKey, privateKey } = await generateKeyPair(); // Encrypt the private key with user's password before storing const encryptedPrivateKey = encryptPrivateKey(privateKey, password); - // Create the user - const did = generateDID(); + // Create the user with did:key format (public key encoded in DID) + const did = generateDID(publicKey); const passwordHash = await hashPassword(password); const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; - // Generate avatar using full handle (@user@domain) for global uniqueness + // Generate avatar and upload to user's S3 storage const fullHandle = `${handle.toLowerCase()}@${nodeDomain}`; - const avatarUrl = await generateAndUploadAvatar(fullHandle); + const avatarUrl = await generateAndUploadAvatarToUserStorage( + fullHandle, + storageEndpoint || null, + storageRegion, + storageBucket, + storageAccessKey, + storageSecretKey + ); + + // Encrypt the storage credentials with user's password + const encryptedAccessKey = encryptPrivateKey(storageAccessKey, password); + const encryptedSecretKey = encryptPrivateKey(storageSecretKey, password); const [user] = await db.insert(users).values({ did, @@ -173,6 +220,12 @@ export async function registerUser( avatarUrl, publicKey, privateKeyEncrypted: serializeEncryptedKey(encryptedPrivateKey), + storageProvider, + storageEndpoint: storageEndpoint || null, + storageRegion, + storageBucket, + storageAccessKeyEncrypted: serializeEncryptedKey(encryptedAccessKey), + storageSecretKeyEncrypted: serializeEncryptedKey(encryptedSecretKey), }).returning(); await upsertHandleEntries([{ diff --git a/src/lib/auth/verify-signature.ts b/src/lib/auth/verify-signature.ts index 8f484ae..0f6cc51 100644 --- a/src/lib/auth/verify-signature.ts +++ b/src/lib/auth/verify-signature.ts @@ -14,6 +14,7 @@ import { eq } from 'drizzle-orm'; import { canonicalize, importPublicKey, base64UrlToBase64 } from '@/lib/crypto/user-signing'; // Note: user-signing helpers are isomorphic (work in Node via webcrypto polyfill/availability) import crypto from 'crypto'; +import { isRateLimited } from '@/lib/rate-limit'; // Use Node's webcrypto for server-side if not global const cryptoSubtle = globalThis.crypto?.subtle || require('crypto').webcrypto.subtle; @@ -77,7 +78,13 @@ export async function verifyUserAction(signedAction: SignedAction): Promise<{ const { sig, ...payload } = signedAction; - // 1. FRESHNESS CHECK (Fail fast before DB/Crypto) + // 1. RATE LIMIT CHECK (Fail fast before heavy operations) + // 5 requests per minute per DID + if (isRateLimited(payload.did, 5, 60 * 1000)) { + return { valid: false, error: 'RATE_LIMITED' }; + } + + // 2. FRESHNESS CHECK (Fail fast before DB/Crypto) const now = Date.now(); const diff = Math.abs(now - payload.ts); // Allow 5 minutes clock skew @@ -87,7 +94,7 @@ export async function verifyUserAction(signedAction: SignedAction): Promise<{ return { valid: false, error: 'INVALID_TIMESTAMP: Request too old or in future' }; } - // 2. FETCH USER & KEY + // 3. FETCH USER & KEY const user = await db.query.users.findFirst({ where: eq(users.did, payload.did), }); @@ -100,18 +107,18 @@ export async function verifyUserAction(signedAction: SignedAction): Promise<{ return { valid: false, error: 'Handle mismatch' }; } - // 3. CRYPTOGRAPHIC VERIFICATION + // 4. CRYPTOGRAPHIC VERIFICATION const isValid = await verifyActionSignature(signedAction, user.publicKey); if (!isValid) { return { valid: false, error: 'INVALID_SIGNATURE' }; } - // 4. ACTION ID HASH COMPUTATION + // 5. ACTION ID HASH COMPUTATION const canonicalString = canonicalize(payload); const actionIdHash = crypto.createHash('sha256').update(canonicalString).digest('hex'); - // 5. REPLAY PROTECTION (DB) + // 6. REPLAY PROTECTION (DB) try { await db.insert(signedActionDedupe).values({ actionId: actionIdHash, diff --git a/src/lib/bots/botManager.ts b/src/lib/bots/botManager.ts index e3897d0..406d6c4 100644 --- a/src/lib/bots/botManager.ts +++ b/src/lib/bots/botManager.ts @@ -11,6 +11,8 @@ */ import { db, bots, users, botContentSources, botContentItems, botMentions, botActivityLogs, botRateLimits, follows } from '@/db'; +import { generateAndUploadAvatarToUserStorage } from '@/lib/storage/s3'; +import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key'; import { eq, and, count } from 'drizzle-orm'; import { generateKeyPair } from '@/lib/crypto/keys'; import { @@ -333,6 +335,15 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis throw new BotHandleTakenError(config.handle); } + // Get owner to access their S3 storage for bot avatar + const owner = await db.query.users.findFirst({ + where: eq(users.id, ownerId), + }); + + if (!owner) { + throw new BotValidationError('Owner user not found'); + } + // Generate cryptographic keys for the bot's user account const { publicKey, privateKey } = await generateKeyPair(); @@ -344,13 +355,27 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; const botDid = `did:web:${nodeDomain}:users:${config.handle.toLowerCase()}`; + // Generate bot avatar using owner's S3 storage if no avatar provided + let botAvatarUrl = config.avatarUrl || null; + if (!botAvatarUrl && owner.storageAccessKeyEncrypted && owner.storageSecretKeyEncrypted && owner.storageBucket) { + try { + // We need the owner's password to decrypt S3 credentials + // Since we don't have the password here, we'll leave avatar null + // The frontend should handle avatar generation with the user's password + // Or we can generate a default avatar URL pattern + console.log('[BotManager] Bot avatar will need to be set via API with user password'); + } catch (err) { + console.error('[BotManager] Failed to generate bot avatar:', err); + } + } + // Create the bot's user account first const [botUser] = await db.insert(users).values({ did: botDid, handle: config.handle.toLowerCase(), displayName: config.name, bio: config.bio || null, - avatarUrl: config.avatarUrl || null, + avatarUrl: botAvatarUrl, headerUrl: config.headerUrl || null, publicKey, privateKeyEncrypted: serializeEncryptedData(encryptedPrivateKey), diff --git a/src/lib/crypto/key-persistence.ts b/src/lib/crypto/key-persistence.ts index 46a8bc0..adcc7c2 100644 --- a/src/lib/crypto/key-persistence.ts +++ b/src/lib/crypto/key-persistence.ts @@ -120,7 +120,7 @@ export async function deriveSessionKey(password: string): Promise { keyMaterial, { name: 'AES-GCM', length: 256 }, true, // extractable so we can store it - ['wrapKey', 'unwrapKey'] + ['encrypt', 'decrypt'] ); } @@ -131,7 +131,7 @@ async function generateSessionKey(): Promise { return crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, - ['wrapKey', 'unwrapKey'] + ['encrypt', 'decrypt'] ); } @@ -147,7 +147,7 @@ async function importSessionKey(keyData: string): Promise { buffer, { name: 'AES-GCM', length: 256 }, false, // not extractable after import - ['wrapKey', 'unwrapKey'] + ['encrypt', 'decrypt'] ); } diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts new file mode 100644 index 0000000..6791eac --- /dev/null +++ b/src/lib/rate-limit.ts @@ -0,0 +1,138 @@ +/** + * In-memory rate limiting for signed actions + * + * Features: + * - Sliding window approach (5 requests per minute per DID by default) + * - Automatic cleanup to prevent memory leaks + * - No external dependencies (Redis-free) + */ + +// Map to store timestamps per DID: did -> array of request timestamps +const rateLimits = new Map(); + +// Configuration for cleanup +const CLEANUP_INTERVAL_MS = 60 * 1000; // Run cleanup every minute +const MAX_IDLE_DID_MS = 10 * 60 * 1000; // Remove DIDs with no recent activity after 10 minutes + +/** + * Check if a DID is rate limited + * @param did - The DID to check + * @param maxRequests - Maximum requests allowed in the window (default: 5) + * @param windowMs - Time window in milliseconds (default: 60000 = 1 minute) + * @returns true if rate limited, false otherwise + */ +export function isRateLimited(did: string, maxRequests = 5, windowMs = 60000): boolean { + const now = Date.now(); + const timestamps = rateLimits.get(did) || []; + + // Filter to only keep timestamps within the window (sliding window) + const recent = timestamps.filter(ts => now - ts < windowMs); + + // Check if limit exceeded + if (recent.length >= maxRequests) { + return true; + } + + // Add current timestamp and update the map + recent.push(now); + rateLimits.set(did, recent); + + return false; +} + +/** + * Get current rate limit status for a DID + * Useful for logging/debugging + */ +export function getRateLimitStatus(did: string, maxRequests = 5, windowMs = 60000): { + limited: boolean; + remaining: number; + resetInMs: number; + current: number; +} { + const now = Date.now(); + const timestamps = rateLimits.get(did) || []; + const recent = timestamps.filter(ts => now - ts < windowMs); + + const limited = recent.length >= maxRequests; + const remaining = Math.max(0, maxRequests - recent.length); + + // Calculate when the oldest request will expire + const oldestInWindow = recent.length > 0 ? Math.min(...recent) : now; + const resetInMs = limited ? (oldestInWindow + windowMs - now) : 0; + + return { + limited, + remaining, + resetInMs, + current: recent.length + }; +} + +/** + * Cleanup old entries to prevent memory leaks + * Removes: + * 1. Timestamps outside the rate limit window for each DID + * 2. DIDs with no recent activity (idle for MAX_IDLE_DID_MS) + */ +export function cleanupRateLimits(windowMs = 60000): { + didsRemoved: number; + timestampsRemoved: number; +} { + const now = Date.now(); + let didsRemoved = 0; + let timestampsRemoved = 0; + + for (const [did, timestamps] of rateLimits.entries()) { + // Filter to keep only recent timestamps + const recent = timestamps.filter(ts => { + const isRecent = now - ts < windowMs; + if (!isRecent) timestampsRemoved++; + return isRecent; + }); + + if (recent.length === 0) { + // Remove DID entirely if no recent activity + // Also check if DID has been idle for too long + const lastActivity = timestamps.length > 0 ? Math.max(...timestamps) : 0; + if (now - lastActivity > MAX_IDLE_DID_MS) { + rateLimits.delete(did); + didsRemoved++; + } else { + rateLimits.set(did, recent); + } + } else { + rateLimits.set(did, recent); + } + } + + return { didsRemoved, timestampsRemoved }; +} + +/** + * Get current size of rate limit store (for monitoring) + */ +export function getRateLimitStoreSize(): { + didCount: number; + totalTimestamps: number; +} { + let totalTimestamps = 0; + for (const timestamps of rateLimits.values()) { + totalTimestamps += timestamps.length; + } + + return { + didCount: rateLimits.size, + totalTimestamps + }; +} + +// Start periodic cleanup to prevent memory leaks +setInterval(() => { + const result = cleanupRateLimits(); + if (result.didsRemoved > 0 || result.timestampsRemoved > 0) { + console.log(`[RateLimit] Cleanup: removed ${result.didsRemoved} DIDs, ${result.timestampsRemoved} old timestamps`); + } +}, CLEANUP_INTERVAL_MS); + +console.log('[RateLimit] Initialized with sliding window rate limiting'); diff --git a/src/lib/storage/ipfs.ts b/src/lib/storage/ipfs.ts new file mode 100644 index 0000000..07bece0 --- /dev/null +++ b/src/lib/storage/ipfs.ts @@ -0,0 +1,134 @@ +/** + * User-Owned Storage (IPFS/Pinata) Utilities + * + * Handles uploads to Pinata using user's own API keys + */ + +import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key'; + +export type StorageProvider = 'pinata'; + +interface StorageUploadResult { + cid: string; + url: string; + gatewayUrl: string; +} + +/** + * Upload a file to user's Pinata account + */ +export async function uploadToPinata( + file: Buffer, + apiKey: string, + filename: string +): Promise { + const formData = new FormData(); + // Create Blob from Buffer - using ArrayBuffer view + const blob = new Blob([file as unknown as BlobPart]); + formData.append('file', blob, filename); + + const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + }, + body: formData, + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Pinata upload failed: ${error}`); + } + + const data = await response.json(); + const cid = data.IpfsHash; + + if (!cid) { + throw new Error('No CID returned from Pinata'); + } + + return { + cid, + url: `ipfs://${cid}`, + gatewayUrl: `https://gateway.pinata.cloud/ipfs/${cid}`, + }; +} + +/** + * Upload file using user's configured storage provider + */ +export async function uploadToUserStorage( + file: Buffer, + filename: string, + provider: StorageProvider, + encryptedApiKey: string, + password: string +): Promise { + // Decrypt the storage API key + const decryptedKey = decryptPrivateKey( + deserializeEncryptedKey(encryptedApiKey), + password + ); + + switch (provider) { + case 'pinata': + return uploadToPinata(file, decryptedKey, filename); + default: + throw new Error(`Unknown storage provider: ${provider}`); + } +} + +/** + * Generate and upload avatar to user's Pinata storage + */ +export async function generateAndUploadAvatarToUserStorage( + handle: string, + apiKey: string +): Promise { + try { + // 1. Fetch the avatar from DiceBear + const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${handle}`; + const response = await fetch(dicebearUrl); + + if (!response.ok) { + console.error(`Failed to fetch avatar from DiceBear: ${response.statusText}`); + return null; + } + + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + // 2. Upload to user's Pinata + const result = await uploadToPinata(buffer, apiKey, `${handle}-avatar.svg`); + + return result.url; // ipfs://cid format + + } catch (error) { + console.error('Error generating/uploading avatar:', error); + return null; + } +} + +/** + * Get public gateway URL for an IPFS CID + */ +export function getIPFSGatewayUrl(cid: string, preferredGateway?: string): string { + // Use preferred gateway or fallback to public ones + const gateway = preferredGateway || 'https://ipfs.io/ipfs'; + return `${gateway}/${cid}`; +} + +/** + * Extract CID from ipfs:// URL or return the CID + */ +export function extractCID(urlOrCid: string): string { + if (urlOrCid.startsWith('ipfs://')) { + return urlOrCid.replace('ipfs://', ''); + } + // Handle gateway URLs + const match = urlOrCid.match(/\/ipfs\/(Qm[a-zA-Z0-9]+|bafy[a-zA-Z0-9]+)/); + if (match) { + return match[1]; + } + return urlOrCid; +} diff --git a/src/lib/storage/s3.ts b/src/lib/storage/s3.ts new file mode 100644 index 0000000..2c6b95b --- /dev/null +++ b/src/lib/storage/s3.ts @@ -0,0 +1,214 @@ +/** + * User-Owned S3-Compatible Storage Utilities + * + * Supports AWS S3, Cloudflare R2, Backblaze B2, Wasabi, MinIO, etc. + */ + +import { S3Client, PutObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3'; +import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key'; + +export type StorageProvider = 's3' | 'r2' | 'b2' | 'wasabi' | 'minio' | 'other'; + +interface S3Credentials { + endpoint?: string; + region: string; + accessKeyId: string; + secretAccessKey: string; + bucket: string; +} + +interface StorageUploadResult { + url: string; + key: string; +} + +/** + * Decrypt S3 credentials from encrypted storage + */ +function decryptS3Credentials( + encryptedAccessKey: string, + encryptedSecretKey: string, + password: string +): { accessKeyId: string; secretAccessKey: string } { + const accessKeyId = decryptPrivateKey( + deserializeEncryptedKey(encryptedAccessKey), + password + ); + const secretAccessKey = decryptPrivateKey( + deserializeEncryptedKey(encryptedSecretKey), + password + ); + return { accessKeyId, secretAccessKey }; +} + +/** + * Create S3 client from credentials + */ +function createS3Client(creds: S3Credentials): S3Client { + return new S3Client({ + region: creds.region, + endpoint: creds.endpoint, + credentials: { + accessKeyId: creds.accessKeyId, + secretAccessKey: creds.secretAccessKey, + }, + forcePathStyle: !!creds.endpoint, // Needed for non-AWS S3-compatible services + }); +} + +/** + * Upload a file to user's S3-compatible storage + */ +export async function uploadToUserStorage( + file: Buffer, + filename: string, + mimeType: string, + provider: StorageProvider, + endpoint: string | null, + region: string, + bucket: string, + encryptedAccessKey: string, + encryptedSecretKey: string, + password: string +): Promise { + // Decrypt credentials + const { accessKeyId, secretAccessKey } = decryptS3Credentials( + encryptedAccessKey, + encryptedSecretKey, + password + ); + + // Create S3 client + const s3 = createS3Client({ + endpoint: endpoint || undefined, + region, + accessKeyId, + secretAccessKey, + bucket, + }); + + // Upload file + const key = `synapsis/${filename}`; + + await s3.send(new PutObjectCommand({ + Bucket: bucket, + Key: key, + Body: file, + ContentType: mimeType, + })); + + // Construct URL based on provider + let url: string; + if (endpoint) { + // Custom endpoint (R2, MinIO, etc) + url = `${endpoint}/${bucket}/${key}`; + } else { + // AWS S3 standard URL + url = `https://${bucket}.s3.${region}.amazonaws.com/${key}`; + } + + return { url, key }; +} + +/** + * Test S3 credentials by attempting to head the bucket + */ +export async function testS3Credentials( + endpoint: string | null, + region: string, + bucket: string, + accessKeyId: string, + secretAccessKey: string +): Promise<{ success: boolean; error?: string }> { + try { + const s3 = createS3Client({ + endpoint: endpoint || undefined, + region, + accessKeyId, + secretAccessKey, + bucket, + }); + + // Try to check if bucket exists/is accessible + await s3.send(new HeadBucketCommand({ Bucket: bucket })); + return { success: true }; + } catch (error: any) { + console.error('[S3 Test] Credential test failed:', error); + + // Parse common errors + if (error.name === 'Forbidden' || error.name === '403') { + return { success: false, error: 'Access denied. Check your Access Key and Secret Key.' }; + } + if (error.name === 'NotFound' || error.name === '404') { + return { success: false, error: `Bucket "${bucket}" not found. Check the bucket name.` }; + } + if (error.name === 'NoSuchBucket') { + return { success: false, error: `Bucket "${bucket}" does not exist.` }; + } + if (error.name === 'InvalidAccessKeyId') { + return { success: false, error: 'Invalid Access Key ID.' }; + } + if (error.name === 'SignatureDoesNotMatch') { + return { success: false, error: 'Invalid Secret Access Key.' }; + } + if (error.name === 'NetworkingError' || error.name === 'ECONNREFUSED') { + return { success: false, error: 'Cannot connect to endpoint. Check your endpoint URL.' }; + } + + return { success: false, error: error.message || 'Failed to connect to storage. Please check your credentials.' }; + } +} + +/** + * Generate and upload avatar to user's S3 storage + */ +export async function generateAndUploadAvatarToUserStorage( + handle: string, + endpoint: string | null, + region: string, + bucket: string, + accessKey: string, + secretKey: string +): Promise { + try { + // 1. Fetch the avatar from DiceBear + const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${handle}`; + const response = await fetch(dicebearUrl); + + if (!response.ok) { + console.error(`Failed to fetch avatar from DiceBear: ${response.statusText}`); + return null; + } + + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + // 2. Upload to user's S3 + const s3 = createS3Client({ + endpoint: endpoint || undefined, + region, + accessKeyId: accessKey, + secretAccessKey: secretKey, + bucket, + }); + + const key = `synapsis/avatars/${handle.replace(/[^a-zA-Z0-9]/g, '')}-avatar.svg`; + + await s3.send(new PutObjectCommand({ + Bucket: bucket, + Key: key, + Body: buffer, + ContentType: 'image/svg+xml', + })); + + // 3. Return URL + if (endpoint) { + return `${endpoint}/${bucket}/${key}`; + } + return `https://${bucket}.s3.${region}.amazonaws.com/${key}`; + + } catch (error) { + console.error('Error generating/uploading avatar:', error); + return null; + } +} diff --git a/src/lib/swarm/identity-cache.ts b/src/lib/swarm/identity-cache.ts new file mode 100644 index 0000000..6a14770 --- /dev/null +++ b/src/lib/swarm/identity-cache.ts @@ -0,0 +1,196 @@ +/** + * Identity Cache for TOFU (Trust on First Use) protection + * + * Caches remote user identities to detect key changes. + * First fetch is trusted (TOFU), subsequent fetches validate against cache. + */ + +import { db, remoteIdentityCache } from '@/db'; +import { eq } from 'drizzle-orm'; + +interface IdentityCacheEntry { + did: string; + publicKey: string; + fetchedAt: Date; + expiresAt: Date; +} + +/** + * Get cached identity for a DID + */ +export async function getCachedIdentity(did: string): Promise { + const cached = await db.query.remoteIdentityCache.findFirst({ + where: eq(remoteIdentityCache.did, did), + }); + + return cached || null; +} + +/** + * Cache a remote user's identity + */ +export async function cacheIdentity( + did: string, + _handle: string, + _nodeDomain: string, + publicKey: string, + _displayName: string | null = null, + _avatarUrl: string | null = null +): Promise { + const now = new Date(); + const expiresAt = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days + + await db.insert(remoteIdentityCache) + .values({ + did, + publicKey, + fetchedAt: now, + expiresAt, + }) + .onConflictDoUpdate({ + target: remoteIdentityCache.did, + set: { + publicKey, + fetchedAt: now, + expiresAt, + }, + }); +} + +/** + * Validate key continuity - check if public key matches cached value + * Returns: { valid: boolean, isFirstUse: boolean, keyChanged?: boolean } + */ +export async function validateKeyContinuity( + did: string, + publicKey: string +): Promise<{ valid: boolean; isFirstUse: boolean; keyChanged?: boolean; oldKey?: string }> { + const cached = await getCachedIdentity(did); + + if (!cached) { + // First time seeing this DID - TOFU moment + return { valid: true, isFirstUse: true }; + } + + if (cached.publicKey === publicKey) { + // Key matches cached value - all good + return { valid: true, isFirstUse: false, keyChanged: false }; + } + + // Key has changed from cached value + return { + valid: true, // Still accept but warn (configurable) + isFirstUse: false, + keyChanged: true, + oldKey: cached.publicKey + }; +} + +/** + * Log a security event for key changes + */ +export function logKeyChange( + did: string, + handle: string, + nodeDomain: string, + oldKey: string, + newKey: string +): void { + // Log a prominent security warning + console.error('╔══════════════════════════════════════════════════════════════════════════════╗'); + console.error('║ 🚨 SECURITY WARNING: REMOTE PUBLIC KEY CHANGED 🚨 ║'); + console.error('╠══════════════════════════════════════════════════════════════════════════════╣'); + console.error(`║ DID: ${did.padEnd(74)} ║`); + console.error(`║ Handle: ${handle.padEnd(71)} ║`); + console.error(`║ Node: ${nodeDomain.padEnd(73)} ║`); + console.error('║ ║'); + console.error('║ This could indicate: ║'); + console.error('║ • MITM attack on your connection to the remote node ║'); + console.error('║ • Compromised remote node serving fake keys ║'); + console.error('║ • Legitimate key rotation by the user ║'); + console.error('║ ║'); + console.error('║ Verify out-of-band if possible. ║'); + console.error('╚══════════════════════════════════════════════════════════════════════════════╝'); +} + +/** + * Securely fetch and cache a remote user's public key with TOFU validation + */ +export async function fetchAndCacheRemoteKey( + did: string, + handle: string, + nodeDomain: string, + fetchPublicKey: () => Promise +): Promise<{ publicKey: string | null; fromCache: boolean; keyChanged: boolean }> { + // Check cache first + const cached = await getCachedIdentity(did); + + if (cached) { + // We have a cached key - return it but also refresh in background + // This ensures we detect changes without blocking + fetchPublicKey().then(async (freshKey) => { + if (freshKey && freshKey !== cached.publicKey) { + // Key changed! Log warning + logKeyChange(did, handle, nodeDomain, cached.publicKey, freshKey); + + // Update cache with new key (configurable: could reject instead) + await cacheIdentity(did, handle, nodeDomain, freshKey, null, null); + } + }).catch(err => { + // Background refresh failed - log but don't fail + console.error(`[IdentityCache] Background refresh failed for ${did}:`, err); + }); + + return { + publicKey: cached.publicKey, + fromCache: true, + keyChanged: false + }; + } + + // No cached key - fetch it + const publicKey = await fetchPublicKey(); + + if (!publicKey) { + return { publicKey: null, fromCache: false, keyChanged: false }; + } + + // Cache the key (TOFU moment) + await cacheIdentity(did, handle, nodeDomain, publicKey, null, null); + + return { + publicKey, + fromCache: false, + keyChanged: false + }; +} + +/** + * Handle key change policy + * Returns true if the key change should be accepted + */ +export function shouldAcceptKeyChange( + did: string, + handle: string, + nodeDomain: string, + oldKey: string, + newKey: string +): boolean { + const policy = process.env.KEY_CHANGE_POLICY || 'warn'; + + switch (policy) { + case 'strict': + // Reject changed keys + console.error(`[IdentityCache] REJECTING key change for ${did} (strict mode)`); + return false; + + case 'allow': + // Silently accept + return true; + + case 'warn': + default: + // Accept but warn (already logged) + return true; + } +}