diff --git a/.dockerignore b/.dockerignore index ed4c8e5..4f68a3b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .git node_modules +/Makefile # Output .output @@ -10,13 +11,15 @@ node_modules /build /data /library +/thumbnails # OS .DS_Store Thumbs.db -# VSCode +# Editors /.vscode +/.idea # Env .env diff --git a/.env.example b/.env.example index f492443..e3b6365 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,4 @@ SESSION_EXPIRES= USER_CLIENT_CHALLENGE_EXPIRES= SESSION_UPGRADE_CHALLENGE_EXPIRES= LIBRARY_PATH= +THUMBNAILS_PATH= diff --git a/.gitignore b/.gitignore index aac77c6..5078fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,13 +9,15 @@ node_modules /build /data /library +/thumbnails # OS .DS_Store Thumbs.db -# VSCode +# Editors /.vscode +/.idea # Env .env diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index f6570a5..31493bf 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -1,7 +1,7 @@ services: database: - image: postgres:17.2 - restart: on-failure + image: postgres:17 + restart: always volumes: - database:/var/lib/postgresql/data environment: diff --git a/docker-compose.yaml b/docker-compose.yaml index dc7f392..57a423f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,12 +1,13 @@ services: server: build: . - restart: on-failure + restart: unless-stopped depends_on: - database user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - ./data/library:/app/data/library + - ./data/thumbnails:/app/data/thumbnails environment: # ArkVault - DATABASE_HOST=database @@ -17,6 +18,7 @@ services: - USER_CLIENT_CHALLENGE_EXPIRES - SESSION_UPGRADE_CHALLENGE_EXPIRES - LIBRARY_PATH=/app/data/library + - THUMBNAILS_PATH=/app/data/thumbnails # SvelteKit - ADDRESS_HEADER=${TRUST_PROXY:+X-Forwarded-For} - XFF_DEPTH=${TRUST_PROXY:-} @@ -25,8 +27,8 @@ services: - ${PORT:-80}:3000 database: - image: postgres:17.2-alpine - restart: on-failure + image: postgres:17-alpine + restart: unless-stopped user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - ./data/database:/var/lib/postgresql/data diff --git a/package.json b/package.json index 8d0ddba..67eb017 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arkvault", "private": true, - "version": "0.4.0", + "version": "0.5.0", "type": "module", "scripts": { "dev": "vite dev", @@ -16,49 +16,50 @@ "db:migrate": "kysely migrate" }, "devDependencies": { - "@eslint/compat": "^1.2.4", - "@iconify-json/material-symbols": "^1.2.12", - "@sveltejs/adapter-node": "^5.2.11", - "@sveltejs/kit": "^2.15.2", + "@eslint/compat": "^1.3.1", + "@iconify-json/material-symbols": "^1.2.29", + "@sveltejs/adapter-node": "^5.2.13", + "@sveltejs/kit": "^2.22.5", "@sveltejs/vite-plugin-svelte": "^4.0.4", "@types/file-saver": "^2.0.7", "@types/ms": "^0.7.34", - "@types/node-schedule": "^2.1.7", - "@types/pg": "^8.11.10", - "autoprefixer": "^10.4.20", - "axios": "^1.7.9", - "dexie": "^4.0.10", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.46.1", - "eslint-plugin-tailwindcss": "^3.17.5", - "exifreader": "^4.26.0", + "@types/node-schedule": "^2.1.8", + "@types/pg": "^8.15.4", + "autoprefixer": "^10.4.21", + "axios": "^1.10.0", + "dexie": "^4.0.11", + "eslint": "^9.30.1", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-svelte": "^3.10.1", + "eslint-plugin-tailwindcss": "^3.18.0", + "exifreader": "^4.31.1", "file-saver": "^2.0.5", - "globals": "^15.14.0", + "globals": "^16.3.0", "heic2any": "^0.0.4", - "kysely-ctl": "^0.10.1", - "mime": "^4.0.6", + "kysely-ctl": "^0.13.1", + "lru-cache": "^11.1.0", + "mime": "^4.0.7", "p-limit": "^6.2.0", - "prettier": "^3.4.2", - "prettier-plugin-svelte": "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.9", - "svelte": "^5.19.1", - "svelte-check": "^4.1.3", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.6.14", + "svelte": "^5.35.6", + "svelte-check": "^4.2.2", "tailwindcss": "^3.4.17", - "typescript": "^5.7.3", - "typescript-eslint": "^8.19.1", - "unplugin-icons": "^0.22.0", - "vite": "^5.4.11" + "typescript": "^5.8.3", + "typescript-eslint": "^8.36.0", + "unplugin-icons": "^22.1.0", + "vite": "^5.4.19" }, "dependencies": { "@fastify/busboy": "^3.1.1", - "argon2": "^0.41.1", - "kysely": "^0.27.5", + "argon2": "^0.43.0", + "kysely": "^0.28.2", "ms": "^2.1.3", "node-schedule": "^2.1.1", - "pg": "^8.13.1", - "uuid": "^11.0.4", - "zod": "^3.24.1" + "pg": "^8.16.3", + "uuid": "^11.1.0", + "zod": "^3.25.76" }, "engines": { "node": "^22.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ed4442..d0d9407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,11 +12,11 @@ importers: specifier: ^3.1.1 version: 3.1.1 argon2: - specifier: ^0.41.1 - version: 0.41.1 + specifier: ^0.43.0 + version: 0.43.0 kysely: - specifier: ^0.27.5 - version: 0.27.5 + specifier: ^0.28.2 + version: 0.28.2 ms: specifier: ^2.1.3 version: 2.1.3 @@ -24,30 +24,30 @@ importers: specifier: ^2.1.1 version: 2.1.1 pg: - specifier: ^8.13.1 - version: 8.13.1 + specifier: ^8.16.3 + version: 8.16.3 uuid: - specifier: ^11.0.4 - version: 11.0.4 + specifier: ^11.1.0 + version: 11.1.0 zod: - specifier: ^3.24.1 - version: 3.24.1 + specifier: ^3.25.76 + version: 3.25.76 devDependencies: '@eslint/compat': - specifier: ^1.2.4 - version: 1.2.4(eslint@9.17.0(jiti@2.4.2)) + specifier: ^1.3.1 + version: 1.3.1(eslint@9.30.1(jiti@2.4.2)) '@iconify-json/material-symbols': - specifier: ^1.2.12 - version: 1.2.12 + specifier: ^1.2.29 + version: 1.2.29 '@sveltejs/adapter-node': - specifier: ^5.2.11 - version: 5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))) + specifier: ^5.2.13 + version: 5.2.13(@sveltejs/kit@2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))) '@sveltejs/kit': - specifier: ^2.15.2 - version: 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) + specifier: ^2.22.5 + version: 2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 - version: 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) + version: 4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -55,83 +55,86 @@ importers: specifier: ^0.7.34 version: 0.7.34 '@types/node-schedule': - specifier: ^2.1.7 - version: 2.1.7 + specifier: ^2.1.8 + version: 2.1.8 '@types/pg': - specifier: ^8.11.10 - version: 8.11.10 + specifier: ^8.15.4 + version: 8.15.4 autoprefixer: - specifier: ^10.4.20 - version: 10.4.20(postcss@8.4.49) + specifier: ^10.4.21 + version: 10.4.21(postcss@8.5.6) axios: - specifier: ^1.7.9 - version: 1.7.9 + specifier: ^1.10.0 + version: 1.10.0 dexie: - specifier: ^4.0.10 - version: 4.0.10 + specifier: ^4.0.11 + version: 4.0.11 eslint: - specifier: ^9.17.0 - version: 9.17.0(jiti@2.4.2) + specifier: ^9.30.1 + version: 9.30.1(jiti@2.4.2) eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@9.17.0(jiti@2.4.2)) + specifier: ^10.1.5 + version: 10.1.5(eslint@9.30.1(jiti@2.4.2)) eslint-plugin-svelte: - specifier: ^2.46.1 - version: 2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.19.1) + specifier: ^3.10.1 + version: 3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.6) eslint-plugin-tailwindcss: - specifier: ^3.17.5 - version: 3.17.5(tailwindcss@3.4.17) + specifier: ^3.18.0 + version: 3.18.0(tailwindcss@3.4.17) exifreader: - specifier: ^4.26.0 - version: 4.26.0 + specifier: ^4.31.1 + version: 4.31.1 file-saver: specifier: ^2.0.5 version: 2.0.5 globals: - specifier: ^15.14.0 - version: 15.14.0 + specifier: ^16.3.0 + version: 16.3.0 heic2any: specifier: ^0.0.4 version: 0.0.4 kysely-ctl: - specifier: ^0.10.1 - version: 0.10.1(kysely@0.27.5) + specifier: ^0.13.1 + version: 0.13.1(kysely@0.28.2) + lru-cache: + specifier: ^11.1.0 + version: 11.1.0 mime: - specifier: ^4.0.6 - version: 4.0.6 + specifier: ^4.0.7 + version: 4.0.7 p-limit: specifier: ^6.2.0 version: 6.2.0 prettier: - specifier: ^3.4.2 - version: 3.4.2 + specifier: ^3.6.2 + version: 3.6.2 prettier-plugin-svelte: - specifier: ^3.3.2 - version: 3.3.2(prettier@3.4.2)(svelte@5.19.1) + specifier: ^3.4.0 + version: 3.4.0(prettier@3.6.2)(svelte@5.35.6) prettier-plugin-tailwindcss: - specifier: ^0.6.9 - version: 0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1))(prettier@3.4.2) + specifier: ^0.6.14 + version: 0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.6))(prettier@3.6.2) svelte: - specifier: ^5.19.1 - version: 5.19.1 + specifier: ^5.35.6 + version: 5.35.6 svelte-check: - specifier: ^4.1.3 - version: 4.1.3(picomatch@4.0.2)(svelte@5.19.1)(typescript@5.7.3) + specifier: ^4.2.2 + version: 4.2.2(picomatch@4.0.2)(svelte@5.35.6)(typescript@5.8.3) tailwindcss: specifier: ^3.4.17 version: 3.4.17 typescript: - specifier: ^5.7.3 - version: 5.7.3 + specifier: ^5.8.3 + version: 5.8.3 typescript-eslint: - specifier: ^8.19.1 - version: 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) + specifier: ^8.36.0 + version: 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) unplugin-icons: - specifier: ^0.22.0 - version: 0.22.0(svelte@5.19.1) + specifier: ^22.1.0 + version: 22.1.0(svelte@5.35.6) vite: - specifier: ^5.4.11 - version: 5.4.11(@types/node@22.10.5) + specifier: ^5.4.19 + version: 5.4.19(@types/node@24.0.13) packages: @@ -143,14 +146,11 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/install-pkg@0.4.1': - resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/install-pkg@0.5.0': - resolution: {integrity: sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==} - - '@antfu/utils@0.7.10': - resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + '@antfu/utils@8.1.1': + resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} @@ -158,284 +158,140 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -444,37 +300,45 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.2.4': - resolution: {integrity: sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==} + '@eslint/compat@1.3.1': + resolution: {integrity: sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^9.10.0 + eslint: ^8.40 || 9 peerDependenciesMeta: eslint: optional: true - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.9.1': - resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.17.0': - resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.4': - resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + '@eslint/js@9.30.1': + resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.3': + resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fastify/busboy@3.1.1': @@ -496,40 +360,35 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@iconify-json/material-symbols@1.2.12': - resolution: {integrity: sha512-2p2T13Kccy7R2HNbdiVsIcHxjp4s9a+iKlfbtt29hldG1pVNaPIlMALNA9bjdEwPjwsVFe06INCbjCRc68JysQ==} + '@iconify-json/material-symbols@1.2.29': + resolution: {integrity: sha512-UUSrsl0gHF0GjAB9eZOpXrj7/v55ayMzo3QnMwUqP/FSfSkITKLR7CsBmUIFS8eEj8eRTfBNWA1yiIJR6UOdWg==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@2.2.1': - resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==} + '@iconify/utils@2.3.0': + resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -551,11 +410,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@polka/url@1.0.0-next.28': - resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@rollup/plugin-commonjs@28.0.2': - resolution: {integrity: sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==} + '@rollup/plugin-commonjs@28.0.6': + resolution: {integrity: sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -572,8 +431,8 @@ packages: rollup: optional: true - '@rollup/plugin-node-resolve@16.0.0': - resolution: {integrity: sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==} + '@rollup/plugin-node-resolve@16.0.1': + resolution: {integrity: sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.78.0||^3.0.0||^4.0.0 @@ -581,8 +440,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -590,114 +449,124 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.30.1': - resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} + '@rollup/rollup-android-arm-eabi@4.44.2': + resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.30.1': - resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} + '@rollup/rollup-android-arm64@4.44.2': + resolution: {integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.30.1': - resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} + '@rollup/rollup-darwin-arm64@4.44.2': + resolution: {integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.30.1': - resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} + '@rollup/rollup-darwin-x64@4.44.2': + resolution: {integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.30.1': - resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} + '@rollup/rollup-freebsd-arm64@4.44.2': + resolution: {integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.30.1': - resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} + '@rollup/rollup-freebsd-x64@4.44.2': + resolution: {integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': - resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} + '@rollup/rollup-linux-arm-gnueabihf@4.44.2': + resolution: {integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.30.1': - resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} + '@rollup/rollup-linux-arm-musleabihf@4.44.2': + resolution: {integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.30.1': - resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} + '@rollup/rollup-linux-arm64-gnu@4.44.2': + resolution: {integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.30.1': - resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} + '@rollup/rollup-linux-arm64-musl@4.44.2': + resolution: {integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': - resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.44.2': + resolution: {integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': - resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': + resolution: {integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.30.1': - resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} + '@rollup/rollup-linux-riscv64-gnu@4.44.2': + resolution: {integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.30.1': - resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} + '@rollup/rollup-linux-riscv64-musl@4.44.2': + resolution: {integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.44.2': + resolution: {integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.30.1': - resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} + '@rollup/rollup-linux-x64-gnu@4.44.2': + resolution: {integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.30.1': - resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} + '@rollup/rollup-linux-x64-musl@4.44.2': + resolution: {integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.30.1': - resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} + '@rollup/rollup-win32-arm64-msvc@4.44.2': + resolution: {integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.30.1': - resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} + '@rollup/rollup-win32-ia32-msvc@4.44.2': + resolution: {integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.30.1': - resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} + '@rollup/rollup-win32-x64-msvc@4.44.2': + resolution: {integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==} cpu: [x64] os: [win32] - '@sveltejs/adapter-node@5.2.11': - resolution: {integrity: sha512-lR7/dfUaKFf3aI408KRDy/BVDYoqUws7zNOJz2Hl4JoshlTnMgdha3brXBRFXB+cWtYvJjjPhvmq3xqpbioi4w==} + '@sveltejs/acorn-typescript@1.0.5': + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-node@5.2.13': + resolution: {integrity: sha512-yS2TVFmIrxjGhYaV5/iIUrJ3mJl6zjaYn0lBD70vTLnYvJeqf3cjvLXeXCUCuYinhSBoyF4DpfGla49BnIy7sQ==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.15.2': - resolution: {integrity: sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==} + '@sveltejs/kit@2.22.5': + resolution: {integrity: sha512-l5i+LcDaoymD2mg5ziptnHmzzF79+c9twJiDoLWAPKq7afMEe4mvGesJ+LVtm33A92mLzd2KUHgtGSqTrvfkvg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.3 || ^6.0.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 '@sveltejs/vite-plugin-svelte-inspector@3.0.1': resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==} @@ -717,8 +586,8 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/file-saver@2.0.7': resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} @@ -729,67 +598,79 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node-schedule@2.1.7': - resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==} + '@types/node-schedule@2.1.8': + resolution: {integrity: sha512-k00g6Yj/oUg/CDC+MeLHUzu0+OFxWbIqrFfDiLi6OPKxTujvpv29mHGM8GtKr7B+9Vv92FcK/8mRqi1DK5f3hA==} - '@types/node@22.10.5': - resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/node@24.0.13': + resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==} - '@types/pg@8.11.10': - resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/pg@8.15.4': + resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@typescript-eslint/eslint-plugin@8.19.1': - resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} + '@typescript-eslint/eslint-plugin@8.36.0': + resolution: {integrity: sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.36.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.19.1': - resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} + '@typescript-eslint/parser@8.36.0': + resolution: {integrity: sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.19.1': - resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} + '@typescript-eslint/project-service@8.36.0': + resolution: {integrity: sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.36.0': + resolution: {integrity: sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.19.1': - resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} + '@typescript-eslint/tsconfig-utils@8.36.0': + resolution: {integrity: sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/type-utils@8.36.0': + resolution: {integrity: sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.19.1': - resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} + '@typescript-eslint/types@8.36.0': + resolution: {integrity: sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.19.1': - resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} + '@typescript-eslint/typescript-estree@8.36.0': + resolution: {integrity: sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.19.1': - resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} + '@typescript-eslint/utils@8.36.0': + resolution: {integrity: sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.19.1': - resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} + '@typescript-eslint/visitor-keys@8.36.0': + resolution: {integrity: sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@xmldom/xmldom@0.9.6': - resolution: {integrity: sha512-Su4xcxR0CPGwlDHNmVP09fqET9YxbyDXHaSob6JlBH7L6reTYaeim6zbk9o08UarO0L5GTRo3uzl0D+9lSxmvw==} + '@xmldom/xmldom@0.9.8': + resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==} engines: {node: '>=14.6'} acorn-jsx@5.3.2: @@ -797,13 +678,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-typescript@1.4.13: - resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} - peerDependencies: - acorn: '>=8.9.0' - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true @@ -836,8 +712,8 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argon2@0.41.1: - resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} + argon2@0.43.0: + resolution: {integrity: sha512-u/HKLcbWShVDhkfwI4hWyiUf3qyX8QhTfaIv2cWE18uqhXCmR5hb6Ed7oqYi2KCQegeAnRhiFzbjzm7i5yl1GA==} engines: {node: '>=16.17.0'} argparse@2.0.1: @@ -850,15 +726,15 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - autoprefixer@10.4.20: - resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axios@1.10.0: + resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -871,29 +747,33 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - c12@2.0.1: - resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==} + c12@3.0.4: + resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} peerDependencies: magicast: ^0.3.5 peerDependenciesMeta: magicast: optional: true + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -902,8 +782,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001692: - resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -917,10 +797,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -952,8 +828,11 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - consola@3.4.0: - resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} cookie@0.6.0: @@ -973,8 +852,8 @@ packages: engines: {node: '>=4'} hasBin: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -996,14 +875,14 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - destr@2.0.3: - resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} - dexie@4.0.10: - resolution: {integrity: sha512-eM2RzuR3i+M046r2Q0Optl3pS31qTWf8aFuA7H9wnsHTwl8EPvroVLwvQene/6paAs39Tbk6fWZcn2aZaHkc/w==} + dexie@4.0.11: + resolution: {integrity: sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==} didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1011,15 +890,19 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dotenv@16.4.7: - resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.79: - resolution: {integrity: sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==} + electron-to-chromium@1.5.182: + resolution: {integrity: sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1027,16 +910,27 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1045,52 +939,42 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-compat-utils@0.5.1: - resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + eslint-config-prettier@10.1.5: + resolution: {integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==} hasBin: true peerDependencies: eslint: '>=7.0.0' - eslint-plugin-svelte@2.46.1: - resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==} - engines: {node: ^14.17.0 || >=16.0.0} + eslint-plugin-svelte@3.10.1: + resolution: {integrity: sha512-csCh2x0ge/DugXC7dCANh46Igi7bjMZEy6rHZCdS13AoGVJSu7a90Kru3I8oMYLGEemPRE1hQXadxvRPVMAAXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0 + eslint: ^8.57.1 || ^9.0.0 svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: svelte: optional: true - eslint-plugin-tailwindcss@3.17.5: - resolution: {integrity: sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==} + eslint-plugin-tailwindcss@3.18.0: + resolution: {integrity: sha512-PQDU4ZMzFH0eb2DrfHPpbgo87Zgg2EXSMOj1NSfzdZm+aJzpuwGerfowMIaVehSREEa0idbf/eoNYAOHSJoDAQ==} engines: {node: '>=18.12.0'} peerDependencies: tailwindcss: ^3.4.0 - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.17.0: - resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} + eslint@9.30.1: + resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1102,20 +986,16 @@ packages: esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@1.4.3: - resolution: {integrity: sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==} + esrap@2.1.0: + resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -1132,12 +1012,11 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + exifreader@4.31.1: + resolution: {integrity: sha512-rkSg/NejDN9D+8GuRWZ2Y4G8KSrj0hdaeMoew8d0J5cF5oS0p6DVar2PSQ0fP3assu6s1PYh6M1lhtS7Kigk6Q==} - exifreader@4.26.0: - resolution: {integrity: sha512-nNN9B0oaXTOpArdnIdJBAro2Sa620m7wMjMA5Xy1rcua0EYHVjzGKM5syBOWDqIG2Qay6Pes/5FOdj65hvZ9Vw==} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1152,11 +1031,11 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fdir@6.4.2: - resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1182,8 +1061,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} @@ -1194,21 +1073,17 @@ packages: debug: optional: true - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + form-data@4.0.3: + resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} engines: {node: '>= 6'} fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1217,15 +1092,16 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} - giget@1.2.3: - resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true glob-parent@5.1.2: @@ -1244,15 +1120,17 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.14.0: - resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globalyzer@0.1.0: - resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1261,6 +1139,14 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1268,20 +1154,17 @@ packages: heic2any@0.0.4: resolution: {integrity: sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} - import-meta-resolve@4.1.0: - resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -1320,10 +1203,6 @@ packages: is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1358,26 +1237,26 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - known-css-properties@0.35.0: - resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - kysely-ctl@0.10.1: - resolution: {integrity: sha512-tB2mGV/MbfaQC6Lo582Rs2OdtfX23ueWDscCSDT42Iy8pYHYbhMy9ncXU35ee8LQz4BO2apQihyY8rDProP+9w==} + kysely-ctl@0.13.1: + resolution: {integrity: sha512-DhTgpt1ru3Y74rI8O3IRLJ5HV09vMSz4q2KEeqaUX0GYMB09zO1KsdwSsF+ZMa8bHeS4F4J3Vjfbc4ulJQ1r0A==} engines: {node: '>=18'} hasBin: true peerDependencies: - kysely: '>=0.18.1 <0.28.0' + kysely: '>=0.18.1 <0.29.0' kysely-postgres-js: ^2 peerDependenciesMeta: kysely-postgres-js: optional: true - kysely@0.27.5: - resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} - engines: {node: '>=14.0.0'} + kysely@0.28.2: + resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==} + engines: {node: '>=18.0.0'} levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -1394,8 +1273,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + local-pkg@1.1.1: + resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} engines: {node: '>=14'} locate-character@3.0.0: @@ -1414,15 +1293,20 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - luxon@3.5.0: - resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + + luxon@3.7.1: + resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} engines: {node: '>=12'} magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -1440,15 +1324,11 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@4.0.6: - resolution: {integrity: sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==} + mime@4.0.7: + resolution: {integrity: sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==} engines: {node: '>=16'} hasBin: true - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1456,36 +1336,19 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - - mlly@1.7.3: - resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} ms@2.1.3: @@ -1494,20 +1357,20 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-addon-api@8.3.0: - resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} + node-addon-api@8.4.0: + resolution: {integrity: sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==} engines: {node: ^18 || ^20 || >= 21} - node-fetch-native@1.6.4: - resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + node-fetch-native@1.6.6: + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} @@ -1528,17 +1391,8 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - nypm@0.3.12: - resolution: {integrity: sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==} - engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true - - nypm@0.4.1: - resolution: {integrity: sha512-1b9mihliBh8UCcKtcGRu//G50iHpjxIQVUqkdhPT/SDVE7KdJKoHXLS0heuYTQCx95dFqiyUbXZB9r8ikn+93g==} + nypm@0.6.0: + resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} engines: {node: ^14.16.0 || >=16.10.0} hasBin: true @@ -1550,18 +1404,11 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - ofetch@1.4.1: resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} - ohash@1.1.4: - resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -1582,8 +1429,8 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.8: - resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -1597,10 +1444,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1608,45 +1451,37 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - pg-cloudflare@1.1.1: - resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} - pg-connection-string@2.7.0: - resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-numeric@1.0.2: - resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} - engines: {node: '>=4'} - - pg-pool@3.7.0: - resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} peerDependencies: pg: '>=8.0' - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg-types@4.0.2: - resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} - engines: {node: '>=10'} - - pg@8.13.1: - resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} - engines: {node: '>= 8.0.0'} + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' peerDependenciesMeta: @@ -1671,12 +1506,15 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkg-types@1.3.0: - resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.2.0: + resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==} postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} @@ -1720,11 +1558,11 @@ packages: peerDependencies: postcss: ^8.2.14 - postcss-safe-parser@6.0.0: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} peerDependencies: - postcss: ^8.3.3 + postcss: ^8.4.31 postcss-scss@4.0.9: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} @@ -1736,67 +1574,54 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - postgres-array@3.0.2: - resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} - engines: {node: '>=12'} - postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} - postgres-bytea@3.0.0: - resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} - engines: {node: '>= 6'} - postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} - postgres-date@2.1.0: - resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} - engines: {node: '>=12'} - postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - postgres-interval@3.0.0: - resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} - engines: {node: '>=12'} - - postgres-range@1.1.4: - resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-svelte@3.3.2: - resolution: {integrity: sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==} + prettier-plugin-svelte@3.4.0: + resolution: {integrity: sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier-plugin-tailwindcss@0.6.9: - resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==} + prettier-plugin-tailwindcss@0.6.14: + resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' '@prettier/plugin-pug': '*' '@shopify/prettier-plugin-liquid': '*' '@trivago/prettier-plugin-sort-imports': '*' - '@zackad/prettier-plugin-twig-melody': '*' + '@zackad/prettier-plugin-twig': '*' prettier: ^3.0 prettier-plugin-astro: '*' prettier-plugin-css-order: '*' @@ -1812,13 +1637,17 @@ packages: peerDependenciesMeta: '@ianvs/prettier-plugin-sort-imports': optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true '@prettier/plugin-pug': optional: true '@shopify/prettier-plugin-liquid': optional: true '@trivago/prettier-plugin-sort-imports': optional: true - '@zackad/prettier-plugin-twig-melody': + '@zackad/prettier-plugin-twig': optional: true prettier-plugin-astro: optional: true @@ -1843,8 +1672,8 @@ packages: prettier-plugin-svelte: optional: true - prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true @@ -1855,6 +1684,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1868,28 +1700,25 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.0.2: - resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} - engines: {node: '>= 14.16.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} hasBin: true - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.30.1: - resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} + rollup@4.44.2: + resolution: {integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1900,8 +1729,8 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true @@ -1920,8 +1749,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sirv@3.0.0: - resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} sorted-array-functions@1.3.0: @@ -1935,8 +1764,8 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -1954,10 +1783,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1975,25 +1800,25 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.1.3: - resolution: {integrity: sha512-IEMoQDH+TrPKwKeIyJim+PU8FxnzQMXsFHR/ldErkHpPXEGHCujHUXiR8jg6qDMqzsif5BbDOUFORltu87ex7g==} + svelte-check@4.2.2: + resolution: {integrity: sha512-1+31EOYZ7NKN0YDMKusav2hhEoA51GD9Ws6o//0SphMT0ve9mBTsTUEX7OmDMadUP3KjNHsSKtJrqdSaD8CrGQ==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@0.43.0: - resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + svelte-eslint-parser@1.2.0: + resolution: {integrity: sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: svelte: optional: true - svelte@5.19.1: - resolution: {integrity: sha512-H/Vs2O51bwILZbaNUSdr4P1NbLpOGsxl4jJAjd88ELjzRgeRi1BHqexcVGannDr7D1pmTYWWajzHOM7bMbtB9Q==} + svelte@5.35.6: + resolution: {integrity: sha512-p7PVLQYrvCxJuxzGfOv/l71hVuHC6EZk5UDjbt/bndMYaBcUV5sFjDsj+PSIYvz1vcfbG6inX83/xIUeik1xGA==} engines: {node: '>=18'} tailwindcss@3.4.17: @@ -2001,10 +1826,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2012,12 +1833,12 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - tiny-glob@0.2.9: - resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} - tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2026,8 +1847,8 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - ts-api-utils@2.0.0: - resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -2035,35 +1856,30 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsx@4.19.2: - resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} - engines: {node: '>=18.0.0'} - hasBin: true - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.19.1: - resolution: {integrity: sha512-LKPUQpdEMVOeKluHi8md7rwLcoXHhwvWp3x+sJkMuq3gGm9yaYJtPo8sRZSblMFJ5pcOGCAak/scKf1mvZDlQw==} + typescript-eslint@8.36.0: + resolution: {integrity: sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} - unplugin-icons@0.22.0: - resolution: {integrity: sha512-CP+iZq5U7doOifer5bcM0jQ9t3Is7EGybIYt3myVxceI8Zuk8EZEpe1NPtJvh7iqMs1VdbK0L41t9+um9VuuLw==} + unplugin-icons@22.1.0: + resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==} peerDependencies: '@svgr/core': '>=7.0.0' '@svgx/core': ^1.0.1 @@ -2085,12 +1901,12 @@ packages: vue-template-es2015-compiler: optional: true - unplugin@2.1.2: - resolution: {integrity: sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==} + unplugin@2.3.5: + resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} engines: {node: '>=18.12.0'} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2101,12 +1917,12 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.0.4: - resolution: {integrity: sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - vite@5.4.11: - resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2136,10 +1952,10 @@ packages: terser: optional: true - vitefu@1.0.5: - resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==} + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 peerDependenciesMeta: vite: optional: true @@ -2168,31 +1984,28 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} - engines: {node: '>= 14'} + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} hasBin: true yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod@3.24.1: - resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: @@ -2200,205 +2013,135 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 - '@antfu/install-pkg@0.4.1': + '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 0.2.8 - tinyexec: 0.3.2 + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 - '@antfu/install-pkg@0.5.0': - dependencies: - package-manager-detector: 0.2.8 - tinyexec: 0.3.2 - - '@antfu/utils@0.7.10': {} + '@antfu/utils@8.1.1': {} '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.1': - optional: true - '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.1': - optional: true - '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.1': - optional: true - '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.1': - optional: true - '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.1': - optional: true - '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.1': - optional: true - '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.1': - optional: true - '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.1': - optional: true - '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.1': - optional: true - '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.1': - optional: true - '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.1': - optional: true - '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.1': - optional: true - '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.1': - optional: true - '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.1': - optional: true - '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.1': - optional: true - '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.1': - optional: true - '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.1': - optional: true - '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.1': - optional: true - - '@esbuild/openbsd-arm64@0.23.1': - optional: true - '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.1': - optional: true - '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.1': - optional: true - '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.1': - optional: true - '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.1': - optional: true - '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.1': - optional: true - - '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': dependencies: - eslint: 9.17.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.4(eslint@9.17.0(jiti@2.4.2))': + '@eslint/compat@1.3.1(eslint@9.30.1(jiti@2.4.2))': optionalDependencies: - eslint: 9.17.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.21.0': dependencies: - '@eslint/object-schema': 2.1.5 - debug: 4.4.0 + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/core@0.9.1': + '@eslint/config-helpers@0.3.0': {} + + '@eslint/core@0.14.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.2.0': + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 + debug: 4.4.1 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.17.0': {} + '@eslint/js@9.30.1': {} - '@eslint/object-schema@2.1.5': {} + '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.4': + '@eslint/plugin-kit@0.3.3': dependencies: + '@eslint/core': 0.15.1 levn: 0.4.1 '@fastify/busboy@3.1.1': {} @@ -2414,24 +2157,24 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.1': {} + '@humanwhocodes/retry@0.4.3': {} - '@iconify-json/material-symbols@1.2.12': + '@iconify-json/material-symbols@1.2.29': dependencies: '@iconify/types': 2.0.0 '@iconify/types@2.0.0': {} - '@iconify/utils@2.2.1': + '@iconify/utils@2.3.0': dependencies: - '@antfu/install-pkg': 0.4.1 - '@antfu/utils': 0.7.10 + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 8.1.1 '@iconify/types': 2.0.0 - debug: 4.4.0 - globals: 15.14.0 + debug: 4.4.1 + globals: 15.15.0 kolorist: 1.8.0 - local-pkg: 0.5.1 - mlly: 1.7.3 + local-pkg: 1.1.1 + mlly: 1.7.4 transitivePeerDependencies: - supports-color @@ -2444,22 +2187,19 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.12': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.5.4': {} - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.29': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -2471,159 +2211,166 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.19.1 '@phc/format@1.0.0': {} '@pkgjs/parseargs@0.11.0': optional: true - '@polka/url@1.0.0-next.28': {} + '@polka/url@1.0.0-next.29': {} - '@rollup/plugin-commonjs@28.0.2(rollup@4.30.1)': + '@rollup/plugin-commonjs@28.0.6(rollup@4.44.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@rollup/pluginutils': 5.2.0(rollup@4.44.2) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.4.2(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) is-reference: 1.2.1 magic-string: 0.30.17 picomatch: 4.0.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/plugin-json@6.1.0(rollup@4.30.1)': + '@rollup/plugin-json@6.1.0(rollup@4.44.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@rollup/pluginutils': 5.2.0(rollup@4.44.2) optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/plugin-node-resolve@16.0.0(rollup@4.30.1)': + '@rollup/plugin-node-resolve@16.0.1(rollup@4.44.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@rollup/pluginutils': 5.2.0(rollup@4.44.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.10 optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/pluginutils@5.1.4(rollup@4.30.1)': + '@rollup/pluginutils@5.2.0(rollup@4.44.2)': dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/rollup-android-arm-eabi@4.30.1': + '@rollup/rollup-android-arm-eabi@4.44.2': optional: true - '@rollup/rollup-android-arm64@4.30.1': + '@rollup/rollup-android-arm64@4.44.2': optional: true - '@rollup/rollup-darwin-arm64@4.30.1': + '@rollup/rollup-darwin-arm64@4.44.2': optional: true - '@rollup/rollup-darwin-x64@4.30.1': + '@rollup/rollup-darwin-x64@4.44.2': optional: true - '@rollup/rollup-freebsd-arm64@4.30.1': + '@rollup/rollup-freebsd-arm64@4.44.2': optional: true - '@rollup/rollup-freebsd-x64@4.30.1': + '@rollup/rollup-freebsd-x64@4.44.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + '@rollup/rollup-linux-arm-gnueabihf@4.44.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.30.1': + '@rollup/rollup-linux-arm-musleabihf@4.44.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.30.1': + '@rollup/rollup-linux-arm64-gnu@4.44.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.30.1': + '@rollup/rollup-linux-arm64-musl@4.44.2': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + '@rollup/rollup-linux-loongarch64-gnu@4.44.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-gnu@4.44.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-musl@4.44.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.30.1': + '@rollup/rollup-linux-s390x-gnu@4.44.2': optional: true - '@rollup/rollup-linux-x64-musl@4.30.1': + '@rollup/rollup-linux-x64-gnu@4.44.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.30.1': + '@rollup/rollup-linux-x64-musl@4.44.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.30.1': + '@rollup/rollup-win32-arm64-msvc@4.44.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.30.1': + '@rollup/rollup-win32-ia32-msvc@4.44.2': optional: true - '@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))': + '@rollup/rollup-win32-x64-msvc@4.44.2': + optional: true + + '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': dependencies: - '@rollup/plugin-commonjs': 28.0.2(rollup@4.30.1) - '@rollup/plugin-json': 6.1.0(rollup@4.30.1) - '@rollup/plugin-node-resolve': 16.0.0(rollup@4.30.1) - '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) - rollup: 4.30.1 + acorn: 8.15.0 - '@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/adapter-node@5.2.13(@sveltejs/kit@2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) + '@rollup/plugin-commonjs': 28.0.6(rollup@4.44.2) + '@rollup/plugin-json': 6.1.0(rollup@4.44.2) + '@rollup/plugin-node-resolve': 16.0.1(rollup@4.44.2) + '@sveltejs/kit': 2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) + rollup: 4.44.2 + + '@sveltejs/kit@2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))': + dependencies: + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) '@types/cookie': 0.6.0 + acorn: 8.15.0 cookie: 0.6.0 devalue: 5.1.1 esm-env: 1.2.2 - import-meta-resolve: 4.1.0 kleur: 4.1.5 magic-string: 0.30.17 - mrmime: 2.0.0 + mrmime: 2.0.1 sade: 1.8.1 set-cookie-parser: 2.7.1 - sirv: 3.0.0 - svelte: 5.19.1 - tiny-glob: 0.2.9 - vite: 5.4.11(@types/node@22.10.5) + sirv: 3.0.1 + svelte: 5.35.6 + vite: 5.4.19(@types/node@24.0.13) - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) - debug: 4.4.0 - svelte: 5.19.1 - vite: 5.4.11(@types/node@22.10.5) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) + debug: 4.4.1 + svelte: 5.35.6 + vite: 5.4.19(@types/node@24.0.13) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) - debug: 4.4.0 + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) + debug: 4.4.1 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 - svelte: 5.19.1 - vite: 5.4.11(@types/node@22.10.5) - vitefu: 1.0.5(vite@5.4.11(@types/node@22.10.5)) + svelte: 5.35.6 + vite: 5.4.19(@types/node@24.0.13) + vitefu: 1.1.1(vite@5.4.19(@types/node@24.0.13)) transitivePeerDependencies: - supports-color '@types/cookie@0.6.0': {} - '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} '@types/file-saver@2.0.7': {} @@ -2631,111 +2378,122 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node-schedule@2.1.7': + '@types/node-schedule@2.1.8': dependencies: - '@types/node': 22.10.5 + '@types/node': 24.0.13 - '@types/node@22.10.5': + '@types/node@24.0.13': dependencies: - undici-types: 6.20.0 + undici-types: 7.8.0 - '@types/pg@8.11.10': + '@types/pg@8.15.4': dependencies: - '@types/node': 22.10.5 - pg-protocol: 1.7.0 - pg-types: 4.0.2 + '@types/node': 24.0.13 + pg-protocol: 1.10.3 + pg-types: 2.2.0 '@types/resolve@1.20.2': {} - '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.19.1 - eslint: 9.17.0(jiti@2.4.2) + '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/type-utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 + eslint: 9.30.1(jiti@2.4.2) graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.0.0(typescript@5.7.3) - typescript: 5.7.3 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.19.1 - debug: 4.4.0 - eslint: 9.17.0(jiti@2.4.2) - typescript: 5.7.3 + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.19.1': + '@typescript-eslint/project-service@8.36.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/visitor-keys': 8.19.1 - - '@typescript-eslint/type-utils@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - debug: 4.4.0 - eslint: 9.17.0(jiti@2.4.2) - ts-api-utils: 2.0.0(typescript@5.7.3) - typescript: 5.7.3 + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 + debug: 4.4.1 + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.19.1': {} - - '@typescript-eslint/typescript-estree@8.19.1(typescript@5.7.3)': + '@typescript-eslint/scope-manager@8.36.0': dependencies: - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/visitor-keys': 8.19.1 - debug: 4.4.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/visitor-keys': 8.36.0 + + '@typescript-eslint/tsconfig-utils@8.36.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.36.0': {} + + '@typescript-eslint/typescript-estree@8.36.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.36.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/visitor-keys': 8.36.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 2.0.0(typescript@5.7.3) - typescript: 5.7.3 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) - eslint: 9.17.0(jiti@2.4.2) - typescript: 5.7.3 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.19.1': + '@typescript-eslint/visitor-keys@8.36.0': dependencies: - '@typescript-eslint/types': 8.19.1 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.36.0 + eslint-visitor-keys: 4.2.1 - '@xmldom/xmldom@0.9.6': + '@xmldom/xmldom@0.9.8': optional: true - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.0 + acorn: 8.15.0 - acorn-typescript@1.4.13(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - - acorn@8.14.0: {} + acorn@8.15.0: {} ajv@6.12.6: dependencies: @@ -2763,10 +2521,10 @@ snapshots: arg@5.0.2: {} - argon2@0.41.1: + argon2@0.43.0: dependencies: '@phc/format': 1.0.0 - node-addon-api: 8.3.0 + node-addon-api: 8.4.0 node-gyp-build: 4.8.4 argparse@2.0.1: {} @@ -2775,20 +2533,20 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.20(postcss@8.4.49): + autoprefixer@10.4.21(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - caniuse-lite: 1.0.30001692 + browserslist: 4.25.1 + caniuse-lite: 1.0.30001727 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.4.49 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - axios@1.7.9: + axios@1.10.0: dependencies: follow-redirects: 1.15.9 - form-data: 4.0.1 + form-data: 4.0.3 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -2799,12 +2557,12 @@ snapshots: binary-extensions@2.3.0: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -2812,33 +2570,38 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: + browserslist@4.25.1: dependencies: - caniuse-lite: 1.0.30001692 - electron-to-chromium: 1.5.79 + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.182 node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.25.1) - c12@2.0.1: + c12@3.0.4: dependencies: chokidar: 4.0.3 - confbox: 0.1.8 + confbox: 0.2.2 defu: 6.1.4 - dotenv: 16.4.7 - giget: 1.2.3 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 jiti: 2.4.2 - mlly: 1.7.3 - ohash: 1.1.4 - pathe: 1.1.2 + ohash: 2.0.11 + pathe: 2.0.3 perfect-debounce: 1.0.0 - pkg-types: 1.3.0 + pkg-types: 2.2.0 rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001692: {} + caniuse-lite@1.0.30001727: {} chalk@4.1.2: dependencies: @@ -2859,13 +2622,11 @@ snapshots: chokidar@4.0.3: dependencies: - readdirp: 4.0.2 - - chownr@2.0.0: {} + readdirp: 4.1.2 citty@0.1.6: dependencies: - consola: 3.4.0 + consola: 3.4.2 clsx@2.1.1: {} @@ -2887,13 +2648,15 @@ snapshots: confbox@0.1.8: {} - consola@3.4.0: {} + confbox@0.2.2: {} + + consola@3.4.2: {} cookie@0.6.0: {} cron-parser@4.9.0: dependencies: - luxon: 3.5.0 + luxon: 3.7.1 cross-spawn@7.0.6: dependencies: @@ -2903,7 +2666,7 @@ snapshots: cssesc@3.0.0: {} - debug@4.4.0: + debug@4.4.1: dependencies: ms: 2.1.3 @@ -2915,26 +2678,47 @@ snapshots: delayed-stream@1.0.0: {} - destr@2.0.3: {} + destr@2.0.5: {} devalue@5.1.1: {} - dexie@4.0.10: {} + dexie@4.0.11: {} didyoumean@1.2.2: {} dlv@1.1.3: {} - dotenv@16.4.7: {} + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.79: {} + electron-to-chromium@1.5.182: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -2961,107 +2745,70 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 - escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@2.4.2)): + eslint-config-prettier@10.1.5(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.17.0(jiti@2.4.2) - semver: 7.6.3 + eslint: 9.30.1(jiti@2.4.2) - eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@2.4.2)): + eslint-plugin-svelte@3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.6): dependencies: - eslint: 9.17.0(jiti@2.4.2) - - eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.19.1): - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) - '@jridgewell/sourcemap-codec': 1.5.0 - eslint: 9.17.0(jiti@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@jridgewell/sourcemap-codec': 1.5.4 + eslint: 9.30.1(jiti@2.4.2) esutils: 2.0.3 - known-css-properties: 0.35.0 - postcss: 8.4.49 - postcss-load-config: 3.1.4(postcss@8.4.49) - postcss-safe-parser: 6.0.0(postcss@8.4.49) - postcss-selector-parser: 6.1.2 - semver: 7.6.3 - svelte-eslint-parser: 0.43.0(svelte@5.19.1) + globals: 16.3.0 + known-css-properties: 0.37.0 + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6) + postcss-safe-parser: 7.0.1(postcss@8.5.6) + semver: 7.7.2 + svelte-eslint-parser: 1.2.0(svelte@5.35.6) optionalDependencies: - svelte: 5.19.1 + svelte: 5.35.6 transitivePeerDependencies: - ts-node - eslint-plugin-tailwindcss@3.17.5(tailwindcss@3.4.17): + eslint-plugin-tailwindcss@3.18.0(tailwindcss@3.4.17): dependencies: fast-glob: 3.3.3 - postcss: 8.4.49 + postcss: 8.5.6 tailwindcss: 3.4.17 - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-scope@8.2.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.17.0(jiti@2.4.2): + eslint@9.30.1(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.9.1 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.17.0 - '@eslint/plugin-kit': 0.2.4 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.30.1 + '@eslint/plugin-kit': 0.3.3 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3083,25 +2830,19 @@ snapshots: esm-env@1.2.2: {} - espree@10.3.0: + espree@10.4.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 - - espree@9.6.1: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 3.4.3 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 esquery@1.6.0: dependencies: estraverse: 5.3.0 - esrap@1.4.3: + esrap@2.1.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 esrecurse@4.3.0: dependencies: @@ -3113,21 +2854,11 @@ snapshots: esutils@2.0.3: {} - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - exifreader@4.26.0: + exifreader@4.31.1: optionalDependencies: - '@xmldom/xmldom': 0.9.6 + '@xmldom/xmldom': 0.9.8 + + exsolve@1.0.7: {} fast-deep-equal@3.1.3: {} @@ -3143,11 +2874,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fastq@1.18.0: + fastq@1.19.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 - fdir@6.4.2(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -3168,51 +2899,59 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 - flatted@3.3.2: {} + flatted@3.3.3: {} follow-redirects@1.15.9: {} - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.1: + form-data@4.0.3: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 fraction.js@4.3.7: {} - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fsevents@2.3.3: optional: true function-bind@1.1.2: {} - get-stream@8.0.1: {} - - get-tsconfig@4.8.1: + get-intrinsic@1.3.0: dependencies: - resolve-pkg-maps: 1.0.0 + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 - giget@1.2.3: + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + giget@2.0.0: dependencies: citty: 0.1.6 - consola: 3.4.0 + consola: 3.4.2 defu: 6.1.4 - node-fetch-native: 1.6.4 - nypm: 0.3.12 - ohash: 1.1.4 - pathe: 1.1.2 - tar: 6.2.1 + node-fetch-native: 1.6.6 + nypm: 0.6.0 + pathe: 2.0.3 glob-parent@5.1.2: dependencies: @@ -3224,7 +2963,7 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 @@ -3233,33 +2972,37 @@ snapshots: globals@14.0.0: {} - globals@15.14.0: {} + globals@15.15.0: {} - globalyzer@0.1.0: {} + globals@16.3.0: {} - globrex@0.1.2: {} + gopd@1.2.0: {} graphemer@1.4.0: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 heic2any@0.0.4: {} - human-signals@5.0.0: {} - ignore@5.3.2: {} - import-fresh@3.3.0: + ignore@7.0.5: {} + + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-meta-resolve@4.1.0: {} - imurmurhash@0.1.4: {} is-binary-path@2.1.0: @@ -3284,13 +3027,11 @@ snapshots: is-reference@1.2.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 is-reference@3.0.3: dependencies: - '@types/estree': 1.0.6 - - is-stream@3.0.0: {} + '@types/estree': 1.0.8 isexe@2.0.0: {} @@ -3320,26 +3061,26 @@ snapshots: kleur@4.1.5: {} - known-css-properties@0.35.0: {} + known-css-properties@0.37.0: {} kolorist@1.8.0: {} - kysely-ctl@0.10.1(kysely@0.27.5): + kysely-ctl@0.13.1(kysely@0.28.2): dependencies: - c12: 2.0.1 + c12: 3.0.4 citty: 0.1.6 - consola: 3.4.0 - kysely: 0.27.5 - nypm: 0.4.1 + consola: 3.4.2 + jiti: 2.4.2 + kysely: 0.28.2 + nypm: 0.6.0 ofetch: 1.4.1 - pathe: 1.1.2 - pkg-types: 1.3.0 - std-env: 3.8.0 - tsx: 4.19.2 + pathe: 2.0.3 + pkg-types: 2.2.0 + std-env: 3.9.0 transitivePeerDependencies: - magicast - kysely@0.27.5: {} + kysely@0.28.2: {} levn@0.4.1: dependencies: @@ -3352,10 +3093,11 @@ snapshots: lines-and-columns@1.2.4: {} - local-pkg@0.5.1: + local-pkg@1.1.1: dependencies: - mlly: 1.7.3 - pkg-types: 1.3.0 + mlly: 1.7.4 + pkg-types: 2.2.0 + quansync: 0.2.10 locate-character@3.0.0: {} @@ -3369,13 +3111,15 @@ snapshots: lru-cache@10.4.3: {} - luxon@3.5.0: {} + lru-cache@11.1.0: {} + + luxon@3.7.1: {} magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 - merge-stream@2.0.0: {} + math-intrinsics@1.1.0: {} merge2@1.4.1: {} @@ -3390,43 +3134,28 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@4.0.6: {} - - mimic-fn@4.0.0: {} + mime@4.0.7: {} minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} + brace-expansion: 2.0.2 minipass@7.1.2: {} - minizlib@2.1.2: + mlly@1.7.4: dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp@1.0.4: {} - - mlly@1.7.3: - dependencies: - acorn: 8.14.0 - pathe: 1.1.2 - pkg-types: 1.3.0 - ufo: 1.5.4 + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 mri@1.2.0: {} - mrmime@2.0.0: {} + mrmime@2.0.1: {} ms@2.1.3: {} @@ -3436,13 +3165,13 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.8: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} - node-addon-api@8.3.0: {} + node-addon-api@8.4.0: {} - node-fetch-native@1.6.4: {} + node-fetch-native@1.6.6: {} node-gyp-build@4.8.4: {} @@ -3458,45 +3187,25 @@ snapshots: normalize-range@0.1.2: {} - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - nypm@0.3.12: + nypm@0.6.0: dependencies: citty: 0.1.6 - consola: 3.4.0 - execa: 8.0.1 - pathe: 1.1.2 - pkg-types: 1.3.0 - ufo: 1.5.4 - - nypm@0.4.1: - dependencies: - citty: 0.1.6 - consola: 3.4.0 - pathe: 1.1.2 - pkg-types: 1.3.0 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.2.0 tinyexec: 0.3.2 - ufo: 1.5.4 object-assign@4.1.1: {} object-hash@3.0.0: {} - obuf@1.1.2: {} - ofetch@1.4.1: dependencies: - destr: 2.0.3 - node-fetch-native: 1.6.4 - ufo: 1.5.4 + destr: 2.0.5 + node-fetch-native: 1.6.6 + ufo: 1.6.1 - ohash@1.1.4: {} - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 + ohash@2.0.11: {} optionator@0.9.4: dependencies: @@ -3513,7 +3222,7 @@ snapshots: p-limit@6.2.0: dependencies: - yocto-queue: 1.1.1 + yocto-queue: 1.2.1 p-locate@5.0.0: dependencies: @@ -3521,7 +3230,7 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.8: {} + package-manager-detector@1.3.0: {} parent-module@1.0.1: dependencies: @@ -3531,8 +3240,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -3540,24 +3247,22 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - pathe@1.1.2: {} + pathe@2.0.3: {} perfect-debounce@1.0.0: {} - pg-cloudflare@1.1.1: + pg-cloudflare@1.2.7: optional: true - pg-connection-string@2.7.0: {} + pg-connection-string@2.9.1: {} pg-int8@1.0.1: {} - pg-numeric@1.0.2: {} - - pg-pool@3.7.0(pg@8.13.1): + pg-pool@3.10.1(pg@8.16.3): dependencies: - pg: 8.13.1 + pg: 8.16.3 - pg-protocol@1.7.0: {} + pg-protocol@1.10.3: {} pg-types@2.2.0: dependencies: @@ -3567,25 +3272,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg-types@4.0.2: + pg@8.16.3: dependencies: - pg-int8: 1.0.1 - pg-numeric: 1.0.2 - postgres-array: 3.0.2 - postgres-bytea: 3.0.0 - postgres-date: 2.1.0 - postgres-interval: 3.0.0 - postgres-range: 1.1.4 - - pg@8.13.1: - dependencies: - pg-connection-string: 2.7.0 - pg-pool: 3.7.0(pg@8.13.1) - pg-protocol: 1.7.0 + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.1.1 + pg-cloudflare: 1.2.7 pgpass@1.0.5: dependencies: @@ -3599,113 +3294,114 @@ snapshots: pify@2.3.0: {} - pirates@4.0.6: {} + pirates@4.0.7: {} - pkg-types@1.3.0: + pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.7.3 - pathe: 1.1.2 + mlly: 1.7.4 + pathe: 2.0.3 - postcss-import@15.1.0(postcss@8.4.49): + pkg-types@2.2.0: dependencies: - postcss: 8.4.49 + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.4.49): + postcss-js@4.0.1(postcss@8.5.6): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.49 + postcss: 8.5.6 - postcss-load-config@3.1.4(postcss@8.4.49): + postcss-load-config@3.1.4(postcss@8.5.6): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.49 + postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.4.49): + postcss-load-config@4.0.2(postcss@8.5.6): dependencies: lilconfig: 3.1.3 - yaml: 2.7.0 + yaml: 2.8.0 optionalDependencies: - postcss: 8.4.49 + postcss: 8.5.6 - postcss-nested@6.2.0(postcss@8.4.49): + postcss-nested@6.2.0(postcss@8.5.6): dependencies: - postcss: 8.4.49 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-safe-parser@6.0.0(postcss@8.4.49): + postcss-safe-parser@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.4.49 + postcss: 8.5.6 - postcss-scss@4.0.9(postcss@8.4.49): + postcss-scss@4.0.9(postcss@8.5.6): dependencies: - postcss: 8.4.49 + postcss: 8.5.6 postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} - postcss@8.4.49: + postcss@8.5.6: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 postgres-array@2.0.0: {} - postgres-array@3.0.2: {} - postgres-bytea@1.0.0: {} - postgres-bytea@3.0.0: - dependencies: - obuf: 1.1.2 - postgres-date@1.0.7: {} - postgres-date@2.1.0: {} - postgres-interval@1.2.0: dependencies: xtend: 4.0.2 - postgres-interval@3.0.0: {} - - postgres-range@1.1.4: {} - prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.6): dependencies: - prettier: 3.4.2 - svelte: 5.19.1 + prettier: 3.6.2 + svelte: 5.35.6 - prettier-plugin-tailwindcss@0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1))(prettier@3.4.2): + prettier-plugin-tailwindcss@0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.6))(prettier@3.6.2): dependencies: - prettier: 3.4.2 + prettier: 3.6.2 optionalDependencies: - prettier-plugin-svelte: 3.3.2(prettier@3.4.2)(svelte@5.19.1) + prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@5.35.6) - prettier@3.4.2: {} + prettier@3.6.2: {} proxy-from-env@1.1.0: {} punycode@2.3.1: {} + quansync@0.2.10: {} + queue-microtask@1.2.3: {} rc9@2.1.2: dependencies: defu: 6.1.4 - destr: 2.0.3 + destr: 2.0.5 read-cache@1.0.0: dependencies: @@ -3715,43 +3411,42 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@4.0.2: {} + readdirp@4.1.2: {} resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve@1.22.10: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.0.4: {} + reusify@1.1.0: {} - rollup@4.30.1: + rollup@4.44.2: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.30.1 - '@rollup/rollup-android-arm64': 4.30.1 - '@rollup/rollup-darwin-arm64': 4.30.1 - '@rollup/rollup-darwin-x64': 4.30.1 - '@rollup/rollup-freebsd-arm64': 4.30.1 - '@rollup/rollup-freebsd-x64': 4.30.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 - '@rollup/rollup-linux-arm-musleabihf': 4.30.1 - '@rollup/rollup-linux-arm64-gnu': 4.30.1 - '@rollup/rollup-linux-arm64-musl': 4.30.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 - '@rollup/rollup-linux-riscv64-gnu': 4.30.1 - '@rollup/rollup-linux-s390x-gnu': 4.30.1 - '@rollup/rollup-linux-x64-gnu': 4.30.1 - '@rollup/rollup-linux-x64-musl': 4.30.1 - '@rollup/rollup-win32-arm64-msvc': 4.30.1 - '@rollup/rollup-win32-ia32-msvc': 4.30.1 - '@rollup/rollup-win32-x64-msvc': 4.30.1 + '@rollup/rollup-android-arm-eabi': 4.44.2 + '@rollup/rollup-android-arm64': 4.44.2 + '@rollup/rollup-darwin-arm64': 4.44.2 + '@rollup/rollup-darwin-x64': 4.44.2 + '@rollup/rollup-freebsd-arm64': 4.44.2 + '@rollup/rollup-freebsd-x64': 4.44.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.2 + '@rollup/rollup-linux-arm-musleabihf': 4.44.2 + '@rollup/rollup-linux-arm64-gnu': 4.44.2 + '@rollup/rollup-linux-arm64-musl': 4.44.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.2 + '@rollup/rollup-linux-riscv64-gnu': 4.44.2 + '@rollup/rollup-linux-riscv64-musl': 4.44.2 + '@rollup/rollup-linux-s390x-gnu': 4.44.2 + '@rollup/rollup-linux-x64-gnu': 4.44.2 + '@rollup/rollup-linux-x64-musl': 4.44.2 + '@rollup/rollup-win32-arm64-msvc': 4.44.2 + '@rollup/rollup-win32-ia32-msvc': 4.44.2 + '@rollup/rollup-win32-x64-msvc': 4.44.2 fsevents: 2.3.3 run-parallel@1.2.0: @@ -3762,7 +3457,7 @@ snapshots: dependencies: mri: 1.2.0 - semver@7.6.3: {} + semver@7.7.2: {} set-cookie-parser@2.7.1: {} @@ -3774,10 +3469,10 @@ snapshots: signal-exit@4.1.0: {} - sirv@3.0.0: + sirv@3.0.1: dependencies: - '@polka/url': 1.0.0-next.28 - mrmime: 2.0.0 + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 totalist: 3.0.1 sorted-array-functions@1.3.0: {} @@ -3786,7 +3481,7 @@ snapshots: split2@4.2.0: {} - std-env@3.8.0: {} + std-env@3.9.0: {} string-width@4.2.3: dependencies: @@ -3808,18 +3503,16 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-final-newline@3.0.0: {} - strip-json-comments@3.1.1: {} sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/gen-mapping': 0.3.12 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.6 + pirates: 4.0.7 ts-interface-checker: 0.1.13 supports-color@7.2.0: @@ -3828,40 +3521,41 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.1.3(picomatch@4.0.2)(svelte@5.19.1)(typescript@5.7.3): + svelte-check@4.2.2(picomatch@4.0.2)(svelte@5.35.6)(typescript@5.8.3): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.29 chokidar: 4.0.3 - fdir: 6.4.2(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.19.1 - typescript: 5.7.3 + svelte: 5.35.6 + typescript: 5.8.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@0.43.0(svelte@5.19.1): + svelte-eslint-parser@1.2.0(svelte@5.35.6): dependencies: - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - postcss: 8.4.49 - postcss-scss: 4.0.9(postcss@8.4.49) + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) + postcss-selector-parser: 7.1.0 optionalDependencies: - svelte: 5.19.1 + svelte: 5.35.6 - svelte@5.19.1: + svelte@5.35.6: dependencies: '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.14.0 - acorn-typescript: 1.4.13(acorn@8.14.0) + '@jridgewell/sourcemap-codec': 1.5.4 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 esm-env: 1.2.2 - esrap: 1.4.3 + esrap: 2.1.0 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.17 @@ -3883,26 +3577,17 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.4.49 - postcss-import: 15.1.0(postcss@8.4.49) - postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.0.1(postcss@8.5.6) + postcss-load-config: 4.0.2(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.10 sucrase: 3.35.0 transitivePeerDependencies: - ts-node - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -3911,74 +3596,63 @@ snapshots: dependencies: any-promise: 1.3.0 - tiny-glob@0.2.9: - dependencies: - globalyzer: 0.1.0 - globrex: 0.1.2 - tinyexec@0.3.2: {} + tinyexec@1.0.1: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 totalist@3.0.1: {} - ts-api-utils@2.0.0(typescript@5.7.3): + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: - typescript: 5.7.3 + typescript: 5.8.3 ts-interface-checker@0.1.13: {} - tsx@4.19.2: - dependencies: - esbuild: 0.23.1 - get-tsconfig: 4.8.1 - optionalDependencies: - fsevents: 2.3.3 - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3): + typescript-eslint@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) - eslint: 9.17.0(jiti@2.4.2) - typescript: 5.7.3 + '@typescript-eslint/eslint-plugin': 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - typescript@5.7.3: {} + typescript@5.8.3: {} - ufo@1.5.4: {} + ufo@1.6.1: {} - undici-types@6.20.0: {} + undici-types@7.8.0: {} - unplugin-icons@0.22.0(svelte@5.19.1): + unplugin-icons@22.1.0(svelte@5.35.6): dependencies: - '@antfu/install-pkg': 0.5.0 - '@antfu/utils': 0.7.10 - '@iconify/utils': 2.2.1 - debug: 4.4.0 - kolorist: 1.8.0 - local-pkg: 0.5.1 - unplugin: 2.1.2 + '@antfu/install-pkg': 1.1.0 + '@iconify/utils': 2.3.0 + debug: 4.4.1 + local-pkg: 1.1.1 + unplugin: 2.3.5 optionalDependencies: - svelte: 5.19.1 + svelte: 5.35.6 transitivePeerDependencies: - supports-color - unplugin@2.1.2: + unplugin@2.3.5: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 + picomatch: 4.0.2 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: - browserslist: 4.24.4 + browserslist: 4.25.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -3988,20 +3662,20 @@ snapshots: util-deprecate@1.0.2: {} - uuid@11.0.4: {} + uuid@11.1.0: {} - vite@5.4.11(@types/node@22.10.5): + vite@5.4.19(@types/node@24.0.13): dependencies: esbuild: 0.21.5 - postcss: 8.4.49 - rollup: 4.30.1 + postcss: 8.5.6 + rollup: 4.44.2 optionalDependencies: - '@types/node': 22.10.5 + '@types/node': 24.0.13 fsevents: 2.3.3 - vitefu@1.0.5(vite@5.4.11(@types/node@22.10.5)): + vitefu@1.1.1(vite@5.4.19(@types/node@24.0.13)): optionalDependencies: - vite: 5.4.11(@types/node@22.10.5) + vite: 5.4.19(@types/node@24.0.13) webpack-virtual-modules@0.6.2: {} @@ -4025,16 +3699,14 @@ snapshots: xtend@4.0.2: {} - yallist@4.0.0: {} - yaml@1.10.2: {} - yaml@2.7.0: {} + yaml@2.8.0: {} yocto-queue@0.1.0: {} - yocto-queue@1.1.1: {} + yocto-queue@1.2.1: {} zimmerframe@1.1.2: {} - zod@3.24.1: {} + zod@3.25.76: {} diff --git a/src/hooks.client.ts b/src/hooks.client.ts index ec6f620..99e11c9 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -4,6 +4,15 @@ import { prepareFileCache } from "$lib/modules/file"; import { prepareOpfs } from "$lib/modules/opfs"; import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores"; +const requestPersistentStorage = async () => { + const isPersistent = await navigator.storage.persist(); + if (isPersistent) { + console.log("[ArkVault] Persistent storage granted."); + } else { + console.warn("[ArkVault] Persistent storage not granted."); + } +}; + const prepareClientKeyStore = async () => { const [encryptKey, decryptKey, signKey, verifyKey] = await Promise.all([ getClientKey("encrypt"), @@ -32,6 +41,7 @@ const prepareHmacSecretStore = async () => { export const init: ClientInit = async () => { await Promise.all([ + requestPersistentStorage(), prepareFileCache(), prepareClientKeyStore(), prepareMasterKeyStore(), diff --git a/src/lib/components/atoms/divs/FullscreenDiv.svelte b/src/lib/components/atoms/divs/FullscreenDiv.svelte index c90e02c..4bb1cc0 100644 --- a/src/lib/components/atoms/divs/FullscreenDiv.svelte +++ b/src/lib/components/atoms/divs/FullscreenDiv.svelte @@ -1,7 +1,15 @@ -
+
{@render children()}
diff --git a/src/lib/components/molecules/ActionModal.svelte b/src/lib/components/molecules/ActionModal.svelte index a351403..6fe06be 100644 --- a/src/lib/components/molecules/ActionModal.svelte +++ b/src/lib/components/molecules/ActionModal.svelte @@ -12,6 +12,7 @@ confirmText: string; isOpen: boolean; onbeforeclose?: () => void; + oncancel?: () => void; onConfirmClick: ConfirmHandler; title: string; } @@ -22,6 +23,7 @@ confirmText, isOpen = $bindable(), onbeforeclose, + oncancel, onConfirmClick, title, }: Props = $props(); @@ -31,6 +33,11 @@ isOpen = false; }; + const cancelAction = () => { + oncancel?.(); + closeModal(); + }; + const confirmAction = async () => { if ((await onConfirmClick()) !== false) { closeModal(); @@ -38,13 +45,13 @@ }; - +

{title}

{@render children()}
- +
diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index 5d4fb81..319e0df 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -10,19 +10,38 @@ name: string; subtext?: string; textClass?: ClassValue; + thumbnail?: string; type: "directory" | "file"; } - let { class: className, name, subtext, textClass: textClassName, type }: Props = $props(); + let { + class: className, + name, + subtext, + textClass: textClassName, + thumbnail, + type, + }: Props = $props(); +{#snippet iconSnippet()} +
+ {#if thumbnail} + {name} + {:else if type === "directory"} + + {:else} + + {/if} +
+{/snippet} + {#snippet subtextSnippet()} {subtext} {/snippet} ; + icon?: Component; iconClass?: ClassValue; + iconSnippet?: Snippet; subtext?: Snippet; textClass?: ClassValue; } @@ -16,15 +17,22 @@ class: className, icon: Icon, iconClass: iconClassName, + iconSnippet, subtext, textClass: textClassName, }: Props = $props();
-
- -
+ {#if iconSnippet} +
+ {@render iconSnippet()} +
+ {:else if Icon} +
+ +
+ {/if}

{@render children()} diff --git a/src/lib/components/organisms/Category/File.svelte b/src/lib/components/organisms/Category/File.svelte index 5263b95..7d49cf3 100644 --- a/src/lib/components/organisms/Category/File.svelte +++ b/src/lib/components/organisms/Category/File.svelte @@ -3,7 +3,7 @@ import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; import type { FileInfo } from "$lib/modules/filesystem"; - import type { SelectedFile } from "./service"; + import { requestFileThumbnailDownload, type SelectedFile } from "./service"; import IconClose from "~icons/material-symbols/close"; @@ -15,6 +15,8 @@ let { info, onclick, onRemoveClick }: Props = $props(); + let thumbnail: string | undefined = $state(); + const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info as FileInfo; if (!dataKey || !dataKeyVersion) return; // TODO: Error handling @@ -28,6 +30,21 @@ onRemoveClick!({ id, dataKey, dataKeyVersion, name }); }; + + $effect(() => { + if ($info?.dataKey) { + requestFileThumbnailDownload($info.id, $info.dataKey) + .then((thumbnailUrl) => { + thumbnail = thumbnailUrl ?? undefined; + }) + .catch(() => { + // TODO: Error Handling + thumbnail = undefined; + }); + } else { + thumbnail = undefined; + } + }); {#if $info} @@ -37,6 +54,6 @@ actionButtonIcon={onRemoveClick && IconClose} onActionButtonClick={removeFile} > - + {/if} diff --git a/src/lib/components/organisms/Category/service.ts b/src/lib/components/organisms/Category/service.ts index 1d587b5..fb6e640 100644 --- a/src/lib/components/organisms/Category/service.ts +++ b/src/lib/components/organisms/Category/service.ts @@ -1,3 +1,5 @@ +export { requestFileThumbnailDownload } from "$lib/services/file"; + export interface SelectedFile { id: number; dataKey: CryptoKey; diff --git a/src/lib/components/organisms/modals/ForceLoginModal.svelte b/src/lib/components/organisms/modals/ForceLoginModal.svelte new file mode 100644 index 0000000..e6aa82b --- /dev/null +++ b/src/lib/components/organisms/modals/ForceLoginModal.svelte @@ -0,0 +1,22 @@ + + + +

다른 디바이스에서는 로그아웃하고, 이 디바이스에서 로그인할까요?

+ diff --git a/src/lib/components/organisms/modals/index.ts b/src/lib/components/organisms/modals/index.ts index 2fa9422..56c64a1 100644 --- a/src/lib/components/organisms/modals/index.ts +++ b/src/lib/components/organisms/modals/index.ts @@ -1,3 +1,4 @@ export { default as CategoryCreateModal } from "./CategoryCreateModal.svelte"; +export { default as ForceLoginModal } from "./ForceLoginModal.svelte"; export { default as RenameModal } from "./RenameModal.svelte"; export { default as TextInputModal } from "./TextInputModal.svelte"; diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index 293c16d..1c2c060 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -55,6 +55,10 @@ export const deleteDirectoryInfo = async (id: number) => { await filesystem.directory.delete(id); }; +export const getAllFileInfos = async () => { + return await filesystem.file.toArray(); +}; + export const getFileInfos = async (parentId: DirectoryId) => { return await filesystem.file.where({ parentId }).toArray(); }; diff --git a/src/lib/modules/crypto/rsa.ts b/src/lib/modules/crypto/rsa.ts index 4df8f9e..13dfd46 100644 --- a/src/lib/modules/crypto/rsa.ts +++ b/src/lib/modules/crypto/rsa.ts @@ -46,6 +46,56 @@ export const exportRSAKeyToBase64 = async (key: CryptoKey) => { return encodeToBase64((await exportRSAKey(key)).key); }; +export const importEncryptionKeyPairFromBase64 = async ( + encryptKeyBase64: string, + decryptKeyBase64: string, +) => { + const algorithm: RsaHashedImportParams = { + name: "RSA-OAEP", + hash: "SHA-256", + }; + const encryptKey = await window.crypto.subtle.importKey( + "spki", + decodeFromBase64(encryptKeyBase64), + algorithm, + true, + ["encrypt", "wrapKey"], + ); + const decryptKey = await window.crypto.subtle.importKey( + "pkcs8", + decodeFromBase64(decryptKeyBase64), + algorithm, + true, + ["decrypt", "unwrapKey"], + ); + return { encryptKey, decryptKey }; +}; + +export const importSigningKeyPairFromBase64 = async ( + signKeyBase64: string, + verifyKeyBase64: string, +) => { + const algorithm: RsaHashedImportParams = { + name: "RSA-PSS", + hash: "SHA-256", + }; + const signKey = await window.crypto.subtle.importKey( + "pkcs8", + decodeFromBase64(signKeyBase64), + algorithm, + true, + ["sign"], + ); + const verifyKey = await window.crypto.subtle.importKey( + "spki", + decodeFromBase64(verifyKeyBase64), + algorithm, + true, + ["verify"], + ); + return { signKey, verifyKey }; +}; + export const makeRSAKeyNonextractable = async (key: CryptoKey) => { const { key: exportedKey, format } = await exportRSAKey(key); return await window.crypto.subtle.importKey( diff --git a/src/lib/modules/file/cache.ts b/src/lib/modules/file/cache.ts index fe3c66c..ccb187e 100644 --- a/src/lib/modules/file/cache.ts +++ b/src/lib/modules/file/cache.ts @@ -1,12 +1,15 @@ +import { LRUCache } from "lru-cache"; import { getFileCacheIndex as getFileCacheIndexFromIndexedDB, storeFileCacheIndex, deleteFileCacheIndex, type FileCacheIndex, } from "$lib/indexedDB"; -import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; +import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; const fileCacheIndex = new Map(); +const loadedThumbnails = new LRUCache({ max: 100 }); export const prepareFileCache = async () => { for (const cache of await getFileCacheIndexFromIndexedDB()) { @@ -48,3 +51,30 @@ export const deleteFileCache = async (fileId: number) => { await deleteFile(`/cache/${fileId}`); await deleteFileCacheIndex(fileId); }; + +export const getFileThumbnailCache = async (fileId: number) => { + const thumbnail = loadedThumbnails.get(fileId); + if (thumbnail) return thumbnail; + + const thumbnailBuffer = await readFile(`/thumbnail/file/${fileId}`); + if (!thumbnailBuffer) return null; + + const thumbnailUrl = getThumbnailUrl(thumbnailBuffer); + loadedThumbnails.set(fileId, thumbnailUrl); + return thumbnailUrl; +}; + +export const storeFileThumbnailCache = async (fileId: number, thumbnailBuffer: ArrayBuffer) => { + await writeFile(`/thumbnail/file/${fileId}`, thumbnailBuffer); + loadedThumbnails.set(fileId, getThumbnailUrl(thumbnailBuffer)); +}; + +export const deleteFileThumbnailCache = async (fileId: number) => { + loadedThumbnails.delete(fileId); + await deleteFile(`/thumbnail/file/${fileId}`); +}; + +export const deleteAllFileThumbnailCaches = async () => { + loadedThumbnails.clear(); + await deleteDirectory("/thumbnail/file"); +}; diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 71a38fb..b5b00a1 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -11,9 +11,11 @@ import { digestMessage, signMessageHmac, } from "$lib/modules/crypto"; +import { generateThumbnail } from "$lib/modules/thumbnail"; import type { DuplicateFileScanRequest, DuplicateFileScanResponse, + FileThumbnailUploadRequest, FileUploadRequest, FileUploadResponse, } from "$lib/server/schemas"; @@ -106,6 +108,10 @@ const encryptFile = limitFunction( createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); const lastModifiedAtEncrypted = await encryptString(file.lastModified.toString(), dataKey); + const thumbnail = await generateThumbnail(fileBuffer, fileType); + const thumbnailBuffer = await thumbnail?.arrayBuffer(); + const thumbnailEncrypted = thumbnailBuffer && (await encryptData(thumbnailBuffer, dataKey)); + status.update((value) => { value.status = "upload-pending"; return value; @@ -120,13 +126,14 @@ const encryptFile = limitFunction( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, + thumbnail: thumbnailEncrypted && { plaintext: thumbnailBuffer, ...thumbnailEncrypted }, }; }, { concurrency: 4 }, ); const requestFileUpload = limitFunction( - async (status: Writable, form: FormData) => { + async (status: Writable, form: FormData, thumbnailForm: FormData | null) => { status.update((value) => { value.status = "uploading"; return value; @@ -144,6 +151,15 @@ const requestFileUpload = limitFunction( }); const { file }: FileUploadResponse = res.data; + if (thumbnailForm) { + try { + await axios.post(`/api/file/${file}/thumbnail/upload`, thumbnailForm); + } catch (e) { + // TODO + console.error(e); + } + } + status.update((value) => { value.status = "uploaded"; return value; @@ -160,7 +176,9 @@ export const uploadFile = async ( hmacSecret: HmacSecret, masterKey: MasterKey, onDuplicate: () => Promise, -): Promise<{ fileId: number; fileBuffer: ArrayBuffer } | undefined> => { +): Promise< + { fileId: number; fileBuffer: ArrayBuffer; thumbnailBuffer?: ArrayBuffer } | undefined +> => { const status = writable({ name: file.name, parentId, @@ -198,6 +216,7 @@ export const uploadFile = async ( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, + thumbnail, } = await encryptFile(status, file, fileBuffer, masterKey); const form = new FormData(); @@ -218,13 +237,26 @@ export const uploadFile = async ( createdAtIv: createdAtEncrypted?.iv, lastModifiedAt: lastModifiedAtEncrypted.ciphertext, lastModifiedAtIv: lastModifiedAtEncrypted.iv, - } as FileUploadRequest), + } satisfies FileUploadRequest), ); form.set("content", new Blob([fileEncrypted.ciphertext])); form.set("checksum", fileEncryptedHash); - const { fileId } = await requestFileUpload(status, form); - return { fileId, fileBuffer }; + let thumbnailForm = null; + if (thumbnail) { + thumbnailForm = new FormData(); + thumbnailForm.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnail.iv, + } satisfies FileThumbnailUploadRequest), + ); + thumbnailForm.set("content", new Blob([thumbnail.ciphertext])); + } + + const { fileId } = await requestFileUpload(status, form, thumbnailForm); + return { fileId, fileBuffer, thumbnailBuffer: thumbnail?.plaintext }; } catch (e) { status.update((value) => { value.status = "error"; diff --git a/src/lib/modules/key.ts b/src/lib/modules/key.ts new file mode 100644 index 0000000..945902c --- /dev/null +++ b/src/lib/modules/key.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; +import { storeClientKey } from "$lib/indexedDB"; +import type { ClientKeys } from "$lib/stores"; + +const serializedClientKeysSchema = z.intersection( + z.object({ + generator: z.literal("ArkVault"), + exportedAt: z.string().datetime(), + }), + z.object({ + version: z.literal(1), + encryptKey: z.string().base64().nonempty(), + decryptKey: z.string().base64().nonempty(), + signKey: z.string().base64().nonempty(), + verifyKey: z.string().base64().nonempty(), + }), +); + +type SerializedClientKeys = z.infer; + +type DeserializedClientKeys = { + encryptKeyBase64: string; + decryptKeyBase64: string; + signKeyBase64: string; + verifyKeyBase64: string; +}; + +export const serializeClientKeys = ({ + encryptKeyBase64, + decryptKeyBase64, + signKeyBase64, + verifyKeyBase64, +}: DeserializedClientKeys) => { + return JSON.stringify({ + version: 1, + generator: "ArkVault", + exportedAt: new Date().toISOString(), + encryptKey: encryptKeyBase64, + decryptKey: decryptKeyBase64, + signKey: signKeyBase64, + verifyKey: verifyKeyBase64, + } satisfies SerializedClientKeys); +}; + +export const deserializeClientKeys = (serialized: string) => { + const zodRes = serializedClientKeysSchema.safeParse(JSON.parse(serialized)); + if (zodRes.success) { + return { + encryptKeyBase64: zodRes.data.encryptKey, + decryptKeyBase64: zodRes.data.decryptKey, + signKeyBase64: zodRes.data.signKey, + verifyKeyBase64: zodRes.data.verifyKey, + } satisfies DeserializedClientKeys; + } + return undefined; +}; + +export const storeClientKeys = async (clientKeys: ClientKeys) => { + await Promise.all([ + storeClientKey(clientKeys.encryptKey, "encrypt"), + storeClientKey(clientKeys.decryptKey, "decrypt"), + storeClientKey(clientKeys.signKey, "sign"), + storeClientKey(clientKeys.verifyKey, "verify"), + ]); +}; diff --git a/src/lib/modules/opfs.ts b/src/lib/modules/opfs.ts index 5ac70da..41f1f72 100644 --- a/src/lib/modules/opfs.ts +++ b/src/lib/modules/opfs.ts @@ -59,3 +59,39 @@ export const deleteFile = async (path: string) => { await parentHandle.removeEntry(filename); }; + +const getDirectoryHandle = async (path: string) => { + if (!rootHandle) { + throw new Error("OPFS not prepared"); + } else if (path[0] !== "/") { + throw new Error("Path must be absolute"); + } + + const parts = path.split("/"); + if (parts.length <= 1) { + throw new Error("Invalid path"); + } + + try { + let directoryHandle = rootHandle; + let parentHandle; + for (const part of parts.slice(1)) { + if (!part) continue; + parentHandle = directoryHandle; + directoryHandle = await directoryHandle.getDirectoryHandle(part); + } + return { directoryHandle, parentHandle }; + } catch (e) { + if (e instanceof DOMException && e.name === "NotFoundError") { + return {}; + } + throw e; + } +}; + +export const deleteDirectory = async (path: string) => { + const { directoryHandle, parentHandle } = await getDirectoryHandle(path); + if (!parentHandle) return; + + await parentHandle.removeEntry(directoryHandle.name, { recursive: true }); +}; diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts new file mode 100644 index 0000000..9c009bd --- /dev/null +++ b/src/lib/modules/thumbnail.ts @@ -0,0 +1,114 @@ +import { encodeToBase64 } from "$lib/modules/crypto"; + +const scaleSize = (width: number, height: number, targetSize: number) => { + if (width <= targetSize || height <= targetSize) { + return { width, height }; + } + + const scale = targetSize / Math.min(width, height); + return { + width: Math.round(width * scale), + height: Math.round(height * scale), + }; +}; + +const capture = ( + width: number, + height: number, + drawer: (context: CanvasRenderingContext2D, width: number, height: number) => void, + targetSize = 250, +) => { + return new Promise((resolve, reject) => { + const canvas = document.createElement("canvas"); + const { width: scaledWidth, height: scaledHeight } = scaleSize(width, height, targetSize); + + canvas.width = scaledWidth; + canvas.height = scaledHeight; + + const context = canvas.getContext("2d"); + if (!context) { + return reject(new Error("Failed to generate thumbnail")); + } + + drawer(context, scaledWidth, scaledHeight); + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error("Failed to generate thumbnail")); + } + }, "image/webp"); + }); +}; + +const generateImageThumbnail = (imageUrl: string) => { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => { + capture(image.width, image.height, (context, width, height) => { + context.drawImage(image, 0, 0, width, height); + }) + .then(resolve) + .catch(reject); + }; + image.onerror = reject; + + image.src = imageUrl; + }); +}; + +export const captureVideoThumbnail = (video: HTMLVideoElement) => { + return capture(video.videoWidth, video.videoHeight, (context, width, height) => { + context.drawImage(video, 0, 0, width, height); + }); +}; + +const generateVideoThumbnail = (videoUrl: string, time = 0) => { + return new Promise((resolve, reject) => { + const video = document.createElement("video"); + video.onloadedmetadata = () => { + video.currentTime = Math.min(time, video.duration); + video.requestVideoFrameCallback(() => { + captureVideoThumbnail(video).then(resolve).catch(reject); + }); + }; + video.onerror = reject; + + video.muted = true; + video.playsInline = true; + video.src = videoUrl; + }); +}; + +export const generateThumbnail = async (fileBuffer: ArrayBuffer, fileType: string) => { + let url; + try { + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL( + (await heic2any({ + blob: new Blob([fileBuffer], { type: fileType }), + toType: "image/png", + })) as Blob, + ); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("image/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("video/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateVideoThumbnail(url); + } + return null; + } catch { + return null; + } finally { + if (url) { + URL.revokeObjectURL(url); + } + } +}; + +export const getThumbnailUrl = (thumbnailBuffer: ArrayBuffer) => { + return `data:image/webp;base64,${encodeToBase64(thumbnailBuffer)}`; +}; diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index cb873c7..373357a 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -178,7 +178,7 @@ export const registerUserClientChallenge = async ( allowedIp: string, expiresAt: Date, ) => { - await db + const { id } = await db .insertInto("user_client_challenge") .values({ user_id: userId, @@ -187,19 +187,25 @@ export const registerUserClientChallenge = async ( allowed_ip: allowedIp, expires_at: expiresAt, }) - .execute(); + .returning("id") + .executeTakeFirstOrThrow(); + return { id }; }; -export const consumeUserClientChallenge = async (userId: number, answer: string, ip: string) => { +export const consumeUserClientChallenge = async ( + challengeId: number, + userId: number, + ip: string, +) => { const challenge = await db .deleteFrom("user_client_challenge") + .where("id", "=", challengeId) .where("user_id", "=", userId) - .where("answer", "=", answer) .where("allowed_ip", "=", ip) .where("expires_at", ">", new Date()) - .returning("client_id") + .returning(["client_id", "answer"]) .executeTakeFirst(); - return challenge ? { clientId: challenge.client_id } : null; + return challenge ? { clientId: challenge.client_id, answer: challenge.answer } : null; }; export const cleanupExpiredUserClientChallenges = async () => { diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 20343b2..c3169fc 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -163,16 +163,24 @@ export const unregisterDirectory = async (userId: number, directoryId: number) = .setIsolationLevel("repeatable read") // TODO: Sufficient? .execute(async (trx) => { const unregisterFiles = async (parentId: number) => { - return await trx + const files = await trx + .selectFrom("file") + .leftJoin("thumbnail", "file.id", "thumbnail.file_id") + .select(["file.id", "file.path", "thumbnail.path as thumbnailPath"]) + .where("file.parent_id", "=", parentId) + .where("file.user_id", "=", userId) + .forUpdate("file") + .execute(); + await trx .deleteFrom("file") .where("parent_id", "=", parentId) .where("user_id", "=", userId) - .returning(["id", "path"]) .execute(); + return files; }; const unregisterDirectoryRecursively = async ( directoryId: number, - ): Promise<{ id: number; path: string }[]> => { + ): Promise<{ id: number; path: string; thumbnailPath: string | null }[]> => { const files = await unregisterFiles(directoryId); const subDirectories = await trx .selectFrom("directory") @@ -327,11 +335,17 @@ export const getAllFilesByCategory = async ( .where("user_id", "=", userId) .where("file_id", "is not", null) .$narrowType<{ file_id: NotNull }>() - .orderBy(["file_id", "depth"]) + .orderBy("file_id") + .orderBy("depth") .execute(); return files.map(({ file_id, depth }) => ({ id: file_id, isRecursive: depth > 0 })); }; +export const getAllFileIds = async (userId: number) => { + const files = await db.selectFrom("file").select("id").where("user_id", "=", userId).execute(); + return files.map(({ id }) => id); +}; + export const getAllFileIdsByContentHmac = async ( userId: number, hskVersion: number, @@ -344,7 +358,7 @@ export const getAllFileIdsByContentHmac = async ( .where("hmac_secret_key_version", "=", hskVersion) .where("content_hmac", "=", contentHmac) .execute(); - return files.map(({ id }) => ({ id })); + return files.map(({ id }) => id); }; export const getFile = async (userId: number, fileId: number) => { @@ -416,16 +430,22 @@ export const setFileEncName = async ( }; export const unregisterFile = async (userId: number, fileId: number) => { - const file = await db - .deleteFrom("file") - .where("id", "=", fileId) - .where("user_id", "=", userId) - .returning("path") - .executeTakeFirst(); - if (!file) { - throw new IntegrityError("File not found"); - } - return { path: file.path }; + return await db.transaction().execute(async (trx) => { + const file = await trx + .selectFrom("file") + .leftJoin("thumbnail", "file.id", "thumbnail.file_id") + .select(["file.path", "thumbnail.path as thumbnailPath"]) + .where("file.id", "=", fileId) + .where("file.user_id", "=", userId) + .forUpdate("file") + .executeTakeFirst(); + if (!file) { + throw new IntegrityError("File not found"); + } + + await trx.deleteFrom("file").where("id", "=", fileId).execute(); + return file; + }); }; export const addFileToCategory = async (fileId: number, categoryId: number) => { diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts new file mode 100644 index 0000000..209e256 --- /dev/null +++ b/src/lib/server/db/media.ts @@ -0,0 +1,110 @@ +import type { NotNull } from "kysely"; +import { IntegrityError } from "./error"; +import db from "./kysely"; + +interface Thumbnail { + id: number; + path: string; + updatedAt: Date; + encContentIv: string; +} + +interface FileThumbnail extends Thumbnail { + fileId: number; +} + +export const updateFileThumbnail = async ( + userId: number, + fileId: number, + dekVersion: Date, + path: string, + encContentIv: string, +) => { + return await db.transaction().execute(async (trx) => { + const file = await trx + .selectFrom("file") + .select("data_encryption_key_version") + .where("id", "=", fileId) + .where("user_id", "=", userId) + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (!file) { + throw new IntegrityError("File not found"); + } else if (file.data_encryption_key_version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } + + const thumbnail = await trx + .selectFrom("thumbnail") + .select("path as oldPath") + .where("file_id", "=", fileId) + .limit(1) + .forUpdate() + .executeTakeFirst(); + const now = new Date(); + + await trx + .insertInto("thumbnail") + .values({ + file_id: fileId, + path, + updated_at: now, + encrypted_content_iv: encContentIv, + }) + .onConflict((oc) => + oc.column("file_id").doUpdateSet({ + path, + updated_at: now, + encrypted_content_iv: encContentIv, + }), + ) + .execute(); + return thumbnail?.oldPath ?? null; + }); +}; + +export const getFileThumbnail = async (userId: number, fileId: number) => { + const thumbnail = await db + .selectFrom("thumbnail") + .innerJoin("file", "thumbnail.file_id", "file.id") + .selectAll("thumbnail") + .where("file.id", "=", fileId) + .where("file.user_id", "=", userId) + .$narrowType<{ file_id: NotNull }>() + .limit(1) + .executeTakeFirst(); + return thumbnail + ? ({ + id: thumbnail.id, + fileId: thumbnail.file_id, + path: thumbnail.path, + encContentIv: thumbnail.encrypted_content_iv, + updatedAt: thumbnail.updated_at, + } satisfies FileThumbnail) + : null; +}; + +export const getMissingFileThumbnails = async (userId: number, limit: number = 100) => { + const files = await db + .selectFrom("file") + .select("id") + .where("user_id", "=", userId) + .where((eb) => + eb.or([eb("content_type", "like", "image/%"), eb("content_type", "like", "video/%")]), + ) + .where((eb) => + eb.not( + eb.exists( + eb + .selectFrom("thumbnail") + .select("thumbnail.id") + .whereRef("thumbnail.file_id", "=", "file.id") + .limit(1), + ), + ), + ) + .limit(limit) + .execute(); + return files.map(({ id }) => id); +}; diff --git a/src/lib/server/db/migrations/1738409340-AddThumbnail.ts b/src/lib/server/db/migrations/1738409340-AddThumbnail.ts new file mode 100644 index 0000000..c3ce806 --- /dev/null +++ b/src/lib/server/db/migrations/1738409340-AddThumbnail.ts @@ -0,0 +1,31 @@ +import { Kysely, sql } from "kysely"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const up = async (db: Kysely) => { + // media.ts + await db.schema + .createTable("thumbnail") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("directory_id", "integer", (col) => + col.references("directory.id").onDelete("cascade").unique(), + ) + .addColumn("file_id", "integer", (col) => + col.references("file.id").onDelete("cascade").unique(), + ) + .addColumn("category_id", "integer", (col) => + col.references("category.id").onDelete("cascade").unique(), + ) + .addColumn("path", "text", (col) => col.unique().notNull()) + .addColumn("updated_at", "timestamp(3)", (col) => col.notNull()) + .addColumn("encrypted_content_iv", "text", (col) => col.notNull()) + .addCheckConstraint( + "thumbnail_ck01", + sql`(file_id IS NOT NULL)::integer + (directory_id IS NOT NULL)::integer + (category_id IS NOT NULL)::integer = 1`, + ) + .execute(); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const down = async (db: Kysely) => { + await db.schema.dropTable("thumbnail").execute(); +}; diff --git a/src/lib/server/db/migrations/index.ts b/src/lib/server/db/migrations/index.ts index aa6ee13..f58c2d0 100644 --- a/src/lib/server/db/migrations/index.ts +++ b/src/lib/server/db/migrations/index.ts @@ -1,7 +1,9 @@ import * as Initial1737357000 from "./1737357000-Initial"; import * as AddFileCategory1737422340 from "./1737422340-AddFileCategory"; +import * as AddThumbnail1738409340 from "./1738409340-AddThumbnail"; export default { "1737357000-Initial": Initial1737357000, "1737422340-AddFileCategory": AddFileCategory1737422340, + "1738409340-AddThumbnail": AddThumbnail1738409340, }; diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index d3dd9b1..4e427fb 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -2,6 +2,7 @@ export * from "./category"; export * from "./client"; export * from "./file"; export * from "./hsk"; +export * from "./media"; export * from "./mek"; export * from "./session"; export * from "./user"; diff --git a/src/lib/server/db/schema/media.ts b/src/lib/server/db/schema/media.ts new file mode 100644 index 0000000..ebfbf29 --- /dev/null +++ b/src/lib/server/db/schema/media.ts @@ -0,0 +1,17 @@ +import type { Generated } from "kysely"; + +interface ThumbnailTable { + id: Generated; + directory_id: number | null; + file_id: number | null; + category_id: number | null; + path: string; + updated_at: Date; + encrypted_content_iv: string; // Base64 +} + +declare module "./index" { + interface Database { + thumbnail: ThumbnailTable; + } +} diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index 727f795..d2baef7 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -5,31 +5,22 @@ import db from "./kysely"; export const createSession = async ( userId: number, - clientId: number | null, sessionId: string, ip: string | null, agent: string | null, ) => { - try { - const now = new Date(); - await db - .insertInto("session") - .values({ - id: sessionId, - user_id: userId, - client_id: clientId, - created_at: now, - last_used_at: now, - last_used_by_ip: ip || null, - last_used_by_agent: agent || null, - }) - .execute(); - } catch (e) { - if (e instanceof pg.DatabaseError && e.code === "23505") { - throw new IntegrityError("Session already exists"); - } - throw e; - } + const now = new Date(); + await db + .insertInto("session") + .values({ + id: sessionId, + user_id: userId, + created_at: now, + last_used_at: now, + last_used_by_ip: ip || null, + last_used_by_agent: agent || null, + }) + .execute(); }; export const refreshSession = async ( @@ -55,15 +46,37 @@ export const refreshSession = async ( return { userId: session.user_id, clientId: session.client_id }; }; -export const upgradeSession = async (sessionId: string, clientId: number) => { - const res = await db - .updateTable("session") - .set({ client_id: clientId }) - .where("id", "=", sessionId) - .where("client_id", "is", null) - .executeTakeFirst(); - if (res.numUpdatedRows === 0n) { - throw new IntegrityError("Session not found"); +export const upgradeSession = async ( + userId: number, + sessionId: string, + clientId: number, + force: boolean, +) => { + try { + await db.transaction().execute(async (trx) => { + if (force) { + await trx + .deleteFrom("session") + .where("id", "!=", sessionId) + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .execute(); + } + const res = await trx + .updateTable("session") + .set({ client_id: clientId }) + .where("id", "=", sessionId) + .where("client_id", "is", null) + .executeTakeFirst(); + if (res.numUpdatedRows === 0n) { + throw new IntegrityError("Session not found"); + } + }); + } catch (e) { + if (e instanceof pg.DatabaseError && e.code === "23505") { + throw new IntegrityError("Session already exists"); + } + throw e; } }; @@ -94,7 +107,7 @@ export const registerSessionUpgradeChallenge = async ( expiresAt: Date, ) => { try { - await db + const { id } = await db .insertInto("session_upgrade_challenge") .values({ session_id: sessionId, @@ -103,7 +116,9 @@ export const registerSessionUpgradeChallenge = async ( allowed_ip: allowedIp, expires_at: expiresAt, }) - .execute(); + .returning("id") + .executeTakeFirstOrThrow(); + return { id }; } catch (e) { if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Challenge already registered"); @@ -113,19 +128,19 @@ export const registerSessionUpgradeChallenge = async ( }; export const consumeSessionUpgradeChallenge = async ( + challengeId: number, sessionId: string, - answer: string, ip: string, ) => { const challenge = await db .deleteFrom("session_upgrade_challenge") + .where("id", "=", challengeId) .where("session_id", "=", sessionId) - .where("answer", "=", answer) .where("allowed_ip", "=", ip) .where("expires_at", ">", new Date()) - .returning("client_id") + .returning(["client_id", "answer"]) .executeTakeFirst(); - return challenge ? { clientId: challenge.client_id } : null; + return challenge ? { clientId: challenge.client_id, answer: challenge.answer } : null; }; export const cleanupExpiredSessionUpgradeChallenges = async () => { diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index d6f4675..3a805d8 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -25,4 +25,5 @@ export default { sessionUpgradeExp: ms(env.SESSION_UPGRADE_CHALLENGE_EXPIRES || "5m"), }, libraryPath: env.LIBRARY_PATH || "library", + thumbnailsPath: env.THUMBNAILS_PATH || "thumbnails", }; diff --git a/src/lib/server/middlewares/authenticate.ts b/src/lib/server/middlewares/authenticate.ts index 8880f1a..8585bce 100644 --- a/src/lib/server/middlewares/authenticate.ts +++ b/src/lib/server/middlewares/authenticate.ts @@ -1,4 +1,5 @@ import { error, redirect, type Handle } from "@sveltejs/kit"; +import env from "$lib/server/loadenv"; import { authenticate, AuthenticationError } from "$lib/server/modules/auth"; export const authenticateMiddleware: Handle = async ({ event, resolve }) => { @@ -15,6 +16,12 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => { const { ip, userAgent } = event.locals; event.locals.session = await authenticate(sessionIdSigned, ip, userAgent); + event.cookies.set("sessionId", sessionIdSigned, { + path: "/", + maxAge: env.session.exp / 1000, + secure: true, + sameSite: "strict", + }); } catch (e) { if (e instanceof AuthenticationError) { if (pathname === "/auth/login") { diff --git a/src/lib/server/modules/auth.ts b/src/lib/server/modules/auth.ts index 4e03783..d25033d 100644 --- a/src/lib/server/modules/auth.ts +++ b/src/lib/server/modules/auth.ts @@ -27,7 +27,7 @@ export class AuthenticationError extends Error { export const startSession = async (userId: number, ip: string, userAgent: string) => { const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret); - await createSession(userId, null, sessionId, ip, userAgent); + await createSession(userId, sessionId, ip, userAgent); return sessionIdSigned; }; diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index e3d6264..b413f9d 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -4,27 +4,29 @@ export const passwordChangeRequest = z.object({ oldPassword: z.string().trim().nonempty(), newPassword: z.string().trim().nonempty(), }); -export type PasswordChangeRequest = z.infer; +export type PasswordChangeRequest = z.input; export const loginRequest = z.object({ email: z.string().email(), password: z.string().trim().nonempty(), }); -export type LoginRequest = z.infer; +export type LoginRequest = z.input; export const sessionUpgradeRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), }); -export type SessionUpgradeRequest = z.infer; +export type SessionUpgradeRequest = z.input; export const sessionUpgradeResponse = z.object({ + id: z.number().int().positive(), challenge: z.string().base64().nonempty(), }); -export type SessionUpgradeResponse = z.infer; +export type SessionUpgradeResponse = z.output; export const sessionUpgradeVerifyRequest = z.object({ - answer: z.string().base64().nonempty(), + id: z.number().int().positive(), answerSig: z.string().base64().nonempty(), + force: z.boolean().default(false), }); -export type SessionUpgradeVerifyRequest = z.infer; +export type SessionUpgradeVerifyRequest = z.input; diff --git a/src/lib/server/schemas/category.ts b/src/lib/server/schemas/category.ts index d02fa4f..55ae413 100644 --- a/src/lib/server/schemas/category.ts +++ b/src/lib/server/schemas/category.ts @@ -15,12 +15,12 @@ export const categoryInfoResponse = z.object({ .optional(), subCategories: z.number().int().positive().array(), }); -export type CategoryInfoResponse = z.infer; +export type CategoryInfoResponse = z.output; export const categoryFileAddRequest = z.object({ file: z.number().int().positive(), }); -export type CategoryFileAddRequest = z.infer; +export type CategoryFileAddRequest = z.input; export const categoryFileListResponse = z.object({ files: z.array( @@ -30,19 +30,19 @@ export const categoryFileListResponse = z.object({ }), ), }); -export type CategoryFileListResponse = z.infer; +export type CategoryFileListResponse = z.output; export const categoryFileRemoveRequest = z.object({ file: z.number().int().positive(), }); -export type CategoryFileRemoveRequest = z.infer; +export type CategoryFileRemoveRequest = z.input; export const categoryRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type CategoryRenameRequest = z.infer; +export type CategoryRenameRequest = z.input; export const categoryCreateRequest = z.object({ parent: categoryIdSchema, @@ -52,4 +52,4 @@ export const categoryCreateRequest = z.object({ name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type CategoryCreateRequest = z.infer; +export type CategoryCreateRequest = z.input; diff --git a/src/lib/server/schemas/client.ts b/src/lib/server/schemas/client.ts index 53cbb88..08a76b7 100644 --- a/src/lib/server/schemas/client.ts +++ b/src/lib/server/schemas/client.ts @@ -8,28 +8,29 @@ export const clientListResponse = z.object({ }), ), }); -export type ClientListResponse = z.infer; +export type ClientListResponse = z.output; export const clientRegisterRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), }); -export type ClientRegisterRequest = z.infer; +export type ClientRegisterRequest = z.input; export const clientRegisterResponse = z.object({ + id: z.number().int().positive(), challenge: z.string().base64().nonempty(), }); -export type ClientRegisterResponse = z.infer; +export type ClientRegisterResponse = z.output; export const clientRegisterVerifyRequest = z.object({ - answer: z.string().base64().nonempty(), + id: z.number().int().positive(), answerSig: z.string().base64().nonempty(), }); -export type ClientRegisterVerifyRequest = z.infer; +export type ClientRegisterVerifyRequest = z.input; export const clientStatusResponse = z.object({ id: z.number().int().positive(), state: z.enum(["pending", "active"]), isInitialMekNeeded: z.boolean(), }); -export type ClientStatusResponse = z.infer; +export type ClientStatusResponse = z.output; diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index 016cfe8..ffd13bc 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -16,19 +16,19 @@ export const directoryInfoResponse = z.object({ subDirectories: z.number().int().positive().array(), files: z.number().int().positive().array(), }); -export type DirectoryInfoResponse = z.infer; +export type DirectoryInfoResponse = z.output; export const directoryDeleteResponse = z.object({ deletedFiles: z.number().int().positive().array(), }); -export type DirectoryDeleteResponse = z.infer; +export type DirectoryDeleteResponse = z.output; export const directoryRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type DirectoryRenameRequest = z.infer; +export type DirectoryRenameRequest = z.input; export const directoryCreateRequest = z.object({ parent: directoryIdSchema, @@ -38,4 +38,4 @@ export const directoryCreateRequest = z.object({ name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type DirectoryCreateRequest = z.infer; +export type DirectoryCreateRequest = z.input; diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index b6aa648..8b9cfe9 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -21,25 +21,47 @@ export const fileInfoResponse = z.object({ lastModifiedAtIv: z.string().base64().nonempty(), categories: z.number().int().positive().array(), }); -export type FileInfoResponse = z.infer; +export type FileInfoResponse = z.output; export const fileRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type FileRenameRequest = z.infer; +export type FileRenameRequest = z.input; + +export const fileThumbnailInfoResponse = z.object({ + updatedAt: z.string().datetime(), + contentIv: z.string().base64().nonempty(), +}); +export type FileThumbnailInfoResponse = z.output; + +export const fileThumbnailUploadRequest = z.object({ + dekVersion: z.string().datetime(), + contentIv: z.string().base64().nonempty(), +}); +export type FileThumbnailUploadRequest = z.input; + +export const fileListResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type FileListResponse = z.output; export const duplicateFileScanRequest = z.object({ hskVersion: z.number().int().positive(), contentHmac: z.string().base64().nonempty(), }); -export type DuplicateFileScanRequest = z.infer; +export type DuplicateFileScanRequest = z.input; export const duplicateFileScanResponse = z.object({ files: z.number().int().positive().array(), }); -export type DuplicateFileScanResponse = z.infer; +export type DuplicateFileScanResponse = z.output; + +export const missingThumbnailFileScanResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type MissingThumbnailFileScanResponse = z.output; export const fileUploadRequest = z.object({ parent: directoryIdSchema, @@ -61,9 +83,9 @@ export const fileUploadRequest = z.object({ lastModifiedAt: z.string().base64().nonempty(), lastModifiedAtIv: z.string().base64().nonempty(), }); -export type FileUploadRequest = z.infer; +export type FileUploadRequest = z.input; export const fileUploadResponse = z.object({ file: z.number().int().positive(), }); -export type FileUploadResponse = z.infer; +export type FileUploadResponse = z.output; diff --git a/src/lib/server/schemas/hsk.ts b/src/lib/server/schemas/hsk.ts index bcea3cd..6f6b428 100644 --- a/src/lib/server/schemas/hsk.ts +++ b/src/lib/server/schemas/hsk.ts @@ -10,10 +10,10 @@ export const hmacSecretListResponse = z.object({ }), ), }); -export type HmacSecretListResponse = z.infer; +export type HmacSecretListResponse = z.output; export const initialHmacSecretRegisterRequest = z.object({ mekVersion: z.number().int().positive(), hsk: z.string().base64().nonempty(), }); -export type InitialHmacSecretRegisterRequest = z.infer; +export type InitialHmacSecretRegisterRequest = z.input; diff --git a/src/lib/server/schemas/mek.ts b/src/lib/server/schemas/mek.ts index e79f810..3d6f468 100644 --- a/src/lib/server/schemas/mek.ts +++ b/src/lib/server/schemas/mek.ts @@ -10,10 +10,10 @@ export const masterKeyListResponse = z.object({ }), ), }); -export type MasterKeyListResponse = z.infer; +export type MasterKeyListResponse = z.output; export const initialMasterKeyRegisterRequest = z.object({ mek: z.string().base64().nonempty(), mekSig: z.string().base64().nonempty(), }); -export type InitialMasterKeyRegisterRequest = z.infer; +export type InitialMasterKeyRegisterRequest = z.input; diff --git a/src/lib/server/schemas/user.ts b/src/lib/server/schemas/user.ts index e5cc8c1..9aec819 100644 --- a/src/lib/server/schemas/user.ts +++ b/src/lib/server/schemas/user.ts @@ -4,9 +4,9 @@ export const userInfoResponse = z.object({ email: z.string().email(), nickname: z.string().nonempty(), }); -export type UserInfoResponse = z.infer; +export type UserInfoResponse = z.output; export const nicknameChangeRequest = z.object({ newNickname: z.string().trim().min(2).max(8), }); -export type NicknameChangeRequest = z.infer; +export type NicknameChangeRequest = z.input; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index 81f0333..1c6867f 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -51,14 +51,7 @@ export const login = async (email: string, password: string, ip: string, userAge error(401, "Invalid email or password"); } - try { - return { sessionIdSigned: await startSession(user.id, ip, userAgent) }; - } catch (e) { - if (e instanceof IntegrityError && e.message === "Session already exists") { - error(403, "Already logged in"); - } - throw e; - } + return { sessionIdSigned: await startSession(user.id, ip, userAgent) }; }; export const logout = async (sessionId: string) => { @@ -81,7 +74,7 @@ export const createSessionUpgradeChallenge = async ( } const { answer, challenge } = await generateChallenge(32, encPubKey); - await registerSessionUpgradeChallenge( + const { id } = await registerSessionUpgradeChallenge( sessionId, client.id, answer.toString("base64"), @@ -89,16 +82,18 @@ export const createSessionUpgradeChallenge = async ( new Date(Date.now() + env.challenge.sessionUpgradeExp), ); - return { challenge: challenge.toString("base64") }; + return { id, challenge: challenge.toString("base64") }; }; export const verifySessionUpgradeChallenge = async ( sessionId: string, + userId: number, ip: string, - answer: string, + challengeId: number, answerSig: string, + force: boolean, ) => { - const challenge = await consumeSessionUpgradeChallenge(sessionId, answer, ip); + const challenge = await consumeSessionUpgradeChallenge(challengeId, sessionId, ip); if (!challenge) { error(403, "Invalid challenge answer"); } @@ -106,15 +101,21 @@ export const verifySessionUpgradeChallenge = async ( const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); - } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { + } else if ( + !verifySignature(Buffer.from(challenge.answer, "base64"), answerSig, client.sigPubKey) + ) { error(403, "Invalid challenge answer signature"); } try { - await upgradeSession(sessionId, client.id); + await upgradeSession(userId, sessionId, client.id, force); } catch (e) { - if (e instanceof IntegrityError && e.message === "Session not found") { - error(500, "Invalid challenge answer"); + if (e instanceof IntegrityError) { + if (e.message === "Session not found") { + error(500, "Invalid challenge answer"); + } else if (!force && e.message === "Session already exists") { + error(409, "Already logged in"); + } } throw e; } diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index ee1b5b3..811e58c 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -34,8 +34,14 @@ const createUserClientChallenge = async ( encPubKey: string, ) => { const { answer, challenge } = await generateChallenge(32, encPubKey); - await registerUserClientChallenge(userId, clientId, answer.toString("base64"), ip, expiresAt()); - return challenge.toString("base64"); + const { id } = await registerUserClientChallenge( + userId, + clientId, + answer.toString("base64"), + ip, + expiresAt(), + ); + return { id, challenge: challenge.toString("base64") }; }; export const registerUserClient = async ( @@ -48,7 +54,7 @@ export const registerUserClient = async ( if (client) { try { await createUserClient(userId, client.id); - return { challenge: await createUserClientChallenge(ip, userId, client.id, encPubKey) }; + return await createUserClientChallenge(ip, userId, client.id, encPubKey); } catch (e) { if (e instanceof IntegrityError && e.message === "User client already exists") { error(409, "Client already registered"); @@ -64,7 +70,7 @@ export const registerUserClient = async ( try { const { id: clientId } = await createClient(encPubKey, sigPubKey, userId); - return { challenge: await createUserClientChallenge(ip, userId, clientId, encPubKey) }; + return await createUserClientChallenge(ip, userId, clientId, encPubKey); } catch (e) { if (e instanceof IntegrityError && e.message === "Public key(s) already registered") { error(409, "Public key(s) already used"); @@ -77,10 +83,10 @@ export const registerUserClient = async ( export const verifyUserClient = async ( userId: number, ip: string, - answer: string, + challengeId: number, answerSig: string, ) => { - const challenge = await consumeUserClientChallenge(userId, answer, ip); + const challenge = await consumeUserClientChallenge(challengeId, userId, ip); if (!challenge) { error(403, "Invalid challenge answer"); } @@ -88,7 +94,9 @@ export const verifyUserClient = async ( const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); - } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { + } else if ( + !verifySignature(Buffer.from(challenge.answer, "base64"), answerSig, client.sigPubKey) + ) { error(403, "Invalid challenge answer signature"); } diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 2525069..fdab587 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -34,12 +34,19 @@ export const getDirectoryInformation = async (userId: number, directoryId: Direc }; }; +const safeUnlink = async (path: string | null) => { + if (path) { + await unlink(path).catch(console.error); + } +}; + export const deleteDirectory = async (userId: number, directoryId: number) => { try { const files = await unregisterDirectory(userId, directoryId); return { - files: files.map(({ id, path }) => { - unlink(path); // Intended + files: files.map(({ id, path, thumbnailPath }) => { + safeUnlink(path); // Intended + safeUnlink(thumbnailPath); // Intended return id; }), }; diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index d0b35ef..ab98dbf 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid"; import { IntegrityError } from "$lib/server/db/error"; import { registerFile, + getAllFileIds, getAllFileIdsByContentHmac, getFile, setFileEncName, @@ -16,6 +17,11 @@ import { getAllFileCategories, type NewFile, } from "$lib/server/db/file"; +import { + updateFileThumbnail, + getFileThumbnail, + getMissingFileThumbnails, +} from "$lib/server/db/media"; import type { Ciphertext } from "$lib/server/db/schema"; import env from "$lib/server/loadenv"; @@ -40,10 +46,17 @@ export const getFileInformation = async (userId: number, fileId: number) => { }; }; +const safeUnlink = async (path: string | null) => { + if (path) { + await unlink(path).catch(console.error); + } +}; + export const deleteFile = async (userId: number, fileId: number) => { try { - const { path } = await unregisterFile(userId, fileId); - unlink(path); // Intended + const { path, thumbnailPath } = await unregisterFile(userId, fileId); + safeUnlink(path); // Intended + safeUnlink(thumbnailPath); // Intended } catch (e) { if (e instanceof IntegrityError && e.message === "File not found") { error(404, "Invalid file id"); @@ -85,17 +98,74 @@ export const renameFile = async ( } }; +export const getFileThumbnailInformation = async (userId: number, fileId: number) => { + const thumbnail = await getFileThumbnail(userId, fileId); + if (!thumbnail) { + error(404, "File or its thumbnail not found"); + } + + return { updatedAt: thumbnail.updatedAt, encContentIv: thumbnail.encContentIv }; +}; + +export const getFileThumbnailStream = async (userId: number, fileId: number) => { + const thumbnail = await getFileThumbnail(userId, fileId); + if (!thumbnail) { + error(404, "File or its thumbnail not found"); + } + + const { size } = await stat(thumbnail.path); + return { + encContentStream: Readable.toWeb(createReadStream(thumbnail.path)), + encContentSize: size, + }; +}; + +export const uploadFileThumbnail = async ( + userId: number, + fileId: number, + dekVersion: Date, + encContentIv: string, + encContentStream: Readable, +) => { + const path = `${env.thumbnailsPath}/${userId}/${uuidv4()}`; + await mkdir(dirname(path), { recursive: true }); + + try { + await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); + + const oldPath = await updateFileThumbnail(userId, fileId, dekVersion, path, encContentIv); + safeUnlink(oldPath); // Intended + } catch (e) { + await safeUnlink(path); + + if (e instanceof IntegrityError) { + if (e.message === "File not found") { + error(404, "File not found"); + } else if (e.message === "Invalid DEK version") { + error(400, "Mismatched DEK version"); + } + } + throw e; + } +}; + +export const getFileList = async (userId: number) => { + const fileIds = await getAllFileIds(userId); + return { files: fileIds }; +}; + export const scanDuplicateFiles = async ( userId: number, hskVersion: number, contentHmac: string, ) => { const fileIds = await getAllFileIdsByContentHmac(userId, hskVersion, contentHmac); - return { files: fileIds.map(({ id }) => id) }; + return { files: fileIds }; }; -const safeUnlink = async (path: string) => { - await unlink(path).catch(console.error); +export const scanMissingFileThumbnails = async (userId: number) => { + const fileIds = await getMissingFileThumbnails(userId); + return { files: fileIds }; }; export const uploadFile = async ( diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts index 498c794..d1975f4 100644 --- a/src/lib/services/auth.ts +++ b/src/lib/services/auth.ts @@ -11,20 +11,29 @@ export const requestSessionUpgrade = async ( decryptKey: CryptoKey, verifyKeyBase64: string, signKey: CryptoKey, + force = false, ) => { let res = await callPostApi("/api/auth/upgradeSession", { encPubKey: encryptKeyBase64, sigPubKey: verifyKeyBase64, }); - if (!res.ok) return false; + if (res.status === 403) return [false, "Unregistered client"] as const; + else if (!res.ok) return [false] as const; - const { challenge }: SessionUpgradeResponse = await res.json(); + const { id, challenge }: SessionUpgradeResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); const answerSig = await signMessageRSA(answer, signKey); res = await callPostApi("/api/auth/upgradeSession/verify", { - answer: encodeToBase64(answer), + id, answerSig: encodeToBase64(answerSig), + force, }); + if (res.status === 409) return [false, "Already logged in"] as const; + else return [res.ok] as const; +}; + +export const requestLogout = async () => { + const res = await callPostApi("/api/auth/logout"); return res.ok; }; diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts new file mode 100644 index 0000000..11742c8 --- /dev/null +++ b/src/lib/services/file.ts @@ -0,0 +1,83 @@ +import { callGetApi } from "$lib/hooks"; +import { getAllFileInfos } from "$lib/indexedDB/filesystem"; +import { decryptData } from "$lib/modules/crypto"; +import { + getFileCache, + storeFileCache, + deleteFileCache, + getFileThumbnailCache, + storeFileThumbnailCache, + deleteFileThumbnailCache, + downloadFile, +} from "$lib/modules/file"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; +import type { + FileThumbnailInfoResponse, + FileThumbnailUploadRequest, + FileListResponse, +} from "$lib/server/schemas"; + +export const requestFileDownload = async ( + fileId: number, + fileEncryptedIv: string, + dataKey: CryptoKey, +) => { + const cache = await getFileCache(fileId); + if (cache) return cache; + + const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); + storeFileCache(fileId, fileBuffer); // Intended + return fileBuffer; +}; + +export const requestFileThumbnailUpload = async ( + fileId: number, + dataKeyVersion: Date, + thumbnailEncrypted: { ciphertext: ArrayBuffer; iv: string }, +) => { + const form = new FormData(); + form.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnailEncrypted.iv, + } satisfies FileThumbnailUploadRequest), + ); + form.set("content", new Blob([thumbnailEncrypted.ciphertext])); + + return await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); +}; + +export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { + const cache = await getFileThumbnailCache(fileId); + if (cache) return cache; + + let res = await callGetApi(`/api/file/${fileId}/thumbnail`); + if (!res.ok) return null; + + const { contentIv: thumbnailEncryptedIv }: FileThumbnailInfoResponse = await res.json(); + + res = await callGetApi(`/api/file/${fileId}/thumbnail/download`); + if (!res.ok) return null; + + const thumbnailEncrypted = await res.arrayBuffer(); + const thumbnailBuffer = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); + + storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended + return getThumbnailUrl(thumbnailBuffer); +}; + +export const requestDeletedFilesCleanup = async () => { + const res = await callGetApi("/api/file/list"); + if (!res.ok) return; + + const { files: liveFiles }: FileListResponse = await res.json(); + const liveFilesSet = new Set(liveFiles); + const maybeCachedFiles = await getAllFileInfos(); + + await Promise.all( + maybeCachedFiles + .filter(({ id }) => !liveFilesSet.has(id)) + .flatMap(({ id }) => [deleteFileCache(id), deleteFileThumbnailCache(id)]), + ); +}; diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts index fb368dd..cecd241 100644 --- a/src/lib/services/key.ts +++ b/src/lib/services/key.ts @@ -2,18 +2,23 @@ import { callGetApi, callPostApi } from "$lib/hooks"; import { storeMasterKeys } from "$lib/indexedDB"; import { encodeToBase64, + exportRSAKeyToBase64, decryptChallenge, signMessageRSA, unwrapMasterKey, + signMasterKeyWrapped, verifyMasterKeyWrapped, } from "$lib/modules/crypto"; import type { ClientRegisterRequest, ClientRegisterResponse, ClientRegisterVerifyRequest, + InitialHmacSecretRegisterRequest, MasterKeyListResponse, + InitialMasterKeyRegisterRequest, } from "$lib/server/schemas"; -import { masterKeyStore } from "$lib/stores"; +import { requestSessionUpgrade } from "$lib/services/auth"; +import { masterKeyStore, type ClientKeys } from "$lib/stores"; export const requestClientRegistration = async ( encryptKeyBase64: string, @@ -27,17 +32,46 @@ export const requestClientRegistration = async ( }); if (!res.ok) return false; - const { challenge }: ClientRegisterResponse = await res.json(); + const { id, challenge }: ClientRegisterResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); const answerSig = await signMessageRSA(answer, signKey); res = await callPostApi("/api/client/register/verify", { - answer: encodeToBase64(answer), + id, answerSig: encodeToBase64(answerSig), }); return res.ok; }; +export const requestClientRegistrationAndSessionUpgrade = async ( + { encryptKey, decryptKey, signKey, verifyKey }: ClientKeys, + force: boolean, +) => { + const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey); + const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey); + const [res, error] = await requestSessionUpgrade( + encryptKeyBase64, + decryptKey, + verifyKeyBase64, + signKey, + force, + ); + if (error === undefined) return [res] as const; + + if ( + error === "Unregistered client" && + !(await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) + ) { + return [false] as const; + } else if (error === "Already logged in") { + return [false, force ? undefined : error] as const; + } + + return [ + (await requestSessionUpgrade(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey))[0], + ] as const; +}; + export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: CryptoKey) => { const res = await callGetApi("/api/mek/list"); if (!res.ok) return false; @@ -68,3 +102,23 @@ export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: return true; }; + +export const requestInitialMasterKeyAndHmacSecretRegistration = async ( + masterKeyWrapped: string, + hmacSecretWrapped: string, + signKey: CryptoKey, +) => { + let res = await callPostApi("/api/mek/register/initial", { + mek: masterKeyWrapped, + mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), + }); + if (!res.ok) { + return res.status === 403 || res.status === 409; + } + + res = await callPostApi("/api/hsk/register/initial", { + mekVersion: 1, + hsk: hmacSecretWrapped, + }); + return res.ok; +}; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index ac3b1a5..8ec1742 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -2,18 +2,57 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; + import { ForceLoginModal } from "$lib/components/organisms"; import { clientKeyStore, masterKeyStore } from "$lib/stores"; - import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service"; + import { + requestLogin, + requestClientRegistrationAndSessionUpgrade, + requestMasterKeyDownload, + requestDeletedFilesCleanup, + requestLogout, + } from "./service"; let { data } = $props(); let email = $state(""); let password = $state(""); + let isForceLoginModalOpen = $state(false); + const redirect = async (url: string) => { return await goto(`${url}?redirect=${encodeURIComponent(data.redirectPath)}`); }; + const upgradeSession = async (force: boolean) => { + try { + const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade( + $clientKeyStore!, + force, + ); + if (!force && upgradeError === "Already logged in") { + isForceLoginModalOpen = true; + return; + } else if (!upgradeRes) { + throw new Error("Failed to upgrade session"); + } + + // TODO: Multi-user support + + if ( + $masterKeyStore || + (await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey)) + ) { + await requestDeletedFilesCleanup(); + await goto(data.redirectPath); + } else { + await redirect("/client/pending"); + } + } catch (e) { + // TODO + throw e; + } + }; + const login = async () => { // TODO: Validation @@ -22,19 +61,7 @@ if (!$clientKeyStore) return await redirect("/key/generate"); - if (!(await requestSessionUpgrade($clientKeyStore))) - throw new Error("Failed to upgrade session"); - - // TODO: Multi-user support - - if ( - $masterKeyStore || - (await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)) - ) { - await goto(data.redirectPath); - } else { - await redirect("/client/pending"); - } + await upgradeSession(false); } catch (e) { // TODO: Alert throw e; @@ -63,3 +90,9 @@ 계정이 없어요 + + upgradeSession(true)} +/> diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index 2d267e1..88613f1 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,37 +1,14 @@ import { callPostApi } from "$lib/hooks"; -import { exportRSAKeyToBase64 } from "$lib/modules/crypto"; import type { LoginRequest } from "$lib/server/schemas"; -import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth"; -import { requestClientRegistration } from "$lib/services/key"; -import type { ClientKeys } from "$lib/stores"; -export { requestMasterKeyDownload } from "$lib/services/key"; +export { requestLogout } from "$lib/services/auth"; +export { requestDeletedFilesCleanup } from "$lib/services/file"; +export { + requestClientRegistrationAndSessionUpgrade, + requestMasterKeyDownload, +} from "$lib/services/key"; export const requestLogin = async (email: string, password: string) => { const res = await callPostApi("/api/auth/login", { email, password }); return res.ok; }; - -export const requestSessionUpgrade = async ({ - encryptKey, - decryptKey, - signKey, - verifyKey, -}: ClientKeys) => { - const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey); - const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey); - if (await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { - return true; - } - - if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { - return await requestSessionUpgradeInternal( - encryptKeyBase64, - decryptKey, - verifyKeyBase64, - signKey, - ); - } else { - return false; - } -}; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 325122f..61465bd 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -11,15 +11,18 @@ type FileInfo, type CategoryInfo, } from "$lib/modules/filesystem"; + import { captureVideoThumbnail } from "$lib/modules/thumbnail"; import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores"; import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte"; import DownloadStatus from "./DownloadStatus.svelte"; import { requestFileRemovalFromCategory, requestFileDownload, + requestThumbnailUpload, requestFileAdditionToCategory, } from "./service"; + import IconCamera from "~icons/material-symbols/camera"; import IconClose from "~icons/material-symbols/close"; import IconAddCircle from "~icons/material-symbols/add-circle"; @@ -40,6 +43,7 @@ let isDownloadRequested = $state(false); let viewerType: "image" | "video" | undefined = $state(); let fileBlobUrl: string | undefined = $state(); + let videoElement: HTMLVideoElement | undefined = $state(); const updateViewer = async (buffer: ArrayBuffer, contentType: string) => { const fileBlob = new Blob([buffer], { type: contentType }); @@ -55,6 +59,11 @@ return fileBlob; }; + const updateThumbnail = async (dataKey: CryptoKey, dataKeyVersion: Date) => { + const thumbnail = await captureVideoThumbnail(videoElement!); + await requestThumbnailUpload(data.id, thumbnail, dataKey, dataKeyVersion); + }; + const addToCategory = async (categoryId: number) => { await requestFileAdditionToCategory(data.id, categoryId); isAddToCategoryBottomSheetOpen = false; @@ -133,8 +142,17 @@ {/if} {:else if viewerType === "video"} {#if fileBlobUrl} - - +
+ + + updateThumbnail($info.dataKey!, $info.dataKeyVersion!)} + class="w-full" + > + 이 장면을 썸네일로 설정하기 + +
{:else} {@render viewerLoading("비디오를 불러오고 있어요.")} {/if} diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 43f0134..00614d6 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,20 +1,25 @@ import { callPostApi } from "$lib/hooks"; -import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; +import { encryptData } from "$lib/modules/crypto"; +import { storeFileThumbnailCache } from "$lib/modules/file"; import type { CategoryFileAddRequest } from "$lib/server/schemas"; +import { requestFileThumbnailUpload } from "$lib/services/file"; export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; +export { requestFileDownload } from "$lib/services/file"; -export const requestFileDownload = async ( +export const requestThumbnailUpload = async ( fileId: number, - fileEncryptedIv: string, + thumbnail: Blob, dataKey: CryptoKey, + dataKeyVersion: Date, ) => { - const cache = await getFileCache(fileId); - if (cache) return cache; + const thumbnailBuffer = await thumbnail.arrayBuffer(); + const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); + const res = await requestFileThumbnailUpload(fileId, dataKeyVersion, thumbnailEncrypted); + if (!res.ok) return false; - const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); - storeFileCache(fileId, fileBuffer); // Intended - return fileBuffer; + storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended + return true; }; export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => { diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index 3a4ae07..80e8eee 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -3,13 +3,12 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; + import { serializeClientKeys, storeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte"; import BeforeContinueModal from "./BeforeContinueModal.svelte"; import { - serializeClientKeys, requestClientRegistration, - storeClientKeys, requestSessionUpgrade, requestInitialMasterKeyAndHmacSecretRegistration, } from "./service"; @@ -22,15 +21,8 @@ let isBeforeContinueBottomSheetOpen = $state(false); const exportClientKeys = () => { - const clientKeysSerialized = serializeClientKeys( - data.encryptKeyBase64, - data.decryptKeyBase64, - data.signKeyBase64, - data.verifyKeyBase64, - ); - const clientKeysBlob = new Blob([JSON.stringify(clientKeysSerialized)], { - type: "application/json", - }); + const clientKeysSerialized = serializeClientKeys(data); + const clientKeysBlob = new Blob([clientKeysSerialized], { type: "application/json" }); FileSaver.saveAs(clientKeysBlob, "arkvault-clientkey.json"); if (!isBeforeContinueBottomSheetOpen) { @@ -59,12 +51,14 @@ await storeClientKeys($clientKeyStore); if ( - !(await requestSessionUpgrade( - data.encryptKeyBase64, - $clientKeyStore.decryptKey, - data.verifyKeyBase64, - $clientKeyStore.signKey, - )) + !( + await requestSessionUpgrade( + data.encryptKeyBase64, + $clientKeyStore.decryptKey, + data.verifyKeyBase64, + $clientKeyStore.signKey, + ) + )[0] ) throw new Error("Failed to upgrade session"); diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index a9aaaee..ddc3dee 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -1,68 +1,5 @@ -import { callPostApi } from "$lib/hooks"; -import { storeClientKey } from "$lib/indexedDB"; -import { signMasterKeyWrapped } from "$lib/modules/crypto"; -import type { - InitialMasterKeyRegisterRequest, - InitialHmacSecretRegisterRequest, -} from "$lib/server/schemas"; -import type { ClientKeys } from "$lib/stores"; - export { requestSessionUpgrade } from "$lib/services/auth"; -export { requestClientRegistration } from "$lib/services/key"; - -type SerializedKeyPairs = { - generator: "ArkVault"; - exportedAt: Date; -} & { - version: 1; - encryptKey: string; - decryptKey: string; - signKey: string; - verifyKey: string; -}; - -export const serializeClientKeys = ( - encryptKeyBase64: string, - decryptKeyBase64: string, - signKeyBase64: string, - verifyKeyBase64: string, -) => { - return { - version: 1, - generator: "ArkVault", - exportedAt: new Date(), - encryptKey: encryptKeyBase64, - decryptKey: decryptKeyBase64, - signKey: signKeyBase64, - verifyKey: verifyKeyBase64, - } satisfies SerializedKeyPairs; -}; - -export const storeClientKeys = async (clientKeys: ClientKeys) => { - await Promise.all([ - storeClientKey(clientKeys.encryptKey, "encrypt"), - storeClientKey(clientKeys.decryptKey, "decrypt"), - storeClientKey(clientKeys.signKey, "sign"), - storeClientKey(clientKeys.verifyKey, "verify"), - ]); -}; - -export const requestInitialMasterKeyAndHmacSecretRegistration = async ( - masterKeyWrapped: string, - hmacSecretWrapped: string, - signKey: CryptoKey, -) => { - let res = await callPostApi("/api/mek/register/initial", { - mek: masterKeyWrapped, - mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), - }); - if (!res.ok) { - return res.status === 409; - } - - res = await callPostApi("/api/hsk/register/initial", { - mekVersion: 1, - hsk: hmacSecretWrapped, - }); - return res.ok; -}; +export { + requestClientRegistration, + requestInitialMasterKeyAndHmacSecretRegistration, +} from "$lib/services/key"; diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 03d6c6d..6f7609c 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -3,19 +3,29 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; + import { ForceLoginModal } from "$lib/components/organisms"; import { gotoStateful } from "$lib/hooks"; + import { storeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; import Order from "./Order.svelte"; import { generateClientKeys, generateInitialMasterKey, generateInitialHmacSecret, + importClientKeys, + requestClientRegistrationAndSessionUpgrade, + requestInitialMasterKeyAndHmacSecretRegistration, + requestDeletedFilesCleanup, } from "./service"; import IconKey from "~icons/material-symbols/key"; let { data } = $props(); + let fileInput: HTMLInputElement | undefined = $state(); + + let isForceLoginModalOpen = $state(false); + // TODO: Update const orders = [ { @@ -51,6 +61,54 @@ }); }; + const upgradeSession = async (force: boolean) => { + const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade( + $clientKeyStore!, + force, + ); + if (!force && upgradeError === "Already logged in") { + isForceLoginModalOpen = true; + return; + } else if (!upgradeRes) { + // TODO: Error Handling + return; + } + + const { masterKey, masterKeyWrapped } = await generateInitialMasterKey( + $clientKeyStore!.encryptKey, + ); + const { hmacSecretWrapped } = await generateInitialHmacSecret(masterKey); + + await storeClientKeys($clientKeyStore!); + + if ( + !(await requestInitialMasterKeyAndHmacSecretRegistration( + masterKeyWrapped, + hmacSecretWrapped, + $clientKeyStore!.signKey, + )) + ) { + // TODO: Error Handling + return; + } + + await requestDeletedFilesCleanup(); + await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); + }; + + const importKeys = async () => { + const file = fileInput?.files?.[0]; + if (!file) return; + + if (await importClientKeys(await file.text())) { + await upgradeSession(false); + } else { + // TODO: Error Handling + } + + fileInput!.value = ""; + }; + onMount(async () => { if ($clientKeyStore) { await goto(data.redirectPath, { replaceState: true }); @@ -62,6 +120,14 @@ 암호 키 생성하기 + + {#snippet title()} @@ -83,6 +149,8 @@ - 키를 갖고 있어요 + fileInput?.click()}>키를 갖고 있어요 + + upgradeSession(true)} /> diff --git a/src/routes/(fullscreen)/key/generate/service.ts b/src/routes/(fullscreen)/key/generate/service.ts index f970f46..1ba4a4c 100644 --- a/src/routes/(fullscreen)/key/generate/service.ts +++ b/src/routes/(fullscreen)/key/generate/service.ts @@ -2,6 +2,8 @@ import { generateEncryptionKeyPair, generateSigningKeyPair, exportRSAKeyToBase64, + importEncryptionKeyPairFromBase64, + importSigningKeyPairFromBase64, makeRSAKeyNonextractable, wrapMasterKey, generateMasterKey, @@ -9,8 +11,16 @@ import { wrapHmacSecret, generateHmacSecret, } from "$lib/modules/crypto"; +import { deserializeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; +export { requestLogout } from "$lib/services/auth"; +export { requestDeletedFilesCleanup } from "$lib/services/file"; +export { + requestClientRegistrationAndSessionUpgrade, + requestInitialMasterKeyAndHmacSecretRegistration, +} from "$lib/services/key"; + export const generateClientKeys = async () => { const { encryptKey, decryptKey } = await generateEncryptionKeyPair(); const { signKey, verifyKey } = await generateSigningKeyPair(); @@ -45,3 +55,25 @@ export const generateInitialHmacSecret = async (masterKey: CryptoKey) => { hmacSecretWrapped: await wrapHmacSecret(hmacSecret, masterKey), }; }; + +export const importClientKeys = async (clientKeysSerialized: string) => { + const clientKeys = deserializeClientKeys(clientKeysSerialized); + if (!clientKeys) return false; + + const { encryptKey, decryptKey } = await importEncryptionKeyPairFromBase64( + clientKeys.encryptKeyBase64, + clientKeys.decryptKeyBase64, + ); + const { signKey, verifyKey } = await importSigningKeyPairFromBase64( + clientKeys.signKeyBase64, + clientKeys.verifyKeyBase64, + ); + + clientKeyStore.set({ + encryptKey, + decryptKey: await makeRSAKeyNonextractable(decryptKey), + signKey: await makeRSAKeyNonextractable(signKey), + verifyKey, + }); + return true; +}; diff --git a/src/routes/(fullscreen)/settings/cache/+page.svelte b/src/routes/(fullscreen)/settings/cache/+page.svelte index 262aacf..af375c2 100644 --- a/src/routes/(fullscreen)/settings/cache/+page.svelte +++ b/src/routes/(fullscreen)/settings/cache/+page.svelte @@ -4,12 +4,11 @@ import { FullscreenDiv } from "$lib/components/atoms"; import { TopBar } from "$lib/components/molecules"; import type { FileCacheIndex } from "$lib/indexedDB"; - import { getFileCacheIndex } from "$lib/modules/file"; + import { getFileCacheIndex, deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; import { formatFileSize } from "$lib/modules/util"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; - import { deleteFileCache as doDeleteFileCache } from "./service"; interface FileCache { index: FileCacheIndex; diff --git a/src/routes/(fullscreen)/settings/cache/service.ts b/src/routes/(fullscreen)/settings/cache/service.ts deleted file mode 100644 index 35b0251..0000000 --- a/src/routes/(fullscreen)/settings/cache/service.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; - -export const deleteFileCache = async (fileId: number) => { - await doDeleteFileCache(fileId); -}; diff --git a/src/routes/(fullscreen)/settings/thumbnail/+page.svelte b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte new file mode 100644 index 0000000..d9cd692 --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte @@ -0,0 +1,77 @@ + + + + 썸네일 설정 + + + + +
+
+ + 저장된 썸네일 모두 삭제하기 + +
+ {#if persistentStates.files.length > 0} +
+

썸네일이 누락된 파일

+
+

+ {persistentStates.files.length}개 파일의 썸네일이 존재하지 않아요. +

+
+ {#each persistentStates.files as { info, status }} + goto(`/file/${id}`)} + onGenerateThumbnailClick={requestThumbnailGeneration} + /> + {/each} +
+
+
+ {/if} +
+ {#if persistentStates.files.length > 0} + + + + {/if} +
diff --git a/src/routes/(fullscreen)/settings/thumbnail/+page.ts b/src/routes/(fullscreen)/settings/thumbnail/+page.ts new file mode 100644 index 0000000..a16cb8e --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnail/+page.ts @@ -0,0 +1,14 @@ +import { error } from "@sveltejs/kit"; +import { callPostApi } from "$lib/hooks"; +import type { MissingThumbnailFileScanResponse } from "$lib/server/schemas"; +import type { PageLoad } from "./$types"; + +export const load: PageLoad = async ({ fetch }) => { + const res = await callPostApi("/api/file/scanMissingThumbnails", undefined, fetch); + if (!res.ok) { + error(500, "Internal server error"); + } + + const { files }: MissingThumbnailFileScanResponse = await res.json(); + return { files }; +}; diff --git a/src/routes/(fullscreen)/settings/thumbnail/File.svelte b/src/routes/(fullscreen)/settings/thumbnail/File.svelte new file mode 100644 index 0000000..8977fc7 --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnail/File.svelte @@ -0,0 +1,46 @@ + + + + +{#if $info} + onclick($info)} + actionButtonIcon={!$generationStatus || $generationStatus === "error" ? IconCamera : undefined} + onActionButtonClick={() => onGenerateThumbnailClick($info)} + actionButtonClass="text-gray-800" + > + {@const subtext = + $generationStatus && $generationStatus !== "uploaded" + ? subtexts[$generationStatus] + : formatDateTime($info.createdAt ?? $info.lastModifiedAt)} + + +{/if} diff --git a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts new file mode 100644 index 0000000..d8f288c --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts @@ -0,0 +1,158 @@ +import { limitFunction } from "p-limit"; +import { get, writable, type Writable } from "svelte/store"; +import { encryptData } from "$lib/modules/crypto"; +import { storeFileThumbnailCache } from "$lib/modules/file"; +import type { FileInfo } from "$lib/modules/filesystem"; +import { generateThumbnail as doGenerateThumbnail } from "$lib/modules/thumbnail"; +import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file"; + +export type GenerationStatus = + | "queued" + | "generation-pending" + | "generating" + | "upload-pending" + | "uploading" + | "uploaded" + | "error"; + +interface File { + id: number; + info: Writable; + status?: Writable; +} + +const workingFiles = new Map>(); + +let queue: (() => void)[] = []; +let memoryUsage = 0; +const memoryLimit = 100 * 1024 * 1024; // 100 MiB + +export const persistentStates = $state({ + files: [] as File[], +}); + +export const getGenerationStatus = (fileId: number) => { + return workingFiles.get(fileId); +}; + +const generateThumbnail = limitFunction( + async ( + status: Writable, + fileBuffer: ArrayBuffer, + fileType: string, + dataKey: CryptoKey, + ) => { + status.set("generating"); + + const thumbnail = await doGenerateThumbnail(fileBuffer, fileType); + if (!thumbnail) return null; + + const thumbnailBuffer = await thumbnail.arrayBuffer(); + const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); + status.set("upload-pending"); + return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; + }, + { concurrency: 4 }, +); + +const requestThumbnailUpload = limitFunction( + async ( + status: Writable, + fileId: number, + dataKeyVersion: Date, + thumbnail: { plaintext: ArrayBuffer; ciphertext: ArrayBuffer; iv: string }, + ) => { + status.set("uploading"); + + const res = await requestFileThumbnailUpload(fileId, dataKeyVersion, thumbnail); + if (!res.ok) return false; + + status.set("uploaded"); + workingFiles.delete(fileId); + persistentStates.files = persistentStates.files.filter(({ id }) => id != fileId); + + storeFileThumbnailCache(fileId, thumbnail.plaintext); // Intended + return true; + }, + { concurrency: 4 }, +); + +const enqueue = async ( + status: Writable | undefined, + fileInfo: FileInfo, + priority = false, +) => { + if (status) { + status.set("queued"); + } else { + status = writable("queued"); + workingFiles.set(fileInfo.id, status); + persistentStates.files = persistentStates.files.map((file) => + file.id === fileInfo.id ? { ...file, status } : file, + ); + } + + let resolver; + const promise = new Promise((resolve) => { + resolver = resolve; + }); + + if (priority) { + queue = [resolver!, ...queue]; + } else { + queue.push(resolver!); + } + + await promise; +}; + +export const requestThumbnailGeneration = async (fileInfo: FileInfo) => { + let status = workingFiles.get(fileInfo.id); + if (status && get(status) !== "error") return; + + if (workingFiles.values().some((status) => get(status) !== "error")) { + await enqueue(status, fileInfo); + } + while (memoryUsage >= memoryLimit) { + await enqueue(status, fileInfo, true); + } + + if (status) { + status.set("generation-pending"); + } else { + status = writable("generation-pending"); + workingFiles.set(fileInfo.id, status); + persistentStates.files = persistentStates.files.map((file) => + file.id === fileInfo.id ? { ...file, status } : file, + ); + } + + let fileSize = 0; + try { + const file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey!); + fileSize = file.byteLength; + + memoryUsage += fileSize; + if (memoryUsage < memoryLimit) { + queue.shift()?.(); + } + + const thumbnail = await generateThumbnail( + status, + file, + fileInfo.contentType, + fileInfo.dataKey!, + ); + if ( + !thumbnail || + !(await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail)) + ) { + status.set("error"); + } + } catch { + status.set("error"); + } finally { + memoryUsage -= fileSize; + queue.shift()?.(); + } +}; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index fd59d03..8251331 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -4,6 +4,7 @@ import { DirectoryEntryLabel } from "$lib/components/molecules"; import type { FileInfo } from "$lib/modules/filesystem"; import { formatDateTime } from "$lib/modules/util"; + import { requestFileThumbnailDownload } from "./service"; import type { SelectedEntry } from "../service.svelte"; import IconMoreVert from "~icons/material-symbols/more-vert"; @@ -16,6 +17,8 @@ let { info, onclick, onOpenMenuClick }: Props = $props(); + let thumbnail: string | undefined = $state(); + const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info!; if (!dataKey || !dataKeyVersion) return; // TODO: Error handling @@ -29,6 +32,21 @@ onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name }); }; + + $effect(() => { + if ($info?.dataKey) { + requestFileThumbnailDownload($info.id, $info.dataKey) + .then((thumbnailUrl) => { + thumbnail = thumbnailUrl ?? undefined; + }) + .catch(() => { + // TODO: Error Handling + thumbnail = undefined; + }); + } else { + thumbnail = undefined; + } + }); {#if $info} @@ -40,6 +58,7 @@ > diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte index 7977c53..a6df05a 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -13,8 +13,8 @@ {#if isFileUploading($status.status)} -
-
+
+
diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts new file mode 100644 index 0000000..d4b47f8 --- /dev/null +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -0,0 +1 @@ +export { requestFileThumbnailDownload } from "$lib/services/file"; diff --git a/src/routes/(main)/directory/[[id]]/service.svelte.ts b/src/routes/(main)/directory/[[id]]/service.svelte.ts index 3c5f689..ba5fc4a 100644 --- a/src/routes/(main)/directory/[[id]]/service.svelte.ts +++ b/src/routes/(main)/directory/[[id]]/service.svelte.ts @@ -2,7 +2,13 @@ import { getContext, setContext } from "svelte"; import { callGetApi, callPostApi } from "$lib/hooks"; import { storeHmacSecrets } from "$lib/indexedDB"; import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$lib/modules/crypto"; -import { storeFileCache, deleteFileCache, uploadFile } from "$lib/modules/file"; +import { + storeFileCache, + deleteFileCache, + storeFileThumbnailCache, + deleteFileThumbnailCache, + uploadFile, +} from "$lib/modules/file"; import type { DirectoryRenameRequest, DirectoryCreateRequest, @@ -81,6 +87,10 @@ export const requestFileUpload = async ( if (!res) return false; storeFileCache(res.fileId, res.fileBuffer); // Intended + if (res.thumbnailBuffer) { + storeFileThumbnailCache(res.fileId, res.thumbnailBuffer); // Intended + } + return true; }; @@ -110,10 +120,12 @@ export const requestEntryDeletion = async (entry: SelectedEntry) => { if (entry.type === "directory") { const { deletedFiles }: DirectoryDeleteResponse = await res.json(); - await Promise.all(deletedFiles.map(deleteFileCache)); + await Promise.all( + deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnailCache(fileId)]), + ); return true; } else { - await deleteFileCache(entry.id); + await Promise.all([deleteFileCache(entry.id), deleteFileThumbnailCache(entry.id)]); return true; } }; diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 13ccb92..40f4a26 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -4,6 +4,7 @@ import { requestLogout } from "./service"; import IconStorage from "~icons/material-symbols/storage"; + import IconImage from "~icons/material-symbols/image"; import IconPassword from "~icons/material-symbols/password"; import IconLogout from "~icons/material-symbols/logout"; @@ -33,6 +34,13 @@ > 캐시 + goto("/settings/thumbnail")} + icon={IconImage} + iconColor="text-blue-500" + > + 썸네일 +

보안

diff --git a/src/routes/(main)/menu/service.ts b/src/routes/(main)/menu/service.ts index 32dd4fd..ca63aa2 100644 --- a/src/routes/(main)/menu/service.ts +++ b/src/routes/(main)/menu/service.ts @@ -1,6 +1 @@ -import { callPostApi } from "$lib/hooks"; - -export const requestLogout = async () => { - const res = await callPostApi("/api/auth/logout"); - return res.ok; -}; +export { requestLogout } from "$lib/services/auth"; diff --git a/src/routes/api/auth/upgradeSession/+server.ts b/src/routes/api/auth/upgradeSession/+server.ts index 760f4c0..fa0b6cf 100644 --- a/src/routes/api/auth/upgradeSession/+server.ts +++ b/src/routes/api/auth/upgradeSession/+server.ts @@ -15,12 +15,12 @@ export const POST: RequestHandler = async ({ locals, request }) => { if (!zodRes.success) error(400, "Invalid request body"); const { encPubKey, sigPubKey } = zodRes.data; - const { challenge } = await createSessionUpgradeChallenge( + const { id, challenge } = await createSessionUpgradeChallenge( sessionId, userId, locals.ip, encPubKey, sigPubKey, ); - return json(sessionUpgradeResponse.parse({ challenge } satisfies SessionUpgradeResponse)); + return json(sessionUpgradeResponse.parse({ id, challenge } satisfies SessionUpgradeResponse)); }; diff --git a/src/routes/api/auth/upgradeSession/verify/+server.ts b/src/routes/api/auth/upgradeSession/verify/+server.ts index 82cb315..bbaedca 100644 --- a/src/routes/api/auth/upgradeSession/verify/+server.ts +++ b/src/routes/api/auth/upgradeSession/verify/+server.ts @@ -5,12 +5,12 @@ import { verifySessionUpgradeChallenge } from "$lib/server/services/auth"; import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ locals, request }) => { - const { sessionId } = await authorize(locals, "notClient"); + const { sessionId, userId } = await authorize(locals, "notClient"); const zodRes = sessionUpgradeVerifyRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); - const { answer, answerSig } = zodRes.data; + const { id, answerSig, force } = zodRes.data; - await verifySessionUpgradeChallenge(sessionId, locals.ip, answer, answerSig); + await verifySessionUpgradeChallenge(sessionId, userId, locals.ip, id, answerSig, force); return text("Session upgraded", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/client/register/+server.ts b/src/routes/api/client/register/+server.ts index d6aa4ce..5ac2a53 100644 --- a/src/routes/api/client/register/+server.ts +++ b/src/routes/api/client/register/+server.ts @@ -15,6 +15,6 @@ export const POST: RequestHandler = async ({ locals, request }) => { if (!zodRes.success) error(400, "Invalid request body"); const { encPubKey, sigPubKey } = zodRes.data; - const { challenge } = await registerUserClient(userId, locals.ip, encPubKey, sigPubKey); - return json(clientRegisterResponse.parse({ challenge } satisfies ClientRegisterResponse)); + const { id, challenge } = await registerUserClient(userId, locals.ip, encPubKey, sigPubKey); + return json(clientRegisterResponse.parse({ id, challenge } satisfies ClientRegisterResponse)); }; diff --git a/src/routes/api/client/register/verify/+server.ts b/src/routes/api/client/register/verify/+server.ts index 32d5214..5ac9396 100644 --- a/src/routes/api/client/register/verify/+server.ts +++ b/src/routes/api/client/register/verify/+server.ts @@ -9,8 +9,8 @@ export const POST: RequestHandler = async ({ locals, request }) => { const zodRes = clientRegisterVerifyRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); - const { answer, answerSig } = zodRes.data; + const { id, answerSig } = zodRes.data; - await verifyUserClient(userId, locals.ip, answer, answerSig); + await verifyUserClient(userId, locals.ip, id, answerSig); return text("Client verified", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/file/[id]/thumbnail/+server.ts b/src/routes/api/file/[id]/thumbnail/+server.ts new file mode 100644 index 0000000..12c9347 --- /dev/null +++ b/src/routes/api/file/[id]/thumbnail/+server.ts @@ -0,0 +1,26 @@ +import { error, json } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { fileThumbnailInfoResponse, type FileThumbnailInfoResponse } from "$lib/server/schemas"; +import { getFileThumbnailInformation } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + const { updatedAt, encContentIv } = await getFileThumbnailInformation(userId, id); + return json( + fileThumbnailInfoResponse.parse({ + updatedAt: updatedAt.toISOString(), + contentIv: encContentIv, + } satisfies FileThumbnailInfoResponse), + ); +}; diff --git a/src/routes/api/file/[id]/thumbnail/download/+server.ts b/src/routes/api/file/[id]/thumbnail/download/+server.ts new file mode 100644 index 0000000..addd800 --- /dev/null +++ b/src/routes/api/file/[id]/thumbnail/download/+server.ts @@ -0,0 +1,25 @@ +import { error } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { getFileThumbnailStream } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + const { encContentStream, encContentSize } = await getFileThumbnailStream(userId, id); + return new Response(encContentStream as ReadableStream, { + headers: { + "Content-Type": "application/octet-stream", + "Content-Length": encContentSize.toString(), + }, + }); +}; diff --git a/src/routes/api/file/[id]/thumbnail/upload/+server.ts b/src/routes/api/file/[id]/thumbnail/upload/+server.ts new file mode 100644 index 0000000..62dfe42 --- /dev/null +++ b/src/routes/api/file/[id]/thumbnail/upload/+server.ts @@ -0,0 +1,74 @@ +import Busboy from "@fastify/busboy"; +import { error, text } from "@sveltejs/kit"; +import { Readable, Writable } from "stream"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { fileThumbnailUploadRequest, type FileThumbnailUploadRequest } from "$lib/server/schemas"; +import { uploadFileThumbnail } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + const contentType = request.headers.get("Content-Type"); + if (!contentType?.startsWith("multipart/form-data") || !request.body) { + error(400, "Invalid request body"); + } + + return new Promise((resolve, reject) => { + const bb = Busboy({ headers: { "content-type": contentType } }); + const handler = + (f: (...args: T) => Promise) => + (...args: T) => { + f(...args).catch(reject); + }; + + let metadata: FileThumbnailUploadRequest | null = null; + let content: Readable | null = null; + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname === "metadata") { + // Ignore subsequent metadata fields + if (!metadata) { + const zodRes = fileThumbnailUploadRequest.safeParse(JSON.parse(val)); + if (!zodRes.success) error(400, "Invalid request body"); + metadata = zodRes.data; + } + } else { + error(400, "Invalid request body"); + } + }), + ); + bb.on( + "file", + handler(async (fieldname, file) => { + if (fieldname !== "content") error(400, "Invalid request body"); + if (!metadata || content) error(400, "Invalid request body"); + content = file; + + await uploadFileThumbnail( + userId, + id, + new Date(metadata.dekVersion), + metadata.contentIv, + content, + ); + resolve(text("Thumbnail uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("error", (e) => { + content?.emit("error", e) ?? reject(e); + }); + + request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error + }); +}; diff --git a/src/routes/api/file/list/+server.ts b/src/routes/api/file/list/+server.ts new file mode 100644 index 0000000..c1b6888 --- /dev/null +++ b/src/routes/api/file/list/+server.ts @@ -0,0 +1,11 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { fileListResponse, type FileListResponse } from "$lib/server/schemas"; +import { getFileList } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "activeClient"); + const { files } = await getFileList(userId); + return json(fileListResponse.parse({ files } satisfies FileListResponse)); +}; diff --git a/src/routes/api/file/scanMissingThumbnails/+server.ts b/src/routes/api/file/scanMissingThumbnails/+server.ts new file mode 100644 index 0000000..bf2a2a6 --- /dev/null +++ b/src/routes/api/file/scanMissingThumbnails/+server.ts @@ -0,0 +1,16 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { + missingThumbnailFileScanResponse, + type MissingThumbnailFileScanResponse, +} from "$lib/server/schemas/file"; +import { scanMissingFileThumbnails } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "activeClient"); + const { files } = await scanMissingFileThumbnails(userId); + return json( + missingThumbnailFileScanResponse.parse({ files } satisfies MissingThumbnailFileScanResponse), + ); +};