diff --git a/AGENTS.md b/AGENTS.md index af06c50..55e4bbe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,11 +102,12 @@ Errors are **values**, not exceptions. The codebase uses Result-style handling. ### Current Conventions 1. **Logic package** — Uses `neverthrow` (`ResultAsync`, `okAsync`, `errAsync`) for async operations that may fail. -2. **`@pkg/result`** — Provides `Result`, `ERROR_CODES`, and `tryCatch()`. The `Result` type is legacy; the project may move toward Effect. Use `ERROR_CODES` for consistent error codes. -3. **`getError()`** — From `@pkg/logger`. Use at boundaries when converting a thrown error to an `Err` object: +2. **`@pkg/result`** — Provides `Result`, `ERROR_CODES`, and `tryCatch()`. The `Result` type is legacy; So don't reach for it primarily. +3. Use `ERROR_CODES` for consistent error codes. +4. **`getError()`** — From `@pkg/logger`. Use at boundaries when converting a thrown error to an `Err` object: `return getError({ code: ERROR_CODES.XXX, message: "...", description: "...", detail: "..." }, e)`. -4. **Domain errors** — Each domain has an `errors.ts` that exports error constructors built with `getError`. Use these instead of ad-hoc error objects. -5. **Check before use** — Always check `result.isOk()` / `result.isErr()` before using `result.value`; never assume success. +5. **Domain errors** — Each domain has an `errors.ts` that exports error constructors built with `getError`. Use these instead of ad-hoc error objects. +6. **Check before use** — Always check `result.isOk()` / `result.isErr()` before using `result.value`; never assume success. ### Err Shape @@ -119,6 +120,7 @@ type Err = { detail: string; actionable?: boolean; error?: any; + // Flexible, but more "defined base fields" in the future }; ``` @@ -155,26 +157,15 @@ Shared state (`user`, `session`, `breadcrumbs`) lives in `$lib/global.stores.ts` ## 5. Processor App -The processor is a **Hono** server intended for **background workers** and async jobs. Its structure is still evolving. +The processor is a **Hono** server intended for **background work** and async jobs. Its structure is still evolving and it is to be updated soon. -### Current State - -- Minimal Hono server; no BullMQ or job processors yet. -- When workers are added, processing logic should live under `src/domains//` and call into `@pkg/logic` controllers and repositories. -- HTTP routes will expose internal APIs (e.g. task status, webhooks), secured (e.g. API key), for use by the main app or external services. -- Async work should be triggered by calling the processor HTTP API, not by importing job queues in the main app or logic package. - -### Conventions (when implemented) - -- The worker processes jobs; do not block it with long-running HTTP logic. -- Job payloads should be validated before processing. -- Follow existing domain patterns for controllers and repositories. +When logic is added, processing logic should live under `src/domains//` and call into `@pkg/logic` controllers and repositories. --- ## 6. Observability -The stack uses **OpenTelemetry** end-to-end for traces, logs, and metrics, shipped to a local **SigNoz** instance (via OTel Collector) in development. +The stack uses **OpenTelemetry** end-to-end for traces, logs, and metrics, shipped to a **SigNoz** instance (via OTel Collector). ### How it fits together diff --git a/packages/db/migrations/0001_silly_venus.sql b/packages/db/migrations/0001_silly_venus.sql new file mode 100644 index 0000000..0a70b58 --- /dev/null +++ b/packages/db/migrations/0001_silly_venus.sql @@ -0,0 +1,55 @@ +CREATE TABLE IF NOT EXISTS "file" ( + "id" text PRIMARY KEY NOT NULL, + "filename" text NOT NULL, + "original_name" text NOT NULL, + "mime_type" text NOT NULL, + "size" integer NOT NULL, + "hash" text NOT NULL, + "bucket_name" text NOT NULL, + "object_key" text NOT NULL, + "r2_url" text NOT NULL, + "metadata" json, + "tags" json, + "visibility" varchar(16) DEFAULT 'private' NOT NULL, + "user_id" text NOT NULL, + "status" varchar(16) DEFAULT 'processing' NOT NULL, + "processing_error" text, + "uploaded_at" timestamp NOT NULL, + "last_accessed_at" timestamp, + "expires_at" timestamp, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "file_access" ( + "id" serial PRIMARY KEY NOT NULL, + "file_id" text NOT NULL, + "user_id" text NOT NULL, + "can_read" boolean DEFAULT false NOT NULL, + "can_write" boolean DEFAULT false NOT NULL, + "can_delete" boolean DEFAULT false NOT NULL, + "can_share" boolean DEFAULT false NOT NULL, + "accessed_at" timestamp, + "granted_at" timestamp NOT NULL, + "expires_at" timestamp, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "file" ADD CONSTRAINT "file_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "file_access" ADD CONSTRAINT "file_access_file_id_file_id_fk" FOREIGN KEY ("file_id") REFERENCES "public"."file"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "file_access" ADD CONSTRAINT "file_access_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/db/migrations/meta/0001_snapshot.json b/packages/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..c3a8ecb --- /dev/null +++ b/packages/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,920 @@ +{ + "id": "32691d1d-382d-4db0-96ca-a49c46ece173", + "prevId": "333bfb88-9996-4dab-bbf5-724a6eadd745", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.twofa_sessions": { + "name": "twofa_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code_used": { + "name": "code_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "twofa_sessions_user_id_user_id_fk": { + "name": "twofa_sessions_user_id_user_id_fk", + "tableFrom": "twofa_sessions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "twofa_sessions_verification_token_unique": { + "name": "twofa_sessions_verification_token_unique", + "nullsNotDistinct": false, + "columns": [ + "verification_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "onboarding_done": { + "name": "onboarding_done", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "last2_fa_verified_at": { + "name": "last2_fa_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file": { + "name": "file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_name": { + "name": "bucket_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "r2_url": { + "name": "r2_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'processing'" + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "file_user_id_user_id_fk": { + "name": "file_user_id_user_id_fk", + "tableFrom": "file", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file_access": { + "name": "file_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "can_read": { + "name": "can_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_write": { + "name": "can_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_delete": { + "name": "can_delete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_share": { + "name": "can_share", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "file_access_file_id_file_id_fk": { + "name": "file_access_file_id_file_id_fk", + "tableFrom": "file_access", + "tableTo": "file", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_access_user_id_user_id_fk": { + "name": "file_access_user_id_user_id_fk", + "tableFrom": "file_access", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "type": { + "name": "type", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "action_url": { + "name": "action_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_type": { + "name": "action_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "action_data": { + "name": "action_data", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notifications_user_id_user_id_fk": { + "name": "notifications_user_id_user_id_fk", + "tableFrom": "notifications", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 0a04ef9..beb8b28 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1772288650927, "tag": "0000_lucky_karma", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1772335785371, + "tag": "0001_silly_venus", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json index 44184a8..3f6ba23 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -13,13 +13,13 @@ "dependencies": { "@pkg/settings": "workspace:*", "dotenv": "^16.4.7", - "drizzle-orm": "^0.36.1", + "drizzle-orm": "^0.45.1", "postgres": "^3.4.8" }, "devDependencies": { "@types/bun": "latest", "@types/pg": "^8.11.10", - "drizzle-kit": "^0.28.0" + "drizzle-kit": "^0.31.9" }, "peerDependencies": { "typescript": "^5.9.3" diff --git a/packages/db/schema/file.schema.ts b/packages/db/schema/file.schema.ts new file mode 100644 index 0000000..977a7ca --- /dev/null +++ b/packages/db/schema/file.schema.ts @@ -0,0 +1,88 @@ +import { + boolean, + integer, + json, + pgTable, + serial, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { user } from "./better.auth.schema"; +import { relations } from "drizzle-orm"; + +// Add this to your existing schema file +export const file = pgTable("file", { + id: text("id").primaryKey(), // UUID + + // File Information + filename: text("filename").notNull(), + originalName: text("original_name").notNull(), + mimeType: text("mime_type").notNull(), + size: integer("size").notNull(), // in bytes + hash: text("hash").notNull(), // SHA-256 for deduplication + + // R2 Storage Information + bucketName: text("bucket_name").notNull(), + objectKey: text("object_key").notNull(), + r2Url: text("r2_url").notNull(), + + // Metadata + metadata: json("metadata").$type>(), + tags: json("tags").$type(), + + // Access Control + visibility: varchar("visibility", { length: 16 }) + .default("private") + .notNull(), // "public", "private", "restricted" + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + + // File Status + status: varchar("status", { length: 16 }).default("processing").notNull(), // "processing", "ready", "error", "deleted" + processingError: text("processing_error"), + + // Timestamps + uploadedAt: timestamp("uploaded_at").notNull(), + lastAccessedAt: timestamp("last_accessed_at"), + expiresAt: timestamp("expires_at"), // For temporary files + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const fileAccess = pgTable("file_access", { + id: serial("id").primaryKey(), + + fileId: text("file_id") + .notNull() + .references(() => file.id, { onDelete: "cascade" }), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + + // Access permissions + canRead: boolean("can_read").default(false).notNull(), + canWrite: boolean("can_write").default(false).notNull(), + canDelete: boolean("can_delete").default(false).notNull(), + canShare: boolean("can_share").default(false).notNull(), + + // Access tracking + accessedAt: timestamp("accessed_at"), + grantedAt: timestamp("granted_at").notNull(), + expiresAt: timestamp("expires_at"), + + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +// Relations +export const filesRelations = relations(file, ({ one, many }) => ({ + owner: one(user, { fields: [file.userId], references: [user.id] }), + fileAccess: many(fileAccess), +})); + +export const fileAccessRelations = relations(fileAccess, ({ one }) => ({ + file: one(file, { fields: [fileAccess.fileId], references: [file.id] }), + user: one(user, { fields: [fileAccess.userId], references: [user.id] }), +})); diff --git a/packages/db/schema/index.ts b/packages/db/schema/index.ts index 2171de4..e9381aa 100644 --- a/packages/db/schema/index.ts +++ b/packages/db/schema/index.ts @@ -1,3 +1,4 @@ export * from "./auth.schema"; export * from "./better.auth.schema"; +export * from "./file.schema"; export * from "./general.schema"; diff --git a/packages/logic/domains/files/controller.ts b/packages/logic/domains/files/controller.ts new file mode 100644 index 0000000..a41934c --- /dev/null +++ b/packages/logic/domains/files/controller.ts @@ -0,0 +1,252 @@ +import { + FileFilters, + FileShareRequest, + FileUpdateRequest, + FileUploadRequest, + PaginationOptions, + PresignedUploadRequest, +} from "./data"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { StorageRepository } from "./storage.repository"; +import { FileRepository } from "./repository"; +import { settings } from "@core/settings"; +import { ResultAsync } from "neverthrow"; +import { traceResultAsync } from "@core/observability"; +import { db } from "@pkg/db"; + +export class FileController { + constructor( + private fileRepo: FileRepository, + private storageRepo: StorageRepository, + private publicUrl: string, + ) {} + + getFiles( + fctx: FlowExecCtx, + filters: FileFilters, + pagination: PaginationOptions, + ) { + return traceResultAsync({ + name: "logic.files.controller.getFiles", + fctx, + attributes: { "app.user.id": filters.userId }, + fn: () => this.fileRepo.getFiles(fctx, filters, pagination), + }); + } + + getFile(fctx: FlowExecCtx, fileId: string, userId: string) { + return traceResultAsync({ + name: "logic.files.controller.getFile", + fctx, + attributes: { "app.user.id": userId, "app.file.id": fileId }, + fn: () => this.fileRepo.getFileById(fctx, fileId, userId), + }); + } + + uploadFile( + fctx: FlowExecCtx, + userId: string, + file: globalThis.File, + uploadRequest: FileUploadRequest, + ) { + return traceResultAsync({ + name: "logic.files.controller.uploadFile", + fctx, + attributes: { "app.user.id": userId, "app.file.name": file.name }, + fn: () => + ResultAsync.fromPromise(file.arrayBuffer(), (error) => ({ + code: "INTERNAL_ERROR", + message: "Failed to read file buffer", + description: "Please try again", + detail: error instanceof Error ? error.message : String(error), + })) + .map((arrayBuffer) => Buffer.from(arrayBuffer)) + .andThen((buffer) => + this.storageRepo.uploadFile( + fctx, + buffer, + file.name, + file.type, + userId, + { + visibility: + (uploadRequest.visibility as + | "public" + | "private") || "private", + metadata: uploadRequest.metadata, + tags: uploadRequest.tags, + processImage: uploadRequest.processImage, + processDocument: uploadRequest.processDocument, + processVideo: uploadRequest.processVideo, + }, + ), + ) + .andThen((fileMetadata) => + this.fileRepo + .createFile(fctx, { + id: fileMetadata.id, + filename: fileMetadata.filename, + originalName: fileMetadata.originalName, + mimeType: fileMetadata.mimeType, + size: fileMetadata.size, + hash: fileMetadata.hash, + bucketName: fileMetadata.bucketName, + objectKey: fileMetadata.objectKey, + r2Url: fileMetadata.r2Url, + visibility: fileMetadata.visibility, + userId: fileMetadata.userId, + metadata: fileMetadata.metadata, + tags: fileMetadata.tags + ? [...fileMetadata.tags] + : undefined, + status: "ready", + uploadedAt: fileMetadata.uploadedAt, + }) + .map((dbFile) => ({ + success: true, + file: dbFile, + uploadId: fileMetadata.id, + })), + ), + }); + } + + generatePresignedUrl( + fctx: FlowExecCtx, + userId: string, + bucketName: string, + request: PresignedUploadRequest, + ) { + const fileId = crypto.randomUUID(); + const extension = request.filename.split(".").pop() || ""; + const filename = `${fileId}.${extension}`; + const objectKey = `uploads/${userId}/${filename}`; + + return traceResultAsync({ + name: "logic.files.controller.generatePresignedUrl", + fctx, + attributes: { "app.user.id": userId, "app.file.id": fileId }, + fn: () => + this.storageRepo + .generatePresignedUploadUrl( + fctx, + objectKey, + request.mimeType, + 3600, + ) + .andThen((presignedData) => + this.fileRepo + .createFile(fctx, { + id: fileId, + filename, + originalName: request.filename, + mimeType: request.mimeType, + size: request.size, + hash: "", + bucketName, + objectKey, + r2Url: `${this.publicUrl}/${bucketName}/${objectKey}`, + visibility: request.visibility || "private", + userId, + status: "processing", + uploadedAt: new Date(), + }) + .map(() => ({ + ...presignedData, + fileId, + objectKey, + })), + ), + }); + } + + updateFile( + fctx: FlowExecCtx, + fileId: string, + userId: string, + updates: FileUpdateRequest, + ) { + return traceResultAsync({ + name: "logic.files.controller.updateFile", + fctx, + attributes: { "app.user.id": userId, "app.file.id": fileId }, + fn: () => this.fileRepo.updateFile(fctx, fileId, userId, updates), + }); + } + + deleteFiles(fctx: FlowExecCtx, fileIds: readonly string[], userId: string) { + return traceResultAsync({ + name: "logic.files.controller.deleteFiles", + fctx, + attributes: { + "app.user.id": userId, + "app.files.count": fileIds.length, + }, + fn: () => + ResultAsync.combine( + [...fileIds].map((fileId) => + this.fileRepo.getFileById(fctx, fileId, userId), + ), + ) + .map((files) => files.map((file) => file.objectKey)) + .andThen((objectKeys) => + this.storageRepo.deleteFiles(fctx, objectKeys), + ) + .andThen(() => + this.fileRepo.deleteFiles(fctx, fileIds, userId), + ), + }); + } + + shareFile( + fctx: FlowExecCtx, + fileId: string, + ownerId: string, + shareRequest: FileShareRequest, + ) { + return traceResultAsync({ + name: "logic.files.controller.shareFile", + fctx, + attributes: { "app.user.id": ownerId, "app.file.id": fileId }, + fn: () => this.fileRepo.shareFile(fctx, fileId, ownerId, shareRequest), + }); + } + + updateFileStatus( + fctx: FlowExecCtx, + fileId: string, + status: string, + processingError?: string, + ) { + return traceResultAsync({ + name: "logic.files.controller.updateFileStatus", + fctx, + attributes: { "app.file.id": fileId }, + fn: () => + this.fileRepo.updateFileStatus( + fctx, + fileId, + status, + processingError, + ), + }); + } +} + +export function getFileController(): FileController { + return new FileController( + new FileRepository(db), + new StorageRepository({ + bucketName: settings.r2BucketName || "", + region: settings.r2Region || "", + endpoint: settings.r2Endpoint || "", + accessKey: settings.r2AccessKey || "", + secretKey: settings.r2SecretKey || "", + publicUrl: settings.r2PublicUrl || "", + maxFileSize: settings.maxFileSize, + allowedMimeTypes: settings.allowedMimeTypes, + allowedExtensions: settings.allowedExtensions, + }), + settings.r2PublicUrl || "", + ); +} diff --git a/packages/logic/domains/files/data.ts b/packages/logic/domains/files/data.ts new file mode 100644 index 0000000..2b1b6fc --- /dev/null +++ b/packages/logic/domains/files/data.ts @@ -0,0 +1,147 @@ +import * as v from "valibot"; + +export const fileSchema = v.object({ + id: v.string(), + filename: v.string(), + originalName: v.string(), + mimeType: v.string(), + size: v.pipe(v.number(), v.integer()), + hash: v.string(), + bucketName: v.string(), + objectKey: v.string(), + r2Url: v.string(), + visibility: v.string(), + userId: v.string(), + metadata: v.optional(v.record(v.string(), v.any())), + tags: v.optional(v.array(v.string())), + status: v.string(), + processingError: v.optional(v.string()), + uploadedAt: v.date(), + lastAccessedAt: v.optional(v.date()), + expiresAt: v.optional(v.date()), + createdAt: v.date(), + updatedAt: v.date(), +}); + +export type File = v.InferOutput; +export type Files = File[]; + +export const fileUploadRequestSchema = v.object({ + visibility: v.optional(v.string()), + metadata: v.optional(v.record(v.string(), v.any())), + tags: v.optional(v.array(v.string())), + processImage: v.optional(v.boolean()), + processDocument: v.optional(v.boolean()), + processVideo: v.optional(v.boolean()), +}); +export type FileUploadRequest = v.InferOutput; + +export const fileFiltersSchema = v.object({ + userId: v.string(), + mimeType: v.optional(v.string()), + visibility: v.optional(v.string()), + status: v.optional(v.string()), + search: v.optional(v.string()), + tags: v.optional(v.array(v.string())), +}); +export type FileFilters = v.InferOutput; + +export const paginationOptionsSchema = v.object({ + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + sortBy: v.optional(v.string()), + sortOrder: v.optional(v.string()), +}); +export type PaginationOptions = v.InferOutput; + +export const PaginatedFilesSchema = v.object({ + data: v.array(fileSchema), + total: v.pipe(v.number(), v.integer()), + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + totalPages: v.pipe(v.number(), v.integer()), +}); +export type PaginatedFiles = v.InferOutput; + +export const getFilesSchema = v.object({ + filters: fileFiltersSchema, + pagination: paginationOptionsSchema, +}); +export type GetFiles = v.InferOutput; + +export const presignedUploadRequestSchema = v.object({ + filename: v.string(), + mimeType: v.string(), + size: v.pipe(v.number(), v.integer()), + visibility: v.optional(v.string()), +}); +export type PresignedUploadRequest = v.InferOutput< + typeof presignedUploadRequestSchema +>; + +export const presignedUploadResponseSchema = v.object({ + uploadUrl: v.string(), + downloadUrl: v.optional(v.string()), + expiresIn: v.pipe(v.number(), v.integer()), + fileId: v.string(), + objectKey: v.string(), + fields: v.optional(v.record(v.string(), v.any())), +}); +export type PresignedUploadResponse = v.InferOutput< + typeof presignedUploadResponseSchema +>; + +export const fileUploadResultSchema = v.object({ + success: v.boolean(), + file: v.optional(fileSchema), + uploadId: v.optional(v.string()), + error: v.optional(v.string()), +}); +export type FileUploadResult = v.InferOutput; + +export const bulkFileIdsSchema = v.object({ + fileIds: v.array(v.string()), +}); +export type BulkFileIds = v.InferOutput; + +export const fileUpdateRequestSchema = v.object({ + filename: v.optional(v.string()), + visibility: v.optional(v.string()), + metadata: v.optional(v.record(v.string(), v.any())), + tags: v.optional(v.array(v.string())), +}); +export type FileUpdateRequest = v.InferOutput; + +export const fileShareRequestSchema = v.object({ + userId: v.string(), + permissions: v.object({ + canRead: v.optional(v.boolean()), + canWrite: v.optional(v.boolean()), + canDelete: v.optional(v.boolean()), + canShare: v.optional(v.boolean()), + }), + expiresAt: v.optional(v.date()), +}); +export type FileShareRequest = v.InferOutput; + +// +// Frontend specific models +// +export const clientFileFiltersSchema = v.object({ + mimeType: v.optional(v.string()), + visibility: v.optional(v.string()), + status: v.optional(v.string()), + search: v.optional(v.string()), + tags: v.optional(v.array(v.string())), +}); +export type ClientFileFilters = v.InferOutput; + +export const clientPaginationOptionsSchema = v.object({ + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + sortBy: v.string(), + sortOrder: v.picklist(["asc", "desc"]), +}); +export type ClientPaginationOptions = v.InferOutput< + typeof clientPaginationOptionsSchema +>; diff --git a/packages/logic/domains/files/errors.ts b/packages/logic/domains/files/errors.ts new file mode 100644 index 0000000..689b002 --- /dev/null +++ b/packages/logic/domains/files/errors.ts @@ -0,0 +1,132 @@ +import { FlowExecCtx } from "@core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const fileErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + fileNotFound: (fctx: FlowExecCtx, fileId: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "File not found", + description: + "The requested file does not exist or you don't have access to it", + detail: `File ID: ${fileId}`, + }), + + getFilesFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to fetch files", + description: "Please try again later", + detail, + }), + + getFileFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to get file", + description: "Please try again later", + detail, + }), + + createFileFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to create file record", + description: "Please try again later", + detail, + }), + + updateFileFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to update file", + description: "Please try again later", + detail, + }), + + deleteFilesFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to delete files", + description: "Please try again later", + detail, + }), + + updateStatusFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to update file status", + description: "Please try again later", + detail, + }), + + shareFileFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to share file", + description: "Please try again later", + detail, + }), + + uploadFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "File upload failed", + description: "Please try again later", + detail, + }), + + noFileMetadata: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Upload succeeded but no file metadata returned", + description: "Please try uploading again", + detail: "Storage service returned no file metadata", + }), + + presignedUrlFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to generate presigned URL", + description: "Please try again later", + detail, + }), + + noPresignedData: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to generate presigned URL", + description: "Please try again later", + detail: "Storage service returned no presigned data", + }), + + storageError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.STORAGE_ERROR, + message: "Storage operation failed", + description: "Please try again later", + detail, + }), +}; diff --git a/packages/logic/domains/files/repository.ts b/packages/logic/domains/files/repository.ts new file mode 100644 index 0000000..416fded --- /dev/null +++ b/packages/logic/domains/files/repository.ts @@ -0,0 +1,537 @@ +import type { + File, + FileFilters, + FileShareRequest, + FileUpdateRequest, + PaginatedFiles, + PaginationOptions, +} from "./data"; +import { + Database, + and, + asc, + count, + desc, + eq, + inArray, + like, + or, + sql, +} from "@pkg/db"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { file, fileAccess } from "@pkg/db/schema"; +import { type Err } from "@pkg/result"; +import { fileErrors } from "./errors"; +import { logDomainEvent } from "@pkg/logger"; + +export class FileRepository { + constructor(private db: Database) {} + + getFiles( + fctx: FlowExecCtx, + filters: FileFilters, + pagination: PaginationOptions, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.list.started", + fctx, + meta: { + userId: filters.userId, + hasSearch: Boolean(filters.search), + hasTags: Boolean(filters.tags?.length), + page: pagination.page, + pageSize: pagination.pageSize, + }, + }); + + const { userId, mimeType, visibility, status, search, tags } = filters; + const { + page, + pageSize, + sortBy = "createdAt", + sortOrder = "desc", + } = pagination; + + const conditions = [eq(file.userId, userId)]; + + if (mimeType) { + conditions.push(like(file.mimeType, `${mimeType}%`)); + } + + if (visibility) { + conditions.push(eq(file.visibility, visibility)); + } + + if (status) { + conditions.push(eq(file.status, status)); + } + + if (search) { + conditions.push( + or( + like(file.filename, `%${search}%`), + like(file.originalName, `%${search}%`), + )!, + ); + } + + if (tags && tags.length > 0) { + conditions.push(sql`${file.tags} @> ${JSON.stringify(tags)}`); + } + + const whereClause = and(...conditions); + + return ResultAsync.fromPromise( + this.db.select({ count: count() }).from(file).where(whereClause), + (error) => { + logDomainEvent({ + level: "error", + event: "files.list.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return fileErrors.getFilesFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((totalResult) => { + const total = totalResult[0]?.count || 0; + const offset = (page - 1) * pageSize; + + const getOrderColumn = (currentSortBy: string) => { + switch (currentSortBy) { + case "createdAt": + return file.createdAt; + case "uploadedAt": + return file.uploadedAt; + case "size": + return file.size; + case "filename": + return file.filename; + default: + return file.createdAt; + } + }; + + const orderColumn = getOrderColumn(sortBy); + const orderFunc = sortOrder === "asc" ? asc : desc; + + return ResultAsync.fromPromise( + this.db + .select() + .from(file) + .where(whereClause) + .orderBy(orderFunc(orderColumn)) + .limit(pageSize) + .offset(offset), + (error) => { + logDomainEvent({ + level: "error", + event: "files.list.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return fileErrors.getFilesFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((data) => { + const totalPages = Math.ceil(total / pageSize); + logDomainEvent({ + event: "files.list.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { + userId, + page, + totalPages, + count: data.length, + }, + }); + + return { + data: data as File[], + total, + page, + pageSize, + totalPages, + }; + }); + }); + } + + getFileById( + fctx: FlowExecCtx, + fileId: string, + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.get.started", + fctx, + meta: { fileId, userId }, + }); + + return ResultAsync.fromPromise( + this.db + .select() + .from(file) + .where(and(eq(file.id, fileId), eq(file.userId, userId))) + .limit(1), + (error) => { + logDomainEvent({ + level: "error", + event: "files.get.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { fileId, userId }, + }); + return fileErrors.getFileFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((result) => { + const dbFile = result[0]; + + if (!dbFile) { + logDomainEvent({ + level: "warn", + event: "files.get.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "File not found" }, + meta: { fileId, userId }, + }); + return errAsync(fileErrors.fileNotFound(fctx, fileId)); + } + + logDomainEvent({ + event: "files.get.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { fileId, userId }, + }); + return okAsync(dbFile as File); + }); + } + + createFile( + fctx: FlowExecCtx, + fileData: Omit, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.create.started", + fctx, + meta: { userId: fileData.userId, filename: fileData.filename }, + }); + + const now = new Date(); + const insertData = { + ...fileData, + createdAt: now, + updatedAt: now, + } as any; + + return ResultAsync.fromPromise( + this.db.insert(file).values(insertData).returning(), + (error) => { + logDomainEvent({ + level: "error", + event: "files.create.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId: fileData.userId, filename: fileData.filename }, + }); + return fileErrors.createFileFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((result) => { + const created = result[0] as File; + logDomainEvent({ + event: "files.create.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { fileId: created.id, userId: created.userId }, + }); + return created; + }); + } + + updateFile( + fctx: FlowExecCtx, + fileId: string, + userId: string, + updates: FileUpdateRequest, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.update.started", + fctx, + meta: { + fileId, + userId, + hasFilename: updates.filename !== undefined, + hasMetadata: updates.metadata !== undefined, + hasTags: updates.tags !== undefined, + }, + }); + + const updateData = { + ...updates, + updatedAt: new Date(), + } as any; + + return ResultAsync.fromPromise( + this.db + .update(file) + .set(updateData) + .where(and(eq(file.id, fileId), eq(file.userId, userId))) + .returning(), + (error) => { + logDomainEvent({ + level: "error", + event: "files.update.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { fileId, userId }, + }); + return fileErrors.updateFileFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((result) => { + const updated = result[0]; + + if (!updated) { + logDomainEvent({ + level: "warn", + event: "files.update.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "File not found" }, + meta: { fileId, userId }, + }); + return errAsync(fileErrors.fileNotFound(fctx, fileId)); + } + + logDomainEvent({ + event: "files.update.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { fileId, userId }, + }); + return okAsync(updated as File); + }); + } + + deleteFiles( + fctx: FlowExecCtx, + fileIds: readonly string[], + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.delete.started", + fctx, + meta: { userId, fileCount: fileIds.length }, + }); + + return ResultAsync.fromPromise( + this.db + .delete(file) + .where(and(eq(file.userId, userId), inArray(file.id, [...fileIds]))), + (error) => { + logDomainEvent({ + level: "error", + event: "files.delete.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, fileCount: fileIds.length }, + }); + return fileErrors.deleteFilesFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "files.delete.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, fileCount: fileIds.length }, + }); + return true; + }); + } + + updateFileStatus( + fctx: FlowExecCtx, + fileId: string, + status: string, + processingError?: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.status_update.started", + fctx, + meta: { + fileId, + status, + hasProcessingError: Boolean(processingError), + }, + }); + + return ResultAsync.fromPromise( + this.db + .update(file) + .set({ + status, + processingError, + updatedAt: new Date(), + }) + .where(eq(file.id, fileId)), + (error) => { + logDomainEvent({ + level: "error", + event: "files.status_update.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { fileId, status }, + }); + return fileErrors.updateStatusFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "files.status_update.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { fileId, status }, + }); + return true; + }); + } + + shareFile( + fctx: FlowExecCtx, + fileId: string, + ownerId: string, + shareRequest: FileShareRequest, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.share.started", + fctx, + meta: { + fileId, + ownerId, + targetUserId: shareRequest.userId, + }, + }); + + return ResultAsync.fromPromise( + this.db + .select() + .from(file) + .where(and(eq(file.id, fileId), eq(file.userId, ownerId))) + .limit(1), + (error) => { + logDomainEvent({ + level: "error", + event: "files.share.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { fileId, ownerId, targetUserId: shareRequest.userId }, + }); + return fileErrors.shareFileFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((result) => { + const ownedFile = result[0]; + + if (!ownedFile) { + logDomainEvent({ + level: "warn", + event: "files.share.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "NOT_FOUND", + message: "File not found or not owned by user", + }, + meta: { fileId, ownerId, targetUserId: shareRequest.userId }, + }); + return errAsync(fileErrors.fileNotFound(fctx, fileId)); + } + + const now = new Date(); + + return ResultAsync.fromPromise( + this.db + .insert(fileAccess) + .values({ + fileId, + userId: shareRequest.userId, + canRead: shareRequest.permissions.canRead || false, + canWrite: shareRequest.permissions.canWrite || false, + canDelete: shareRequest.permissions.canDelete || false, + canShare: shareRequest.permissions.canShare || false, + grantedAt: now, + expiresAt: shareRequest.expiresAt, + createdAt: now, + updatedAt: now, + }) + .onConflictDoNothing(), + (error) => { + logDomainEvent({ + level: "error", + event: "files.share.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { + fileId, + ownerId, + targetUserId: shareRequest.userId, + }, + }); + return fileErrors.shareFileFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "files.share.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { fileId, ownerId, targetUserId: shareRequest.userId }, + }); + return true; + }); + }); + } +} diff --git a/packages/logic/domains/files/storage.repository.ts b/packages/logic/domains/files/storage.repository.ts new file mode 100644 index 0000000..ba5526a --- /dev/null +++ b/packages/logic/domains/files/storage.repository.ts @@ -0,0 +1,287 @@ +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import type { PresignedUploadResponse } from "./data"; +import { R2StorageClient } from "@pkg/objectstorage"; +import { type Err } from "@pkg/result"; +import { fileErrors } from "./errors"; +import { logDomainEvent } from "@pkg/logger"; + +export type StorageConfig = { + bucketName: string; + region: string; + endpoint: string; + accessKey: string; + secretKey: string; + publicUrl: string; + maxFileSize: number; + allowedMimeTypes: string[]; + allowedExtensions: string[]; +}; + +export type UploadOptions = { + visibility: "public" | "private"; + metadata?: Record; + tags?: string[]; + processImage?: boolean; + processDocument?: boolean; + processVideo?: boolean; +}; + +export type UploadedFileMetadata = { + id: string; + filename: string; + originalName: string; + mimeType: string; + size: number; + hash: string; + bucketName: string; + objectKey: string; + r2Url: string; + visibility: string; + userId: string; + metadata?: Record; + tags?: string[]; + uploadedAt: Date; +}; + +export class StorageRepository { + private storageClient: R2StorageClient; + + constructor(config: StorageConfig) { + this.storageClient = new R2StorageClient(config); + } + + uploadFile( + fctx: FlowExecCtx, + buffer: Buffer, + filename: string, + mimeType: string, + userId: string, + options: UploadOptions, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.storage.upload.started", + fctx, + meta: { + userId, + filename, + mimeType, + size: buffer.length, + visibility: options.visibility, + }, + }); + + return ResultAsync.fromPromise( + this.storageClient.uploadFile( + buffer, + filename, + mimeType, + userId, + options, + ), + (error) => { + logDomainEvent({ + level: "error", + event: "files.storage.upload.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, filename }, + }); + return fileErrors.uploadFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((uploadResult) => { + if (uploadResult.error) { + logDomainEvent({ + level: "error", + event: "files.storage.upload.failed", + fctx, + durationMs: Date.now() - startedAt, + error: uploadResult.error, + meta: { userId, filename, stage: "storage_response" }, + }); + return errAsync( + fileErrors.uploadFailed(fctx, String(uploadResult.error)), + ); + } + + const uploadData = uploadResult.data; + if (!uploadData || !uploadData.file) { + logDomainEvent({ + level: "error", + event: "files.storage.upload.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "NO_FILE_METADATA", + message: "Storage upload returned no file metadata", + }, + meta: { userId, filename }, + }); + return errAsync(fileErrors.noFileMetadata(fctx)); + } + + logDomainEvent({ + event: "files.storage.upload.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, fileId: uploadData.file.id, filename }, + }); + return okAsync(uploadData.file as UploadedFileMetadata); + }); + } + + generatePresignedUploadUrl( + fctx: FlowExecCtx, + objectKey: string, + mimeType: string, + expiresIn: number, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.storage.presigned.started", + fctx, + meta: { objectKey, mimeType, expiresIn }, + }); + + return ResultAsync.fromPromise( + this.storageClient.generatePresignedUploadUrl( + objectKey, + mimeType, + expiresIn, + ), + (error) => { + logDomainEvent({ + level: "error", + event: "files.storage.presigned.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { objectKey }, + }); + return fileErrors.presignedUrlFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((result) => { + if (result.error) { + logDomainEvent({ + level: "error", + event: "files.storage.presigned.failed", + fctx, + durationMs: Date.now() - startedAt, + error: result.error, + meta: { objectKey, stage: "storage_response" }, + }); + return errAsync( + fileErrors.presignedUrlFailed(fctx, String(result.error)), + ); + } + + const presignedData = result.data; + if (!presignedData) { + logDomainEvent({ + level: "error", + event: "files.storage.presigned.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "NO_PRESIGNED_DATA", + message: "No presigned data returned", + }, + meta: { objectKey }, + }); + return errAsync(fileErrors.noPresignedData(fctx)); + } + + logDomainEvent({ + event: "files.storage.presigned.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { objectKey }, + }); + return okAsync(presignedData as PresignedUploadResponse); + }); + } + + deleteFile( + fctx: FlowExecCtx, + objectKey: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.storage.delete.started", + fctx, + meta: { objectKey }, + }); + + return ResultAsync.fromPromise( + this.storageClient.deleteFile(objectKey), + (error) => { + logDomainEvent({ + level: "error", + event: "files.storage.delete.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { objectKey }, + }); + return fileErrors.storageError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((result) => { + if (result.error) { + logDomainEvent({ + level: "error", + event: "files.storage.delete.failed", + fctx, + durationMs: Date.now() - startedAt, + error: result.error, + meta: { objectKey, stage: "storage_response" }, + }); + return errAsync( + fileErrors.storageError(fctx, String(result.error)), + ); + } + + logDomainEvent({ + event: "files.storage.delete.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { objectKey }, + }); + return okAsync(true); + }); + } + + deleteFiles( + fctx: FlowExecCtx, + objectKeys: string[], + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "files.storage.delete_many.started", + fctx, + meta: { fileCount: objectKeys.length }, + }); + + return ResultAsync.combine( + objectKeys.map((key) => this.deleteFile(fctx, key)), + ).map(() => { + logDomainEvent({ + event: "files.storage.delete_many.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { fileCount: objectKeys.length }, + }); + return true; + }); + } +} diff --git a/packages/objectstorage/.gitignore b/packages/objectstorage/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/objectstorage/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/objectstorage/index.ts b/packages/objectstorage/index.ts new file mode 100644 index 0000000..ed9236b --- /dev/null +++ b/packages/objectstorage/index.ts @@ -0,0 +1,4 @@ +export * from "./src/client"; +export * from "./src/data"; +export * from "./src/processors"; +export * from "./src/validation"; diff --git a/packages/objectstorage/package.json b/packages/objectstorage/package.json new file mode 100644 index 0000000..1607171 --- /dev/null +++ b/packages/objectstorage/package.json @@ -0,0 +1,23 @@ +{ + "name": "@pkg/objectstorage", + "module": "index.ts", + "type": "module", + "dependencies": { + "@aws-sdk/client-s3": "^3.832.0", + "@aws-sdk/s3-request-presigner": "^3.832.0", + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/result": "workspace:*", + "mime-types": "^3.0.1", + "sharp": "^0.34.2", + "valibot": "^1.2.0" + }, + "devDependencies": { + "@types/mime-types": "^3.0.1", + "@types/node": "latest", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "typescript": "^5.9.3" + } +} diff --git a/packages/objectstorage/src/client.ts b/packages/objectstorage/src/client.ts new file mode 100644 index 0000000..8a55039 --- /dev/null +++ b/packages/objectstorage/src/client.ts @@ -0,0 +1,484 @@ +import { + CopyObjectCommand, + DeleteObjectCommand, + GetObjectCommand, + HeadObjectCommand, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import type { + FileMetadata, + FileUploadConfig, + PresignedUrlResult, + UploadOptions, + UploadResult, +} from "./data"; +import { + generateFileHash, + generateObjectKey, + isDocumentFile, + isImageFile, + isVideoFile, +} from "./utils"; +import { processDocument } from "./processors/document-processor"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { processVideo } from "./processors/video-processor"; +import { processImage } from "./processors/image-processor"; +import { ERROR_CODES, type Result } from "@pkg/result"; +import { getError, logger } from "@pkg/logger"; +import { validateFile } from "./validation"; + +export class R2StorageClient { + private s3Client: S3Client; + private config: FileUploadConfig; + + constructor(config: FileUploadConfig) { + this.config = config; + this.s3Client = new S3Client({ + region: config.region, + endpoint: config.endpoint, + credentials: { + accessKeyId: config.accessKey, + secretAccessKey: config.secretKey, + }, + }); + } + + /** + * Upload a file directly to R2 + */ + async uploadFile( + file: Buffer | Uint8Array, + originalName: string, + mimeType: string, + userId: string, + options?: UploadOptions, + ): Promise> { + try { + // Validate file + const validationResult = validateFile( + file, + originalName, + mimeType, + this.config, + ); + if (!validationResult.isValid) { + return { + error: getError({ + code: ERROR_CODES.VALIDATION_ERROR, + message: "File validation failed", + description: validationResult.errors.join(", "), + detail: "File validation failed", + }), + }; + } + + // Generate file hash for deduplication + const hash = generateFileHash(file); + + // Generate unique filename and object key + const fileId = crypto.randomUUID(); + const extension = originalName.split(".").pop() || ""; + const filename = `${fileId}.${extension}`; + const objectKey = generateObjectKey(userId, filename); + + let processedFile = Buffer.from(file); + let thumbnailBuffer: Buffer | undefined; + let metadata = options?.metadata ? { ...options?.metadata } : {}; + + // Process file based on type + if (options?.processImage && isImageFile(mimeType)) { + const processingResult = await processImage(file, { + format: "webp", + quality: 85, + generateThumbnail: true, + thumbnailSize: { width: 300, height: 300 }, + resize: { + width: 1920, + height: 1920, + fit: "inside", + }, + }); + + if ( + processingResult.processed && + processingResult.processedFile + ) { + processedFile = Buffer.from(processingResult.processedFile); + thumbnailBuffer = processingResult.thumbnail + ? Buffer.from(processingResult.thumbnail) + : undefined; + metadata = { ...metadata, ...processingResult.metadata }; + } + } else if (options?.processDocument && isDocumentFile(mimeType)) { + const processingResult = await processDocument(file, mimeType, { + extractText: true, + generatePreview: true, + extractMetadata: true, + }); + + if (processingResult.processed && processingResult.metadata) { + metadata = { ...metadata, ...processingResult.metadata }; + } + } else if (options?.processVideo && isVideoFile(mimeType)) { + const processingResult = await processVideo(file, mimeType, { + generateThumbnail: true, + extractMetadata: true, + thumbnailTimestamp: 1, // 1 second into video + }); + + if (processingResult.processed && processingResult.metadata) { + metadata = { ...metadata, ...processingResult.metadata }; + } + } + + // Upload main file to R2 + const uploadCommand = new PutObjectCommand({ + Bucket: this.config.bucketName, + Key: objectKey, + Body: processedFile, + ContentType: mimeType, + Metadata: { + originalName, + userId, + hash, + uploadId: fileId, + processed: "true", + ...Object.fromEntries( + Object.entries(metadata).map(([key, value]) => [ + key, + typeof value === "string" + ? value + : JSON.stringify(value), + ]), + ), + }, + }); + + await this.s3Client.send(uploadCommand); + + // Upload thumbnail if generated + if (thumbnailBuffer) { + const thumbnailKey = `thumbnails/${userId}/${fileId}_thumb.webp`; + const thumbnailCommand = new PutObjectCommand({ + Bucket: this.config.bucketName, + Key: thumbnailKey, + Body: thumbnailBuffer, + ContentType: "image/webp", + Metadata: { + originalFileId: fileId, + type: "thumbnail", + }, + }); + + await this.s3Client.send(thumbnailCommand); + metadata.thumbnailKey = thumbnailKey; + } + + // Construct R2 URL + const r2Url = `${this.config.publicUrl || this.config.endpoint}/${objectKey}`; + + const fileMetadata: FileMetadata = { + id: fileId, + filename, + originalName, + mimeType, + size: processedFile.length, + hash, + bucketName: this.config.bucketName, + objectKey, + r2Url, + visibility: options?.visibility || "private", + userId, + metadata, + tags: options?.tags, + uploadedAt: new Date(), + }; + + const result: UploadResult = { + success: true, + file: fileMetadata, + uploadId: fileId, + }; + + logger.info( + `Successfully uploaded file ${fileId} for user ${userId}`, + ); + return { data: result }; + } catch (error) { + logger.error("File upload failed:", error); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Upload failed", + description: "Failed to upload file to storage", + detail: "S3 upload operation failed", + }, + error, + ), + }; + } + } + + /** + * Generate presigned URL for direct upload + */ + async generatePresignedUploadUrl( + objectKey: string, + mimeType: string, + expiresIn: number = 3600, + ): Promise> { + try { + const command = new PutObjectCommand({ + Bucket: this.config.bucketName, + Key: objectKey, + ContentType: mimeType, + }); + + const uploadUrl = await getSignedUrl(this.s3Client, command, { + expiresIn, + }); + + const result: PresignedUrlResult = { + uploadUrl, + expiresIn, + }; + + logger.info(`Generated presigned URL for ${objectKey}`); + return { data: result }; + } catch (error) { + logger.error("Failed to generate presigned URL:", error); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Failed to generate presigned URL", + description: "Could not create upload URL", + detail: "S3 presigned URL generation failed", + }, + error, + ), + }; + } + } + + /** + * Get file from R2 + */ + async getFile(objectKey: string): Promise> { + try { + const command = new GetObjectCommand({ + Bucket: this.config.bucketName, + Key: objectKey, + }); + + const response = await this.s3Client.send(command); + const body = response.Body; + + if (!body) { + return { + error: getError({ + code: ERROR_CODES.NOT_FOUND, + message: "File not found", + description: "The requested file does not exist", + detail: "S3 response body is empty", + }), + }; + } + + // Handle different response body types + if (body instanceof Uint8Array) { + return { data: Buffer.from(body) }; + } + + // For Node.js Readable streams (AWS SDK v3) + if (typeof body.transformToByteArray === "function") { + const byteArray = await body.transformToByteArray(); + return { data: Buffer.from(byteArray) }; + } + + // Fallback: treat as readable stream + const chunks: Buffer[] = []; + + // Type assertion to handle the stream properly + const stream = body as NodeJS.ReadableStream; + + return new Promise>((resolve, reject) => { + stream.on("data", (chunk: Buffer) => { + chunks.push(chunk); + }); + + stream.on("end", () => { + const buffer = Buffer.concat(chunks); + logger.info(`Successfully retrieved file ${objectKey}`); + resolve({ data: buffer }); + }); + + stream.on("error", (error) => { + reject(error); + }); + }); + } catch (error) { + logger.error(`Failed to get file ${objectKey}:`, error); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Failed to get file", + description: "Could not retrieve file from storage", + detail: "S3 get operation failed", + }, + error, + ), + }; + } + } + + /** + * Delete file from R2 + */ + async deleteFile(objectKey: string): Promise> { + try { + const command = new DeleteObjectCommand({ + Bucket: this.config.bucketName, + Key: objectKey, + }); + + await this.s3Client.send(command); + logger.info(`Successfully deleted file ${objectKey}`); + return { data: true }; + } catch (error) { + logger.error(`Failed to delete file ${objectKey}:`, error); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Failed to delete file", + description: "Could not delete file from storage", + detail: "S3 delete operation failed", + }, + error, + ), + }; + } + } + + /** + * Get file metadata from R2 + */ + async getFileMetadata( + objectKey: string, + ): Promise>> { + try { + const command = new HeadObjectCommand({ + Bucket: this.config.bucketName, + Key: objectKey, + }); + + const response = await this.s3Client.send(command); + + const metadata = { + size: response.ContentLength, + lastModified: response.LastModified, + contentType: response.ContentType, + metadata: response.Metadata || {}, + }; + + logger.info(`Successfully retrieved metadata for ${objectKey}`); + return { data: metadata }; + } catch (error) { + logger.error( + `Failed to get file metadata for ${objectKey}:`, + error, + ); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Failed to get file metadata", + description: "Could not retrieve file information", + detail: "S3 head operation failed", + }, + error, + ), + }; + } + } + + /** + * Check if a file exists in R2 + */ + async fileExists(objectKey: string): Promise> { + try { + const command = new HeadObjectCommand({ + Bucket: this.config.bucketName, + Key: objectKey, + }); + + await this.s3Client.send(command); + return { data: true }; + } catch (error: any) { + if ( + error.name === "NotFound" || + error.$metadata?.httpStatusCode === 404 + ) { + return { data: false }; + } + + logger.error( + `Failed to check file existence for ${objectKey}:`, + error, + ); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Failed to check file existence", + description: "Could not verify if file exists", + detail: "S3 head operation failed", + }, + error, + ), + }; + } + } + + /** + * Copy file within R2 + */ + async copyFile( + sourceKey: string, + destinationKey: string, + ): Promise> { + try { + const command = new CopyObjectCommand({ + Bucket: this.config.bucketName, + Key: destinationKey, + CopySource: `${this.config.bucketName}/${sourceKey}`, + }); + + await this.s3Client.send(command); + logger.info( + `Successfully copied file from ${sourceKey} to ${destinationKey}`, + ); + return { data: true }; + } catch (error) { + logger.error( + `Failed to copy file from ${sourceKey} to ${destinationKey}:`, + error, + ); + return { + error: getError( + { + code: ERROR_CODES.STORAGE_ERROR, + message: "Failed to copy file", + description: "Could not copy file in storage", + detail: "S3 copy operation failed", + }, + error, + ), + }; + } + } +} diff --git a/packages/objectstorage/src/data.ts b/packages/objectstorage/src/data.ts new file mode 100644 index 0000000..77cc6a3 --- /dev/null +++ b/packages/objectstorage/src/data.ts @@ -0,0 +1,154 @@ +import * as v from "valibot"; + +// File Upload Configuration Schema +export const fileUploadConfigSchema = v.object({ + bucketName: v.string(), + region: v.string(), + endpoint: v.string(), + accessKey: v.string(), + secretKey: v.string(), + publicUrl: v.optional(v.string()), + maxFileSize: v.pipe(v.number(), v.integer()), // in bytes + allowedMimeTypes: v.array(v.string()), + allowedExtensions: v.array(v.string()), +}); +export type FileUploadConfig = v.InferOutput; + +// File Visibility Schema +export const fileVisibilitySchema = v.picklist([ + "public", + "private", + "restricted", +]); +export type FileVisibility = v.InferOutput; + +// File Metadata Schema +export const fileMetadataSchema = v.object({ + id: v.string(), + filename: v.string(), + originalName: v.string(), + mimeType: v.string(), + size: v.pipe(v.number(), v.integer()), + hash: v.string(), + bucketName: v.string(), + objectKey: v.string(), + r2Url: v.string(), + visibility: fileVisibilitySchema, + userId: v.string(), + metadata: v.optional(v.record(v.string(), v.any())), + tags: v.optional(v.array(v.string())), + uploadedAt: v.date(), + expiresAt: v.optional(v.date()), +}); +export type FileMetadata = v.InferOutput; + +// Upload Result Schema +export const uploadResultSchema = v.object({ + success: v.boolean(), + file: v.optional(fileMetadataSchema), + error: v.optional(v.string()), + uploadId: v.optional(v.string()), +}); +export type UploadResult = v.InferOutput; + +// Presigned URL Result Schema +export const presignedUrlResultSchema = v.object({ + uploadUrl: v.string(), + downloadUrl: v.optional(v.string()), + expiresIn: v.pipe(v.number(), v.integer()), + fields: v.optional(v.record(v.string(), v.any())), +}); +export type PresignedUrlResult = v.InferOutput; + +// File Validation Result Schema +export const fileValidationResultSchema = v.object({ + isValid: v.boolean(), + errors: v.array(v.string()), + warnings: v.array(v.string()), +}); +export type FileValidationResult = v.InferOutput< + typeof fileValidationResultSchema +>; + +// Image Resize Options Schema +export const imageResizeOptionsSchema = v.object({ + width: v.optional(v.pipe(v.number(), v.integer())), + height: v.optional(v.pipe(v.number(), v.integer())), + fit: v.optional( + v.picklist(["cover", "contain", "fill", "inside", "outside"]), + ), +}); +export type ImageResizeOptions = v.InferOutput; + +// Thumbnail Size Schema +export const thumbnailSizeSchema = v.object({ + width: v.pipe(v.number(), v.integer()), + height: v.pipe(v.number(), v.integer()), +}); +export type ThumbnailSize = v.InferOutput; + +// Image Processing Options Schema +export const imageProcessingOptionsSchema = v.object({ + resize: v.optional(imageResizeOptionsSchema), + format: v.optional(v.picklist(["jpeg", "png", "webp", "avif"])), + quality: v.optional(v.pipe(v.number(), v.integer())), + generateThumbnail: v.optional(v.boolean()), + thumbnailSize: v.optional(thumbnailSizeSchema), +}); +export type ImageProcessingOptions = v.InferOutput< + typeof imageProcessingOptionsSchema +>; + +// File Processing Result Schema +export const fileProcessingResultSchema = v.object({ + processed: v.boolean(), + originalFile: v.optional(v.instance(Uint8Array)), // Buffer equivalent + processedFile: v.optional(v.instance(Uint8Array)), // Buffer equivalent + thumbnail: v.optional(v.instance(Uint8Array)), // Buffer equivalent + metadata: v.optional(v.record(v.string(), v.any())), + error: v.optional(v.string()), +}); +export type FileProcessingResult = v.InferOutput< + typeof fileProcessingResultSchema +>; + +// File Security Result Schema (from utils.ts) +export const fileSecurityResultSchema = v.object({ + isSecure: v.boolean(), + issues: v.array(v.string()), + warnings: v.array(v.string()), +}); +export type FileSecurityResult = v.InferOutput; + +// Document Processing Options Schema +export const documentProcessingOptionsSchema = v.object({ + extractText: v.optional(v.boolean()), + generatePreview: v.optional(v.boolean()), + extractMetadata: v.optional(v.boolean()), + validateStructure: v.optional(v.boolean()), +}); +export type DocumentProcessingOptions = v.InferOutput< + typeof documentProcessingOptionsSchema +>; + +// Video Processing Options Schema +export const videoProcessingOptionsSchema = v.object({ + generateThumbnail: v.optional(v.boolean()), + extractMetadata: v.optional(v.boolean()), + thumbnailTimestamp: v.optional(v.number()), // Seconds into video for thumbnail + thumbnailSize: v.optional(thumbnailSizeSchema), +}); +export type VideoProcessingOptions = v.InferOutput< + typeof videoProcessingOptionsSchema +>; + +// Upload Options Schema (used in client.ts) +export const uploadOptionsSchema = v.object({ + visibility: v.optional(fileVisibilitySchema), + metadata: v.optional(v.record(v.string(), v.any())), + tags: v.optional(v.array(v.string())), + processImage: v.optional(v.boolean()), + processDocument: v.optional(v.boolean()), + processVideo: v.optional(v.boolean()), +}); +export type UploadOptions = v.InferOutput; diff --git a/packages/objectstorage/src/processors/document-processor.ts b/packages/objectstorage/src/processors/document-processor.ts new file mode 100644 index 0000000..578999d --- /dev/null +++ b/packages/objectstorage/src/processors/document-processor.ts @@ -0,0 +1,132 @@ +import { createHash } from "crypto"; +import type { DocumentProcessingOptions, FileProcessingResult } from "../data"; + +/** + * Process documents (PDF, text files, etc.) + */ +export async function processDocument( + buffer: Buffer | Uint8Array, + mimeType: string, + options: DocumentProcessingOptions = {}, +): Promise { + try { + const inputBuffer = Buffer.from(buffer); + const metadata: Record = {}; + + // Basic document information + metadata.original = { + size: inputBuffer.length, + mimeType, + hash: createHash("sha256").update(inputBuffer).digest("hex"), + }; + + // Process based on document type + if (mimeType === "application/pdf") { + return await processPDF(inputBuffer, options); + } else if (mimeType.startsWith("text/")) { + return await processTextFile(inputBuffer, options); + } else { + return await processGenericDocument(inputBuffer, options); + } + } catch (error) { + return { + processed: false, + error: `Document processing failed: ${error instanceof Error ? error.message : String(error)}`, + }; + } +} + +async function processPDF( + buffer: Buffer, + options: DocumentProcessingOptions, +): Promise { + const metadata: Record = { + type: "pdf", + processed: true, + }; + + // In a real implementation, you would use a PDF library like pdf2pic or pdf-parse + // For now, we'll just provide basic processing + + if (options.extractMetadata) { + // Extract PDF metadata (page count, author, title, etc.) + metadata.pdf = { + // This would be extracted using a PDF library + pageCount: 1, // Placeholder + title: "Unknown", + author: "Unknown", + creationDate: new Date().toISOString(), + }; + } + + if (options.extractText) { + // Extract text content from PDF + metadata.textContent = { + extracted: true, + characterCount: 0, // Placeholder + wordCount: 0, // Placeholder + }; + } + + if (options.generatePreview) { + // Generate thumbnail/preview of first page + metadata.preview = { + generated: true, + format: "png", + }; + } + + return { + processed: true, + originalFile: buffer, + processedFile: buffer, // PDFs typically don't need processing + metadata, + }; +} + +async function processTextFile( + buffer: Buffer, + options: DocumentProcessingOptions, +): Promise { + const text = buffer.toString("utf-8"); + const metadata: Record = { + type: "text", + processed: true, + }; + + if (options.extractText || options.extractMetadata) { + const lines = text.split("\n"); + const words = text.split(/\s+/).filter((word) => word.length > 0); + + metadata.textAnalysis = { + characterCount: text.length, + wordCount: words.length, + lineCount: lines.length, + encoding: "utf-8", + }; + } + + return { + processed: true, + originalFile: buffer, + processedFile: buffer, + metadata, + }; +} + +async function processGenericDocument( + buffer: Buffer, + options: DocumentProcessingOptions, +): Promise { + const metadata: Record = { + type: "generic", + processed: true, + }; + + return { + processed: true, + originalFile: buffer, + processedFile: buffer, + metadata, + }; +} diff --git a/packages/objectstorage/src/processors/image-processor.ts b/packages/objectstorage/src/processors/image-processor.ts new file mode 100644 index 0000000..d4f6c84 --- /dev/null +++ b/packages/objectstorage/src/processors/image-processor.ts @@ -0,0 +1,286 @@ +import sharp from "sharp"; +import type { FileProcessingResult, ImageProcessingOptions } from "../data"; + +/** + * Process images with compression, resizing, format conversion, and thumbnail generation + */ +export async function processImage( + buffer: Buffer | Uint8Array, + options: ImageProcessingOptions = {}, +): Promise { + try { + const inputBuffer = Buffer.isBuffer(buffer) + ? buffer + : Buffer.from(buffer); + let processedBuffer = inputBuffer; + let thumbnailBuffer: Buffer | undefined; + const metadata: Record = {}; + + // Initialize Sharp instance + const image = sharp(inputBuffer); + const originalMetadata = await image.metadata(); + + // Store original metadata + metadata.original = { + width: originalMetadata.width, + height: originalMetadata.height, + format: originalMetadata.format, + size: inputBuffer.length, + colorSpace: originalMetadata.space, + channels: originalMetadata.channels, + density: originalMetadata.density, + hasAlpha: originalMetadata.hasAlpha, + }; + + // Apply transformations + let transformedImage = image; + + // Resize if requested + if (options.resize) { + const { width, height, fit = "cover" } = options.resize; + + transformedImage = transformedImage.resize(width, height, { + fit: fit as keyof sharp.FitEnum, + withoutEnlargement: true, // Don't enlarge smaller images + }); + + metadata.processed = { + ...metadata.processed, + resized: true, + targetWidth: width, + targetHeight: height, + fit, + }; + } + + // Apply format conversion and quality settings + const outputFormat = options.format || "webp"; + const quality = options.quality || 85; + + switch (outputFormat) { + case "jpeg": + transformedImage = transformedImage.jpeg({ + quality, + progressive: true, + mozjpeg: true, // Use mozjpeg encoder for better compression + }); + break; + case "png": + transformedImage = transformedImage.png({ + quality, + compressionLevel: 9, + progressive: true, + }); + break; + case "webp": + transformedImage = transformedImage.webp({ + quality, + effort: 6, // Max compression effort + }); + break; + case "avif": + transformedImage = transformedImage.avif({ + quality, + effort: 6, + }); + break; + default: + // Keep original format but apply quality if possible + if (originalMetadata.format === "jpeg") { + transformedImage = transformedImage.jpeg({ quality }); + } else if (originalMetadata.format === "png") { + transformedImage = transformedImage.png({ quality }); + } + } + + // Generate processed image + processedBuffer = await transformedImage.toBuffer(); + + // Get final metadata + const finalMetadata = await sharp(processedBuffer).metadata(); + metadata.processed = { + ...metadata.processed, + width: finalMetadata.width, + height: finalMetadata.height, + format: outputFormat, + size: processedBuffer.length, + quality, + compressionRatio: inputBuffer.length / processedBuffer.length, + }; + + // Generate thumbnail if requested + if (options.generateThumbnail) { + const thumbSize = options.thumbnailSize || { + width: 300, + height: 300, + }; + + thumbnailBuffer = await sharp(inputBuffer) + .resize(thumbSize.width, thumbSize.height, { + fit: "cover", + position: "center", + }) + .webp({ quality: 80 }) + .toBuffer(); + + const thumbMetadata = await sharp(thumbnailBuffer).metadata(); + metadata.thumbnail = { + width: thumbMetadata.width, + height: thumbMetadata.height, + format: "webp", + size: thumbnailBuffer.length, + }; + } + + // Add processing stats + metadata.processing = { + processedAt: new Date().toISOString(), + sizeSaving: inputBuffer.length - processedBuffer.length, + sizeSavingPercentage: + ((inputBuffer.length - processedBuffer.length) / + inputBuffer.length) * + 100, + processingTime: Date.now(), // You'd measure this properly in production + }; + + return { + processed: true, + originalFile: inputBuffer, + processedFile: processedBuffer, + thumbnail: thumbnailBuffer, + metadata, + }; + } catch (error) { + return { + processed: false, + error: `Image processing failed: ${error instanceof Error ? error.message : String(error)}`, + }; + } +} + +/** + * Extract image metadata without processing + */ +export async function extractImageMetadata( + buffer: Buffer | Uint8Array, +): Promise> { + try { + const inputBuffer = Buffer.isBuffer(buffer) + ? buffer + : Buffer.from(buffer); + const image = sharp(inputBuffer); + const metadata = await image.metadata(); + + return { + width: metadata.width, + height: metadata.height, + format: metadata.format, + size: inputBuffer.length, + colorSpace: metadata.space, + channels: metadata.channels, + density: metadata.density, + hasAlpha: metadata.hasAlpha, + isAnimated: metadata.pages && metadata.pages > 1, + orientation: metadata.orientation, + }; + } catch (error) { + throw new Error( + `Failed to extract image metadata: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} + +/** + * Generate multiple sizes for responsive images + */ +export async function generateResponsiveSizes( + buffer: Buffer | Uint8Array, + sizes: Array<{ name: string; width: number; height?: number }> = [ + { name: "small", width: 400 }, + { name: "medium", width: 800 }, + { name: "large", width: 1200 }, + { name: "xlarge", width: 1920 }, + ], +): Promise> { + const results: Record = {}; + const inputBuffer = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer); + + try { + for (const size of sizes) { + const resized = await sharp(inputBuffer) + .resize(size.width, size.height, { + fit: "inside", + withoutEnlargement: true, + }) + .webp({ quality: 85 }) + .toBuffer(); + + results[size.name] = resized; + } + + return results; + } catch (error) { + throw new Error( + `Failed to generate responsive sizes: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} + +/** + * Create an optimized avatar image + */ +export async function processAvatar( + buffer: Buffer | Uint8Array, + size: number = 200, +): Promise { + try { + const inputBuffer = Buffer.isBuffer(buffer) + ? buffer + : Buffer.from(buffer); + return await sharp(inputBuffer) + .resize(size, size, { fit: "cover", position: "center" }) + .webp({ quality: 90 }) + .toBuffer(); + } catch (error) { + throw new Error( + `Avatar processing failed: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} + +/** + * Remove EXIF data from images for privacy + */ +export async function stripExifData( + buffer: Buffer | Uint8Array, +): Promise { + try { + const inputBuffer = Buffer.isBuffer(buffer) + ? buffer + : Buffer.from(buffer); + return await sharp(inputBuffer) + .rotate() // Auto-rotate based on EXIF, then removes EXIF + .toBuffer(); + } catch (error) { + throw new Error( + `EXIF stripping failed: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} + +/** + * Validate if buffer contains a valid image + */ +export async function validateImage( + buffer: Buffer | Uint8Array, +): Promise { + try { + const inputBuffer = Buffer.isBuffer(buffer) + ? buffer + : Buffer.from(buffer); + const metadata = await sharp(inputBuffer).metadata(); + return !!(metadata.width && metadata.height && metadata.format); + } catch { + return false; + } +} diff --git a/packages/objectstorage/src/processors/index.ts b/packages/objectstorage/src/processors/index.ts new file mode 100644 index 0000000..f1f2737 --- /dev/null +++ b/packages/objectstorage/src/processors/index.ts @@ -0,0 +1,3 @@ +export * from "./document-processor"; +export * from "./image-processor"; +export * from "./video-processor"; diff --git a/packages/objectstorage/src/processors/video-processor.ts b/packages/objectstorage/src/processors/video-processor.ts new file mode 100644 index 0000000..2b31321 --- /dev/null +++ b/packages/objectstorage/src/processors/video-processor.ts @@ -0,0 +1,62 @@ +import { createHash } from "crypto"; +import type { FileProcessingResult, VideoProcessingOptions } from "../data"; + +/** + * Process video files (extract metadata, generate thumbnails) + * Note: This is a basic implementation. For production use, you'd want to use FFmpeg + */ +export async function processVideo( + buffer: Buffer | Uint8Array, + mimeType: string, + options: VideoProcessingOptions = {}, +): Promise { + try { + const inputBuffer = Buffer.from(buffer); + const metadata: Record = {}; + + // Basic video information + metadata.original = { + size: inputBuffer.length, + mimeType, + hash: createHash("sha256").update(inputBuffer).digest("hex"), + }; + + // For a real implementation, you would use FFmpeg through a library like fluent-ffmpeg + // This is a placeholder implementation + + if (options.extractMetadata) { + metadata.video = { + // These would be extracted using FFmpeg + duration: 0, // seconds + width: 1920, // placeholder + height: 1080, // placeholder + framerate: 30, // placeholder + bitrate: 5000000, // placeholder + codec: "h264", // placeholder + }; + } + + if (options.generateThumbnail) { + // Generate video thumbnail at specified timestamp + // This would use FFmpeg to extract a frame + metadata.thumbnail = { + generated: true, + timestamp: options.thumbnailTimestamp || 0, + format: "jpeg", + size: options.thumbnailSize || { width: 640, height: 360 }, + }; + } + + return { + processed: true, + originalFile: inputBuffer, + processedFile: inputBuffer, // Videos are typically not re-encoded during upload + metadata, + }; + } catch (error) { + return { + processed: false, + error: `Video processing failed: ${error instanceof Error ? error.message : String(error)}`, + }; + } +} diff --git a/packages/objectstorage/src/utils.ts b/packages/objectstorage/src/utils.ts new file mode 100644 index 0000000..78c00c8 --- /dev/null +++ b/packages/objectstorage/src/utils.ts @@ -0,0 +1,186 @@ +import { lookup } from "mime-types"; +import { createHash } from "crypto"; + +/** + * Generate a secure file hash for deduplication + */ +export function generateFileHash(buffer: Buffer | Uint8Array): string { + return createHash("sha256").update(buffer).digest("hex"); +} + +/** + * Generate a unique filename with timestamp and random suffix + */ +export function generateUniqueFilename( + originalName: string, + userId?: string, +): string { + const fileId = crypto.randomUUID(); + const timestamp = Date.now(); + const extension = getFileExtension(originalName); + const baseName = originalName.replace(`.${extension}`, "").slice(0, 50); // Limit length + + const sanitizedBaseName = sanitizeFilename(baseName); + const userPrefix = userId ? `${userId.slice(0, 8)}_` : ""; + + return `${userPrefix}${timestamp}_${sanitizedBaseName}_${fileId}.${extension}`; +} + +/** + * Sanitize filename for safe storage + */ +export function sanitizeFilename(filename: string): string { + return filename + .replace(/[^a-zA-Z0-9._-]/g, "_") // Replace unsafe characters + .replace(/_{2,}/g, "_") // Remove multiple underscores + .replace(/^_+|_+$/g, "") // Remove leading/trailing underscores + .toLowerCase(); +} + +/** + * Get file extension from filename + */ +export function getFileExtension(filename: string): string { + const parts = filename.split("."); + return parts.length > 1 ? parts.pop()!.toLowerCase() : ""; +} + +/** + * Get MIME type from filename + */ +export function getMimeTypeFromFilename(filename: string): string | null { + return lookup(filename) || null; +} + +/** + * Format file size in human readable format + */ +export function formatFileSize(bytes: number): string { + const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; + if (bytes === 0) return "0 Bytes"; + + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${Math.round((bytes / Math.pow(1024, i)) * 100) / 100} ${sizes[i]}`; +} + +/** + * Check if file type is an image + */ +export function isImageFile(mimeType: string): boolean { + return mimeType.startsWith("image/"); +} + +/** + * Check if file type is a video + */ +export function isVideoFile(mimeType: string): boolean { + return mimeType.startsWith("video/"); +} + +/** + * Check if file type is a document + */ +export function isDocumentFile(mimeType: string): boolean { + const documentTypes = [ + "application/pdf", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "text/plain", + "text/csv", + "application/rtf", + ]; + + return documentTypes.includes(mimeType) || mimeType.startsWith("text/"); +} + +/** + * Generate object key for R2 storage + */ +export function generateObjectKey( + userId: string, + filename: string, + category: string = "uploads", +): string { + const date = new Date(); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + + return `${category}/${userId}/${year}-${month}-${day}/${filename}`; +} + +/** + * Validate file against security rules + */ +export interface FileSecurityResult { + isSecure: boolean; + issues: string[]; + warnings: string[]; +} + +export function validateFileSecurity( + buffer: Buffer | Uint8Array, + filename: string, + mimeType: string, +): FileSecurityResult { + const issues: string[] = []; + const warnings: string[] = []; + + // Check for malicious file extensions + const dangerousExtensions = [ + "exe", + "bat", + "cmd", + "com", + "pif", + "scr", + "vbs", + "js", + "jar", + "app", + "deb", + "pkg", + "dmg", + "rpm", + ]; + + const extension = getFileExtension(filename); + if (dangerousExtensions.includes(extension)) { + issues.push(`Potentially dangerous file extension: .${extension}`); + } + + // Check for suspicious filename patterns + if ( + filename.includes("..") || + filename.includes("/") || + filename.includes("\\") + ) { + issues.push("Filename contains path traversal characters"); + } + + // Check for null bytes + if (filename.includes("\0")) { + issues.push("Filename contains null bytes"); + } + + // Check file size (basic DoS protection) + if (buffer.length === 0) { + issues.push("File is empty"); + } + + // Check for MIME type spoofing + const expectedMimeType = getMimeTypeFromFilename(filename); + if (expectedMimeType && expectedMimeType !== mimeType) { + warnings.push( + `MIME type mismatch: expected ${expectedMimeType}, got ${mimeType}`, + ); + } + + return { + isSecure: issues.length === 0, + issues, + warnings, + }; +} diff --git a/packages/objectstorage/src/validation.ts b/packages/objectstorage/src/validation.ts new file mode 100644 index 0000000..51bffa9 --- /dev/null +++ b/packages/objectstorage/src/validation.ts @@ -0,0 +1,81 @@ +import type { FileUploadConfig, FileValidationResult } from "./data"; +import { lookup } from "mime-types"; + +export function validateFile( + file: Buffer | Uint8Array, + originalName: string, + mimeType: string, + config: FileUploadConfig, +): FileValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + + // Check file size + if (file.length > config.maxFileSize) { + errors.push( + `File size ${file.length} exceeds maximum allowed size of ${config.maxFileSize} bytes`, + ); + } + + // Check MIME type + if (!config.allowedMimeTypes.includes(mimeType)) { + errors.push( + `MIME type ${mimeType} is not allowed. Allowed types: ${config.allowedMimeTypes.join(", ")}`, + ); + } + + // Check file extension + const extension = originalName.split(".").pop()?.toLowerCase(); + if (!extension || !config.allowedExtensions.includes(extension)) { + errors.push( + `File extension .${extension} is not allowed. Allowed extensions: ${config.allowedExtensions.join(", ")}`, + ); + } + + // Verify MIME type matches file extension + const expectedMimeType = lookup(originalName); + if (expectedMimeType && expectedMimeType !== mimeType) { + warnings.push( + `MIME type ${mimeType} doesn't match expected type ${expectedMimeType} for file ${originalName}`, + ); + } + + // Check for empty file + if (file.length === 0) { + errors.push("File is empty"); + } + + // Basic file signature validation for common types + if (mimeType.startsWith("image/")) { + const isValidImage = validateImageSignature(file, mimeType); + if (!isValidImage) { + errors.push("Invalid image file signature"); + } + } + + return { + isValid: errors.length === 0, + errors, + warnings, + }; +} + +function validateImageSignature( + file: Buffer | Uint8Array, + mimeType: string, +): boolean { + const buffer = Buffer.from(file); + + // Check basic image signatures + const signatures = { + "image/jpeg": [0xff, 0xd8, 0xff], + "image/png": [0x89, 0x50, 0x4e, 0x47], + "image/gif": [0x47, 0x49, 0x46], + "image/webp": [0x52, 0x49, 0x46, 0x46], + }; + + const signature = signatures[mimeType as keyof typeof signatures]; + if (!signature) return true; // Skip validation for unknown types + + return signature.every((byte, index) => buffer[index] === byte); +} diff --git a/packages/objectstorage/tsconfig.json b/packages/objectstorage/tsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/packages/objectstorage/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/packages/settings/index.ts b/packages/settings/index.ts index 6c2bdc7..5760e2b 100644 --- a/packages/settings/index.ts +++ b/packages/settings/index.ts @@ -32,6 +32,19 @@ export const settingsSchema = v.object({ otelServiceName: v.string(), otelExporterOtlpHttpEndpoint: v.string(), + + // R2/Object Storage settings + r2BucketName: v.string(), + r2Region: v.string(), + r2Endpoint: v.string(), + r2AccessKey: v.string(), + r2SecretKey: v.string(), + r2PublicUrl: v.optional(v.string()), + + // File upload settings + maxFileSize: v.number(), + allowedMimeTypes: v.array(v.string()), + allowedExtensions: v.array(v.string()), }); export type Settings = v.InferOutput; @@ -53,6 +66,16 @@ function getEnvNumber(key: string, defaultValue: number): number { return Number.isNaN(parsed) ? defaultValue : parsed; } +/** + * Parse comma-separated string into array + */ +function parseCommaSeparated(value: string): string[] { + return value + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0); +} + /** * Load and validate settings from environment variables */ @@ -91,6 +114,26 @@ function loadSettings(): Settings { otelExporterOtlpHttpEndpoint: getEnv( "OTEL_EXPORTER_OTLP_HTTP_ENDPOINT", ), + + // R2/Object Storage settings + r2BucketName: getEnv("R2_BUCKET_NAME"), + r2Region: getEnv("R2_REGION", "auto"), + r2Endpoint: getEnv("R2_ENDPOINT"), + r2AccessKey: getEnv("R2_ACCESS_KEY"), + r2SecretKey: getEnv("R2_SECRET_KEY"), + r2PublicUrl: getEnv("R2_PUBLIC_URL") || undefined, + + // File upload settings + maxFileSize: getEnvNumber("MAX_FILE_SIZE", 10485760), // 10MB default + allowedMimeTypes: parseCommaSeparated( + getEnv( + "ALLOWED_MIME_TYPES", + "image/jpeg,image/png,image/webp,image/gif,application/pdf,text/plain", + ), + ), + allowedExtensions: parseCommaSeparated( + getEnv("ALLOWED_EXTENSIONS", "jpg,jpeg,png,webp,gif,pdf,txt"), + ), }; try { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd1ebd2..d5d298b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,7 +67,7 @@ importers: version: link:../../packages/settings better-auth: specifier: ^1.4.20 - version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -98,13 +98,13 @@ importers: version: 0.561.0(svelte@5.53.6) '@sveltejs/adapter-node': specifier: ^5.5.4 - version: 5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + version: 5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) '@sveltejs/kit': specifier: ^2.53.4 - version: 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@tailwindcss/forms': specifier: ^0.5.10 version: 0.5.11(tailwindcss@4.2.1) @@ -113,7 +113,7 @@ importers: version: 0.5.19(tailwindcss@4.2.1) '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.2.1(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.2.1(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/table-core': specifier: ^8.21.3 version: 8.21.3 @@ -122,7 +122,7 @@ importers: version: 1.5.6 bits-ui: specifier: ^2.14.4 - version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -131,7 +131,7 @@ importers: version: 8.6.0(svelte@5.53.6) formsnap: specifier: ^2.0.1 - version: 2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)) + version: 2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)) layerchart: specifier: 2.0.0-next.43 version: 2.0.0-next.43(svelte@5.53.6) @@ -161,7 +161,7 @@ importers: version: 1.0.7(svelte@5.53.6) sveltekit-superforms: specifier: ^2.30.0 - version: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3) + version: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3) tailwind-merge: specifier: ^3.4.0 version: 3.5.0 @@ -185,10 +185,10 @@ importers: version: 1.0.0-next.7(svelte@5.53.6) vite: specifier: ^7.2.6 - version: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: ^4.0.15 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) apps/processor: dependencies: @@ -224,8 +224,8 @@ importers: specifier: ^16.4.7 version: 16.6.1 drizzle-orm: - specifier: ^0.36.1 - version: 0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8) + specifier: ^0.45.1 + version: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8) postgres: specifier: ^3.4.8 version: 3.4.8 @@ -240,8 +240,8 @@ importers: specifier: ^8.11.10 version: 8.16.0 drizzle-kit: - specifier: ^0.28.0 - version: 0.28.1 + specifier: ^0.31.9 + version: 0.31.9 packages/keystore: dependencies: @@ -315,7 +315,7 @@ importers: version: 0.43.1 better-auth: specifier: ^1.4.7 - version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) date-fns-tz: specifier: ^3.2.0 version: 3.2.0(date-fns@4.1.0) @@ -360,6 +360,43 @@ importers: specifier: ^10.0.0 version: 10.0.0 + packages/objectstorage: + dependencies: + '@aws-sdk/client-s3': + specifier: ^3.832.0 + version: 3.1000.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.832.0 + version: 3.1000.0 + '@pkg/db': + specifier: workspace:* + version: link:../db + '@pkg/logger': + specifier: workspace:* + version: link:../logger + '@pkg/result': + specifier: workspace:* + version: link:../result + mime-types: + specifier: ^3.0.1 + version: 3.0.2 + sharp: + specifier: ^0.34.2 + version: 0.34.5 + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) + devDependencies: + '@types/mime-types': + specifier: ^3.0.1 + version: 3.0.1 + '@types/node': + specifier: latest + version: 25.3.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/result: dependencies: typescript: @@ -397,6 +434,173 @@ packages: '@ark/util@0.56.0': resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.1000.0': + resolution: {integrity: sha512-7kPy33qNGq3NfwHC0412T6LDK1bp4+eiPzetX0sVd9cpTSXuQDKpoOFnB0Njj6uZjJDcLS3n2OeyarwwgkQ0Ow==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.15': + resolution: {integrity: sha512-AlC0oQ1/mdJ8vCIqu524j5RB7M8i8E24bbkZmya1CuiQxkY7SdIZAyw7NDNMGaNINQFq/8oGRMX0HeOfCVsl/A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.3': + resolution: {integrity: sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.13': + resolution: {integrity: sha512-6ljXKIQ22WFKyIs1jbORIkGanySBHaPPTOI4OxACP5WXgbcR0nDYfqNJfXEGwCK7IzHdNbCSFsNKKs0qCexR8Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.15': + resolution: {integrity: sha512-dJuSTreu/T8f24SHDNTjd7eQ4rabr0TzPh2UTCwYexQtzG3nTDKm1e5eIdhiroTMDkPEJeY+WPkA6F9wod/20A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.13': + resolution: {integrity: sha512-JKSoGb7XeabZLBJptpqoZIFbROUIS65NuQnEHGOpuT9GuuZwag2qciKANiDLFiYk4u8nSrJC9JIOnWKVvPVjeA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.13': + resolution: {integrity: sha512-RtYcrxdnJHKY8MFQGLltCURcjuMjnaQpAxPE6+/QEdDHHItMKZgabRe/KScX737F9vJMQsmJy9EmMOkCnoC1JQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.14': + resolution: {integrity: sha512-WqoC2aliIjQM/L3oFf6j+op/enT2i9Cc4UTxxMEKrJNECkq4/PlKE5BOjSYFcq6G9mz65EFbXJh7zOU4CvjSKQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.13': + resolution: {integrity: sha512-rsRG0LQA4VR+jnDyuqtXi2CePYSmfm5GNL9KxiW8DSe25YwJSr06W8TdUfONAC+rjsTI+aIH2rBGG5FjMeANrw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.13': + resolution: {integrity: sha512-fr0UU1wx8kNHDhTQBXioc/YviSW8iXuAxHvnH7eQUtn8F8o/FU3uu6EUMvAQgyvn7Ne5QFnC0Cj0BFlwCk+RFw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.13': + resolution: {integrity: sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.6': + resolution: {integrity: sha512-3H2bhvb7Cb/S6WFsBy/Dy9q2aegC9JmGH1inO8Lb2sWirSqpLJlZmvQHPE29h2tIxzv6el/14X/tLCQ8BQU6ZQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.6': + resolution: {integrity: sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.973.1': + resolution: {integrity: sha512-QLXsxsI6VW8LuGK+/yx699wzqP/NMCGk/hSGP+qtB+Lcff+23UlbahyouLlk+nfT7Iu021SkXBhnAuVd6IZcPw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.6': + resolution: {integrity: sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.6': + resolution: {integrity: sha512-XdZ2TLwyj3Am6kvUc67vquQvs6+D8npXvXgyEUJAdkUDx5oMFJKOqpK+UpJhVDsEL068WAJl2NEGzbSik7dGJQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.6': + resolution: {integrity: sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.6': + resolution: {integrity: sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.15': + resolution: {integrity: sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.6': + resolution: {integrity: sha512-acvMUX9jF4I2Ew+Z/EA6gfaFaz9ehci5wxBmXCZeulLuv8m+iGf6pY9uKz8TPjg39bdAz3hxoE0eLP8Qz+IYlA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.15': + resolution: {integrity: sha512-ABlFVcIMmuRAwBT+8q5abAxOr7WmaINirDJBnqGY5b5jSDo00UMlg/G4a0xoAgwm6oAECeJcwkvDlxDwKf58fQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.3': + resolution: {integrity: sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.6': + resolution: {integrity: sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1000.0': + resolution: {integrity: sha512-DP6EbwCD0CKzBwBnT1X6STB5i+bY765CxjMbWCATDhCgOB343Q6AHM9c1S/300Uc5waXWtI/Wdeak9Ru56JOvg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.3': + resolution: {integrity: sha512-gQYI/Buwp0CAGQxY7mR5VzkP56rkWq2Y1ROkFuXh5XY94DsSjJw62B3I0N0lysQmtwiL2ht2KHI9NylM/RP4FA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.999.0': + resolution: {integrity: sha512-cx0hHUlgXULfykx4rdu/ciNAJaa3AL5xz3rieCz7NKJ68MJwlj3664Y8WR5MGgxfyYJBdamnkjNSx5Kekuc0cg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.4': + resolution: {integrity: sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.2': + resolution: {integrity: sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.3': + resolution: {integrity: sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-format-url@3.972.6': + resolution: {integrity: sha512-0YNVNgFyziCejXJx0rzxPiD2rkxTWco4c9wiMF6n37Tb9aQvIF8+t7GyEyIFCwQHZ0VMQaAl+nCZHOYz5I5EKw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.4': + resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.6': + resolution: {integrity: sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==} + + '@aws-sdk/util-user-agent-node@3.973.0': + resolution: {integrity: sha512-A9J2G4Nf236e9GpaC1JnA8wRn6u6GjnOXiTwBLA6NUJhlBTIGfrTy+K1IazmF8y+4OFdW3O5TZlhyspJMqiqjA==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.8': + resolution: {integrity: sha512-Ql8elcUdYCha83Ol7NznBsgN5GVZnv3vUd86fEc6waU6oUdY0T1O9NODkEEOS/Uaogr87avDrUC6DSeM4oXjZg==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.3': + resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==} + engines: {node: '>=18.0.0'} + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -439,6 +643,9 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -447,9 +654,9 @@ packages: resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} deprecated: 'Merged into tsx: https://tsx.is' - '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -465,9 +672,9 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -483,9 +690,9 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} cpu: [arm] os: [android] @@ -501,9 +708,9 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} cpu: [x64] os: [android] @@ -519,9 +726,9 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -537,9 +744,9 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -555,9 +762,9 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -573,9 +780,9 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -591,9 +798,9 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -609,9 +816,9 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -627,9 +834,9 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -645,9 +852,9 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -663,9 +870,9 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -681,9 +888,9 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -699,9 +906,9 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -717,9 +924,9 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -735,9 +942,9 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -747,6 +954,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} @@ -759,9 +972,9 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -771,6 +984,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} @@ -783,9 +1002,9 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -795,6 +1014,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} @@ -807,9 +1032,9 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -825,9 +1050,9 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -843,9 +1068,9 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -861,9 +1086,9 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -915,6 +1140,159 @@ packages: '@iconify/utils@3.1.0': resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@internationalized/date@3.11.0': resolution: {integrity: sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==} @@ -1688,6 +2066,222 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@smithy/abort-controller@4.2.10': + resolution: {integrity: sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader-native@4.2.2': + resolution: {integrity: sha512-QzzYIlf4yg0w5TQaC9VId3B3ugSk1MI/wb7tgcHtd7CBV9gNRKZrhc2EPSxSZuDy10zUZ0lomNMgkc6/VVe8xg==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.1': + resolution: {integrity: sha512-y5d4xRiD6TzeP5BWlb+Ig/VFqF+t9oANNhGeMqyzU7obw7FYgTgVi50i5JqBTeKp+TABeDIeeXFZdz65RipNtA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.9': + resolution: {integrity: sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.23.6': + resolution: {integrity: sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.10': + resolution: {integrity: sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.10': + resolution: {integrity: sha512-A4ynrsFFfSXUHicfTcRehytppFBcY3HQxEGYiyGktPIOye3Ot7fxpiy4VR42WmtGI4Wfo6OXt/c1Ky1nUFxYYQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.10': + resolution: {integrity: sha512-0xupsu9yj9oDVuQ50YCTS9nuSYhGlrwqdaKQel9y2Fz7LU9fNErVlw9N0o4pm4qqvWEGbSTI4HKc6XJfB30MVw==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.10': + resolution: {integrity: sha512-8kn6sinrduk0yaYHMJDsNuiFpXwQwibR7n/4CDUqn4UgaG+SeBHu5jHGFdU9BLFAM7Q4/gvr9RYxBHz9/jKrhA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.10': + resolution: {integrity: sha512-uUrxPGgIffnYfvIOUmBM5i+USdEBRTdh7mLPttjphgtooxQ8CtdO1p6K5+Q4BBAZvKlvtJ9jWyrWpBJYzBKsyQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.10': + resolution: {integrity: sha512-aArqzOEvcs2dK+xQVCgLbpJQGfZihw8SD4ymhkwNTtwKbnrzdhJsFDKuMQnam2kF69WzgJYOU5eJlCx+CA32bw==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.11': + resolution: {integrity: sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.11': + resolution: {integrity: sha512-DrcAx3PM6AEbWZxsKl6CWAGnVwiz28Wp1ZhNu+Hi4uI/6C1PIZBIaPM2VoqBDAsOWbM6ZVzOEQMxFLLdmb4eBQ==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.10': + resolution: {integrity: sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.10': + resolution: {integrity: sha512-w78xsYrOlwXKwN5tv1GnKIRbHb1HygSpeZMP6xDxCPGf1U/xDHjCpJu64c5T35UKyEPwa0bPeIcvU69VY3khUA==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.10': + resolution: {integrity: sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.1': + resolution: {integrity: sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.10': + resolution: {integrity: sha512-Op+Dh6dPLWTjWITChFayDllIaCXRofOed8ecpggTC5fkh8yXes0vAEX7gRUfjGK+TlyxoCAA05gHbZW/zB9JwQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.10': + resolution: {integrity: sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.20': + resolution: {integrity: sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.37': + resolution: {integrity: sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.11': + resolution: {integrity: sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.10': + resolution: {integrity: sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.10': + resolution: {integrity: sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.12': + resolution: {integrity: sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.10': + resolution: {integrity: sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.10': + resolution: {integrity: sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.10': + resolution: {integrity: sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.10': + resolution: {integrity: sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.10': + resolution: {integrity: sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.5': + resolution: {integrity: sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.10': + resolution: {integrity: sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.0': + resolution: {integrity: sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.13.0': + resolution: {integrity: sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.10': + resolution: {integrity: sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.1': + resolution: {integrity: sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.1': + resolution: {integrity: sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.2': + resolution: {integrity: sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.1': + resolution: {integrity: sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.1': + resolution: {integrity: sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.36': + resolution: {integrity: sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.39': + resolution: {integrity: sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.1': + resolution: {integrity: sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.1': + resolution: {integrity: sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.10': + resolution: {integrity: sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.10': + resolution: {integrity: sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.15': + resolution: {integrity: sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.1': + resolution: {integrity: sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.1': + resolution: {integrity: sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.10': + resolution: {integrity: sha512-4eTWph/Lkg1wZEDAyObwme0kmhEb7J/JjibY2znJdrYRgKbKqB7YoEhhJVJ4R1g/SYih4zuwX7LpJaM8RsnTVg==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.1': + resolution: {integrity: sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==} + engines: {node: '>=18.0.0'} + '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} @@ -1876,12 +2470,18 @@ packages: '@types/memcached@2.2.10': resolution: {integrity: sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==} + '@types/mime-types@3.0.1': + resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==} + '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} '@types/node@25.3.2': resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==} + '@types/node@25.3.3': + resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} + '@types/oracledb@6.5.2': resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} @@ -2124,6 +2724,9 @@ packages: '@internationalized/date': ^3.8.1 svelte: ^5.33.0 + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -2402,40 +3005,40 @@ packages: resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} - drizzle-kit@0.28.1: - resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==} + drizzle-kit@0.31.9: + resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} hasBin: true - drizzle-orm@0.36.4: - resolution: {integrity: sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==} + drizzle-orm@0.45.1: + resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' + '@cloudflare/workers-types': '>=4' '@electric-sql/pglite': '>=0.2.0' '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' + '@planetscale/database': '>=1.13' '@prisma/client': '*' '@tidbcloud/serverless': '*' '@types/better-sqlite3': '*' '@types/pg': '*' - '@types/react': '>=18' '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' '@vercel/postgres': '>=0.8.0' '@xata.io/client': '*' better-sqlite3: '>=7' bun-types: '*' expo-sqlite: '>=14.0.0' + gel: '>=2' knex: '*' kysely: '*' mysql2: '>=2' pg: '>=8' postgres: '>=3' prisma: '*' - react: '>=18' sql.js: '>=1' sqlite3: '>=5' peerDependenciesMeta: @@ -2465,10 +3068,10 @@ packages: optional: true '@types/pg': optional: true - '@types/react': - optional: true '@types/sql.js': optional: true + '@upstash/redis': + optional: true '@vercel/postgres': optional: true '@xata.io/client': @@ -2479,6 +3082,8 @@ packages: optional: true expo-sqlite: optional: true + gel: + optional: true knex: optional: true kysely: @@ -2491,8 +3096,6 @@ packages: optional: true prisma: optional: true - react: - optional: true sql.js: optional: true sqlite3: @@ -2551,9 +3154,9 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} hasBin: true esbuild@0.27.3: @@ -2591,6 +3194,10 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-xml-parser@5.3.6: + resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==} + hasBin: true + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2931,6 +3538,14 @@ packages: resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==} engines: {node: '>=18'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -3353,6 +3968,11 @@ packages: selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -3362,6 +3982,10 @@ packages: set-cookie-parser@3.0.1: resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3438,6 +4062,9 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strnum@2.2.0: + resolution: {integrity: sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==} + style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} @@ -3861,6 +4488,466 @@ snapshots: '@ark/util@0.56.0': optional: true + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.4 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.4 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-locate-window': 3.965.4 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-locate-window': 3.965.4 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.4 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1000.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.15 + '@aws-sdk/credential-provider-node': 3.972.14 + '@aws-sdk/middleware-bucket-endpoint': 3.972.6 + '@aws-sdk/middleware-expect-continue': 3.972.6 + '@aws-sdk/middleware-flexible-checksums': 3.973.1 + '@aws-sdk/middleware-host-header': 3.972.6 + '@aws-sdk/middleware-location-constraint': 3.972.6 + '@aws-sdk/middleware-logger': 3.972.6 + '@aws-sdk/middleware-recursion-detection': 3.972.6 + '@aws-sdk/middleware-sdk-s3': 3.972.15 + '@aws-sdk/middleware-ssec': 3.972.6 + '@aws-sdk/middleware-user-agent': 3.972.15 + '@aws-sdk/region-config-resolver': 3.972.6 + '@aws-sdk/signature-v4-multi-region': 3.996.3 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-endpoints': 3.996.3 + '@aws-sdk/util-user-agent-browser': 3.972.6 + '@aws-sdk/util-user-agent-node': 3.973.0 + '@smithy/config-resolver': 4.4.9 + '@smithy/core': 3.23.6 + '@smithy/eventstream-serde-browser': 4.2.10 + '@smithy/eventstream-serde-config-resolver': 4.3.10 + '@smithy/eventstream-serde-node': 4.2.10 + '@smithy/fetch-http-handler': 5.3.11 + '@smithy/hash-blob-browser': 4.2.11 + '@smithy/hash-node': 4.2.10 + '@smithy/hash-stream-node': 4.2.10 + '@smithy/invalid-dependency': 4.2.10 + '@smithy/md5-js': 4.2.10 + '@smithy/middleware-content-length': 4.2.10 + '@smithy/middleware-endpoint': 4.4.20 + '@smithy/middleware-retry': 4.4.37 + '@smithy/middleware-serde': 4.2.11 + '@smithy/middleware-stack': 4.2.10 + '@smithy/node-config-provider': 4.3.10 + '@smithy/node-http-handler': 4.4.12 + '@smithy/protocol-http': 5.3.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + '@smithy/url-parser': 4.2.10 + '@smithy/util-base64': 4.3.1 + '@smithy/util-body-length-browser': 4.2.1 + '@smithy/util-body-length-node': 4.2.2 + '@smithy/util-defaults-mode-browser': 4.3.36 + '@smithy/util-defaults-mode-node': 4.2.39 + '@smithy/util-endpoints': 3.3.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-retry': 4.2.10 + '@smithy/util-stream': 4.5.15 + '@smithy/util-utf8': 4.2.1 + '@smithy/util-waiter': 4.2.10 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.15': + dependencies: + '@aws-sdk/types': 3.973.4 + '@aws-sdk/xml-builder': 3.972.8 + '@smithy/core': 3.23.6 + '@smithy/node-config-provider': 4.3.10 + '@smithy/property-provider': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/signature-v4': 5.3.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.3': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/types': 3.973.4 + '@smithy/property-provider': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.15': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/types': 3.973.4 + '@smithy/fetch-http-handler': 5.3.11 + '@smithy/node-http-handler': 4.4.12 + '@smithy/property-provider': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + '@smithy/util-stream': 4.5.15 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/credential-provider-env': 3.972.13 + '@aws-sdk/credential-provider-http': 3.972.15 + '@aws-sdk/credential-provider-login': 3.972.13 + '@aws-sdk/credential-provider-process': 3.972.13 + '@aws-sdk/credential-provider-sso': 3.972.13 + '@aws-sdk/credential-provider-web-identity': 3.972.13 + '@aws-sdk/nested-clients': 3.996.3 + '@aws-sdk/types': 3.973.4 + '@smithy/credential-provider-imds': 4.2.10 + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/nested-clients': 3.996.3 + '@aws-sdk/types': 3.973.4 + '@smithy/property-provider': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.14': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.13 + '@aws-sdk/credential-provider-http': 3.972.15 + '@aws-sdk/credential-provider-ini': 3.972.13 + '@aws-sdk/credential-provider-process': 3.972.13 + '@aws-sdk/credential-provider-sso': 3.972.13 + '@aws-sdk/credential-provider-web-identity': 3.972.13 + '@aws-sdk/types': 3.973.4 + '@smithy/credential-provider-imds': 4.2.10 + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/types': 3.973.4 + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/nested-clients': 3.996.3 + '@aws-sdk/token-providers': 3.999.0 + '@aws-sdk/types': 3.973.4 + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/nested-clients': 3.996.3 + '@aws-sdk/types': 3.973.4 + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-arn-parser': 3.972.2 + '@smithy/node-config-provider': 4.3.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-config-provider': 4.2.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.973.1': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.15 + '@aws-sdk/crc64-nvme': 3.972.3 + '@aws-sdk/types': 3.973.4 + '@smithy/is-array-buffer': 4.2.1 + '@smithy/node-config-provider': 4.3.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-stream': 4.5.15 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@aws/lambda-invoke-store': 0.2.3 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.15': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-arn-parser': 3.972.2 + '@smithy/core': 3.23.6 + '@smithy/node-config-provider': 4.3.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/signature-v4': 5.3.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + '@smithy/util-config-provider': 4.2.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-stream': 4.5.15 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.15': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-endpoints': 3.996.3 + '@smithy/core': 3.23.6 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.996.3': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.15 + '@aws-sdk/middleware-host-header': 3.972.6 + '@aws-sdk/middleware-logger': 3.972.6 + '@aws-sdk/middleware-recursion-detection': 3.972.6 + '@aws-sdk/middleware-user-agent': 3.972.15 + '@aws-sdk/region-config-resolver': 3.972.6 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-endpoints': 3.996.3 + '@aws-sdk/util-user-agent-browser': 3.972.6 + '@aws-sdk/util-user-agent-node': 3.973.0 + '@smithy/config-resolver': 4.4.9 + '@smithy/core': 3.23.6 + '@smithy/fetch-http-handler': 5.3.11 + '@smithy/hash-node': 4.2.10 + '@smithy/invalid-dependency': 4.2.10 + '@smithy/middleware-content-length': 4.2.10 + '@smithy/middleware-endpoint': 4.4.20 + '@smithy/middleware-retry': 4.4.37 + '@smithy/middleware-serde': 4.2.11 + '@smithy/middleware-stack': 4.2.10 + '@smithy/node-config-provider': 4.3.10 + '@smithy/node-http-handler': 4.4.12 + '@smithy/protocol-http': 5.3.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + '@smithy/url-parser': 4.2.10 + '@smithy/util-base64': 4.3.1 + '@smithy/util-body-length-browser': 4.2.1 + '@smithy/util-body-length-node': 4.2.2 + '@smithy/util-defaults-mode-browser': 4.3.36 + '@smithy/util-defaults-mode-node': 4.2.39 + '@smithy/util-endpoints': 3.3.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-retry': 4.2.10 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/config-resolver': 4.4.9 + '@smithy/node-config-provider': 4.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1000.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.996.3 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-format-url': 3.972.6 + '@smithy/middleware-endpoint': 4.4.20 + '@smithy/protocol-http': 5.3.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.3': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.15 + '@aws-sdk/types': 3.973.4 + '@smithy/protocol-http': 5.3.10 + '@smithy/signature-v4': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.999.0': + dependencies: + '@aws-sdk/core': 3.973.15 + '@aws-sdk/nested-clients': 3.996.3 + '@aws-sdk/types': 3.973.4 + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.4': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.2': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.3': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/types': 4.13.0 + '@smithy/url-parser': 4.2.10 + '@smithy/util-endpoints': 3.3.1 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/querystring-builder': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.4': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/types': 4.13.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.15 + '@aws-sdk/types': 3.973.4 + '@smithy/node-config-provider': 4.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.8': + dependencies: + '@smithy/types': 4.13.0 + fast-xml-parser: 5.3.6 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.3': {} + '@babel/runtime@7.28.6': optional: true @@ -3901,6 +4988,11 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -3911,7 +5003,7 @@ snapshots: '@esbuild-kit/core-utils': 3.3.2 get-tsconfig: 4.13.6 - '@esbuild/aix-ppc64@0.19.12': + '@esbuild/aix-ppc64@0.25.12': optional: true '@esbuild/aix-ppc64@0.27.3': @@ -3920,7 +5012,7 @@ snapshots: '@esbuild/android-arm64@0.18.20': optional: true - '@esbuild/android-arm64@0.19.12': + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm64@0.27.3': @@ -3929,7 +5021,7 @@ snapshots: '@esbuild/android-arm@0.18.20': optional: true - '@esbuild/android-arm@0.19.12': + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-arm@0.27.3': @@ -3938,7 +5030,7 @@ snapshots: '@esbuild/android-x64@0.18.20': optional: true - '@esbuild/android-x64@0.19.12': + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/android-x64@0.27.3': @@ -3947,7 +5039,7 @@ snapshots: '@esbuild/darwin-arm64@0.18.20': optional: true - '@esbuild/darwin-arm64@0.19.12': + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-arm64@0.27.3': @@ -3956,7 +5048,7 @@ snapshots: '@esbuild/darwin-x64@0.18.20': optional: true - '@esbuild/darwin-x64@0.19.12': + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/darwin-x64@0.27.3': @@ -3965,7 +5057,7 @@ snapshots: '@esbuild/freebsd-arm64@0.18.20': optional: true - '@esbuild/freebsd-arm64@0.19.12': + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.27.3': @@ -3974,7 +5066,7 @@ snapshots: '@esbuild/freebsd-x64@0.18.20': optional: true - '@esbuild/freebsd-x64@0.19.12': + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/freebsd-x64@0.27.3': @@ -3983,7 +5075,7 @@ snapshots: '@esbuild/linux-arm64@0.18.20': optional: true - '@esbuild/linux-arm64@0.19.12': + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm64@0.27.3': @@ -3992,7 +5084,7 @@ snapshots: '@esbuild/linux-arm@0.18.20': optional: true - '@esbuild/linux-arm@0.19.12': + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-arm@0.27.3': @@ -4001,7 +5093,7 @@ snapshots: '@esbuild/linux-ia32@0.18.20': optional: true - '@esbuild/linux-ia32@0.19.12': + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-ia32@0.27.3': @@ -4010,7 +5102,7 @@ snapshots: '@esbuild/linux-loong64@0.18.20': optional: true - '@esbuild/linux-loong64@0.19.12': + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-loong64@0.27.3': @@ -4019,7 +5111,7 @@ snapshots: '@esbuild/linux-mips64el@0.18.20': optional: true - '@esbuild/linux-mips64el@0.19.12': + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-mips64el@0.27.3': @@ -4028,7 +5120,7 @@ snapshots: '@esbuild/linux-ppc64@0.18.20': optional: true - '@esbuild/linux-ppc64@0.19.12': + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-ppc64@0.27.3': @@ -4037,7 +5129,7 @@ snapshots: '@esbuild/linux-riscv64@0.18.20': optional: true - '@esbuild/linux-riscv64@0.19.12': + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-riscv64@0.27.3': @@ -4046,7 +5138,7 @@ snapshots: '@esbuild/linux-s390x@0.18.20': optional: true - '@esbuild/linux-s390x@0.19.12': + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-s390x@0.27.3': @@ -4055,43 +5147,52 @@ snapshots: '@esbuild/linux-x64@0.18.20': optional: true - '@esbuild/linux-x64@0.19.12': + '@esbuild/linux-x64@0.25.12': optional: true '@esbuild/linux-x64@0.27.3': optional: true + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.27.3': optional: true '@esbuild/netbsd-x64@0.18.20': optional: true - '@esbuild/netbsd-x64@0.19.12': + '@esbuild/netbsd-x64@0.25.12': optional: true '@esbuild/netbsd-x64@0.27.3': optional: true + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.27.3': optional: true '@esbuild/openbsd-x64@0.18.20': optional: true - '@esbuild/openbsd-x64@0.19.12': + '@esbuild/openbsd-x64@0.25.12': optional: true '@esbuild/openbsd-x64@0.27.3': optional: true + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/openharmony-arm64@0.27.3': optional: true '@esbuild/sunos-x64@0.18.20': optional: true - '@esbuild/sunos-x64@0.19.12': + '@esbuild/sunos-x64@0.25.12': optional: true '@esbuild/sunos-x64@0.27.3': @@ -4100,7 +5201,7 @@ snapshots: '@esbuild/win32-arm64@0.18.20': optional: true - '@esbuild/win32-arm64@0.19.12': + '@esbuild/win32-arm64@0.25.12': optional: true '@esbuild/win32-arm64@0.27.3': @@ -4109,7 +5210,7 @@ snapshots: '@esbuild/win32-ia32@0.18.20': optional: true - '@esbuild/win32-ia32@0.19.12': + '@esbuild/win32-ia32@0.25.12': optional: true '@esbuild/win32-ia32@0.27.3': @@ -4118,7 +5219,7 @@ snapshots: '@esbuild/win32-x64@0.18.20': optional: true - '@esbuild/win32-x64@0.19.12': + '@esbuild/win32-x64@0.25.12': optional: true '@esbuild/win32-x64@0.27.3': @@ -4175,6 +5276,102 @@ snapshots: '@iconify/types': 2.0.0 mlly: 1.8.0 + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@internationalized/date@3.11.0': dependencies: '@swc/helpers': 0.5.19 @@ -5123,6 +6320,344 @@ snapshots: '@sideway/pinpoint@2.0.0': optional: true + '@smithy/abort-controller@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@4.2.2': + dependencies: + '@smithy/util-base64': 4.3.1 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.9': + dependencies: + '@smithy/node-config-provider': 4.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-config-provider': 4.2.1 + '@smithy/util-endpoints': 3.3.1 + '@smithy/util-middleware': 4.2.10 + tslib: 2.8.1 + + '@smithy/core@3.23.6': + dependencies: + '@smithy/middleware-serde': 4.2.11 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + '@smithy/util-body-length-browser': 4.2.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-stream': 4.5.15 + '@smithy/util-utf8': 4.2.1 + '@smithy/uuid': 1.1.1 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.10': + dependencies: + '@smithy/node-config-provider': 4.3.10 + '@smithy/property-provider': 4.2.10 + '@smithy/types': 4.13.0 + '@smithy/url-parser': 4.2.10 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.10': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.13.0 + '@smithy/util-hex-encoding': 4.2.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.10': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.10': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.10': + dependencies: + '@smithy/eventstream-codec': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.11': + dependencies: + '@smithy/protocol-http': 5.3.10 + '@smithy/querystring-builder': 4.2.10 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.2.11': + dependencies: + '@smithy/chunked-blob-reader': 5.2.1 + '@smithy/chunked-blob-reader-native': 4.2.2 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + '@smithy/util-buffer-from': 4.2.1 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.10': + dependencies: + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.4.20': + dependencies: + '@smithy/core': 3.23.6 + '@smithy/middleware-serde': 4.2.11 + '@smithy/node-config-provider': 4.3.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + '@smithy/url-parser': 4.2.10 + '@smithy/util-middleware': 4.2.10 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.37': + dependencies: + '@smithy/node-config-provider': 4.3.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/service-error-classification': 4.2.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-retry': 4.2.10 + '@smithy/uuid': 1.1.1 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.11': + dependencies: + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.10': + dependencies: + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.4.12': + dependencies: + '@smithy/abort-controller': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/querystring-builder': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + '@smithy/util-uri-escape': 4.2.1 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + + '@smithy/shared-ini-file-loader@4.4.5': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.10': + dependencies: + '@smithy/is-array-buffer': 4.2.1 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-hex-encoding': 4.2.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-uri-escape': 4.2.1 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@smithy/smithy-client@4.12.0': + dependencies: + '@smithy/core': 3.23.6 + '@smithy/middleware-endpoint': 4.4.20 + '@smithy/middleware-stack': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-stream': 4.5.15 + tslib: 2.8.1 + + '@smithy/types@4.13.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.10': + dependencies: + '@smithy/querystring-parser': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.1': + dependencies: + '@smithy/util-buffer-from': 4.2.1 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.1': + dependencies: + '@smithy/is-array-buffer': 4.2.1 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.36': + dependencies: + '@smithy/property-provider': 4.2.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.39': + dependencies: + '@smithy/config-resolver': 4.4.9 + '@smithy/credential-provider-imds': 4.2.10 + '@smithy/node-config-provider': 4.3.10 + '@smithy/property-provider': 4.2.10 + '@smithy/smithy-client': 4.12.0 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.3.1': + dependencies: + '@smithy/node-config-provider': 4.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.10': + dependencies: + '@smithy/service-error-classification': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.15': + dependencies: + '@smithy/fetch-http-handler': 5.3.11 + '@smithy/node-http-handler': 4.4.12 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + '@smithy/util-buffer-from': 4.2.1 + '@smithy/util-hex-encoding': 4.2.1 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.1': + dependencies: + '@smithy/util-buffer-from': 4.2.1 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.10': + dependencies: + '@smithy/abort-controller': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.1': + dependencies: + tslib: 2.8.1 + '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -5134,19 +6669,19 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': + '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: '@rollup/plugin-commonjs': 29.0.0(rollup@4.59.0) '@rollup/plugin-json': 6.1.0(rollup@4.59.0) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.59.0) - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) rollup: 4.59.0 - '@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -5158,27 +6693,27 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 5.53.6 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) optionalDependencies: '@opentelemetry/api': 1.9.0 typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) obug: 2.1.1 svelte: 5.53.6 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 svelte: 5.53.6 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - vitefu: 1.1.2(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@swc/helpers@0.5.19': dependencies: @@ -5255,12 +6790,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.1 - '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 tailwindcss: 4.2.1 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) '@tanstack/table-core@8.21.3': {} @@ -5272,7 +6807,7 @@ snapshots: '@types/bunyan@1.8.11': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 '@types/chai@5.2.3': dependencies: @@ -5281,7 +6816,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 '@types/cookie@0.6.0': {} @@ -5296,19 +6831,25 @@ snapshots: '@types/memcached@2.2.10': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 + + '@types/mime-types@3.0.1': {} '@types/mysql@2.15.27': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 '@types/node@25.3.2': dependencies: undici-types: 7.18.2 + '@types/node@25.3.3': + dependencies: + undici-types: 7.18.2 + '@types/oracledb@6.5.2': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 '@types/pdfkit@0.14.0': dependencies: @@ -5320,7 +6861,7 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 pg-protocol: 1.12.0 pg-types: 2.2.0 @@ -5332,13 +6873,13 @@ snapshots: '@types/qrcode@1.5.6': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 '@types/resolve@1.20.2': {} '@types/tedious@4.0.14': dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 '@types/tmp@0.2.6': {} @@ -5392,13 +6933,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@4.0.18': dependencies: @@ -5476,7 +7017,7 @@ snapshots: balanced-match@1.0.2: {} - better-auth@1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): + better-auth@1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@better-auth/core': 1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/telemetry': 1.4.20(@better-auth/core@1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)) @@ -5491,9 +7032,11 @@ snapshots: nanostores: 1.1.1 zod: 4.3.6 optionalDependencies: - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + drizzle-kit: 0.31.9 + drizzle-orm: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8) svelte: 5.53.6 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) better-call@1.1.8(zod@4.3.6): dependencies: @@ -5506,19 +7049,21 @@ snapshots: bignumber.js@9.3.1: {} - bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): + bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): dependencies: '@floating-ui/core': 1.7.4 '@floating-ui/dom': 1.7.5 '@internationalized/date': 3.11.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) svelte: 5.53.6 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' + bowser@2.14.1: {} + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -5527,7 +7072,7 @@ snapshots: bun-types@1.3.9: dependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 camelcase@5.3.1: {} @@ -5764,16 +7309,16 @@ snapshots: dotenv@17.3.1: {} - drizzle-kit@0.28.1: + drizzle-kit@0.31.9: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.19.12 - esbuild-register: 3.6.0(esbuild@0.19.12) + esbuild: 0.25.12 + esbuild-register: 3.6.0(esbuild@0.25.12) transitivePeerDependencies: - supports-color - drizzle-orm@0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8): + drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8): optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/pg': 8.16.0 @@ -5818,10 +7363,10 @@ snapshots: es-module-lexer@1.7.0: {} - esbuild-register@3.6.0(esbuild@0.19.12): + esbuild-register@3.6.0(esbuild@0.25.12): dependencies: debug: 4.4.3 - esbuild: 0.19.12 + esbuild: 0.25.12 transitivePeerDependencies: - supports-color @@ -5850,31 +7395,34 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 - esbuild@0.19.12: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 esbuild@0.27.3: optionalDependencies: @@ -5930,6 +7478,10 @@ snapshots: pure-rand: 6.1.0 optional: true + fast-xml-parser@5.3.6: + dependencies: + strnum: 2.2.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -5957,11 +7509,11 @@ snapshots: dependencies: fetch-blob: 3.2.0 - formsnap@2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)): + formsnap@2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)): dependencies: svelte: 5.53.6 svelte-toolbelt: 0.5.0(svelte@5.53.6) - sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3) + sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3) forwarded-parse@2.1.2: {} @@ -6307,6 +7859,12 @@ snapshots: dependencies: mimic-function: 5.0.1 + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mimic-function@5.0.1: {} mini-svg-data-uri@1.4.4: {} @@ -6533,7 +8091,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.3.2 + '@types/node': 25.3.3 long: 5.3.2 protobufjs@8.0.0: @@ -6548,7 +8106,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.3.2 + '@types/node': 25.3.3 long: 5.3.2 punycode.js@2.3.1: {} @@ -6665,14 +8223,14 @@ snapshots: esm-env: 1.2.2 svelte: 5.53.6 - runed@0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): + runed@0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.53.6 optionalDependencies: - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) rw@1.3.3: {} @@ -6690,12 +8248,45 @@ snapshots: dependencies: parseley: 0.12.1 + semver@7.7.4: {} + set-blocking@2.0.0: {} set-cookie-parser@2.7.2: {} set-cookie-parser@3.0.1: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6766,6 +8357,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strnum@2.2.0: {} + style-to-object@1.0.14: dependencies: inline-style-parser: 0.2.7 @@ -6792,10 +8385,10 @@ snapshots: runed: 0.28.0(svelte@5.53.6) svelte: 5.53.6 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) style-to-object: 1.0.14 svelte: 5.53.6 transitivePeerDependencies: @@ -6840,9 +8433,9 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3): + sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) devalue: 5.6.3 memoize-weak: 1.0.2 svelte: 5.53.6 @@ -7002,7 +8595,7 @@ snapshots: svelte: 5.53.6 svelte-toolbelt: 0.7.1(svelte@5.53.6) - vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -7011,21 +8604,21 @@ snapshots: rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.3.2 + '@types/node': 25.3.3 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.31.1 tsx: 4.21.0 yaml: 2.8.2 - vitefu@1.1.2(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): + vitefu@1.1.2(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -7042,11 +8635,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 25.3.2 + '@types/node': 25.3.3 transitivePeerDependencies: - jiti - less diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ef298de..e6dd244 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,7 @@ packages: - - apps/* - - packages/* + - apps/* + - packages/* + +onlyBuiltDependencies: + - esbuild + - sharp