From 798c23251022dece67cb7055f33f6724c55ed538 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 02:05:33 +0000 Subject: [PATCH 01/27] feat(backend): migrate to Deno with auth security hardening and Docker optimization Replace Node.js toolchain with native Deno (remove tsconfig, vitest, SWC, pnpm). Install Better Auth with Argon2id password hashing, email verification, password reset flow, and TOTP 2FA with backup codes/trusted devices. Add Redis secondary storage for sessions, cookie cache, and rate limiting. The Dockerfile is rewritten with multi-stage deno compile producing a standalone binary (~50MB vs ~170MB). --- .dockerignore | 19 +- .github/workflows/ci.yml | 24 +- .github/workflows/deploy.yml | 2 +- .gitignore | 3 + .vscode/extensions.json | 30 +- .vscode/settings.json | 117 +- AGENTS.md | 155 +- CLAUDE.md | 114 - CONTRIBUTING.md | 115 +- README.md | 118 +- apps/backend/.swcrc | 23 - apps/backend/Dockerfile | 62 +- apps/backend/deno.json | 97 + apps/backend/docs/ARCHITECTURE.md | 200 +- apps/backend/docs/DECISIONS.md | 344 +- apps/backend/docs/PATTERNS.md | 276 +- apps/backend/docs/REFERENCES.md | 158 +- apps/backend/drizzle.config.ts | 19 +- apps/backend/package.json | 66 +- apps/backend/src/app.ts | 32 +- apps/backend/src/db/migrate.ts | 31 +- apps/backend/src/env.ts | 90 +- apps/backend/src/index.ts | 28 +- apps/backend/src/jobs/index.ts | 62 +- apps/backend/src/jobs/worker.ts | 83 +- apps/backend/src/lib/__tests__/result.test.ts | 271 +- apps/backend/src/lib/auth.ts | 202 +- apps/backend/src/lib/cache.ts | 40 +- apps/backend/src/lib/configure-open-api.ts | 96 +- apps/backend/src/lib/create-app.ts | 102 +- apps/backend/src/lib/email.ts | 30 + apps/backend/src/lib/error.ts | 82 +- apps/backend/src/lib/http-status-phrases.ts | 6 +- apps/backend/src/lib/infra.ts | 16 +- apps/backend/src/lib/rate-limit.ts | 90 +- apps/backend/src/lib/redis.ts | 14 +- apps/backend/src/lib/storage.ts | 82 +- apps/backend/src/lib/types.ts | 228 +- apps/backend/src/lib/ws.ts | 118 +- apps/backend/src/middlewares/auth.ts | 106 +- apps/backend/src/middlewares/wide-event.ts | 99 +- apps/backend/src/modules/health/handlers.ts | 36 +- apps/backend/src/modules/health/index.ts | 8 +- apps/backend/src/modules/health/routes.ts | 64 +- .../health/usecases/check-health.usecase.ts | 82 +- .../modules/users/__tests__/handlers.test.ts | 376 +- .../users/__tests__/users.repository.test.ts | 189 +- .../users/__tests__/users.usecases.test.ts | 88 +- apps/backend/src/modules/users/handlers.ts | 280 +- apps/backend/src/modules/users/index.ts | 10 +- apps/backend/src/modules/users/routes.ts | 106 +- .../src/modules/users/users.repository.ts | 94 +- .../src/modules/users/users.usecases.ts | 13 +- apps/backend/src/routes/index.ts | 4 +- apps/backend/tsconfig.json | 32 - apps/backend/vitest.config.ts | 26 - biome.json | 180 +- deno.json | 29 + deno.lock | 2730 +++++++ docker-compose.prod.yml | 4 +- docs/BATTERIES.md | 252 +- docs/DENO_WORKSPACE_SCOPE.md | 375 + docs/DEPLOYMENT.md | 32 +- docs/PHILOSOPHY.md | 137 +- docs/WRITING.md | 89 +- package.json | 56 +- packages/db/deno.json | 23 + packages/db/drizzle.config.ts | 17 +- .../0001_curious_fantastic_four.sql | 9 + .../db/migrations/meta/0000_snapshot.json | 2 +- .../db/migrations/meta/0001_snapshot.json | 359 + packages/db/migrations/meta/_journal.json | 7 + packages/db/package.json | 51 +- packages/db/src/schema/sessions.ts | 92 +- packages/db/src/schema/users.ts | 7 +- packages/db/src/types.ts | 6 +- packages/db/tsconfig.json | 29 - packages/email-templates/deno.json | 6 + packages/email-templates/package.json | 31 +- .../src/__tests__/index.test.ts | 215 +- packages/email-templates/src/index.ts | 57 +- packages/email-templates/tsconfig.json | 9 - packages/email-templates/vitest.config.ts | 12 - packages/shared/deno.json | 14 + packages/shared/package.json | 39 +- packages/shared/src/__tests__/result.test.ts | 334 +- packages/shared/src/__tests__/schemas.test.ts | 230 +- packages/shared/src/index.ts | 6 +- packages/shared/src/schemas.ts | 78 +- packages/shared/src/types.ts | 16 +- packages/shared/tsconfig.json | 9 - packages/shared/vitest.config.ts | 12 - pnpm-lock.yaml | 7279 ----------------- pnpm-workspace.yaml | 8 +- scripts/new-module.sh | 10 +- scripts/setup.sh | 33 +- tsconfig.base.json | 21 - tsconfig.json | 13 - 98 files changed, 7422 insertions(+), 10754 deletions(-) delete mode 100644 CLAUDE.md delete mode 100644 apps/backend/.swcrc create mode 100644 apps/backend/deno.json create mode 100644 apps/backend/src/lib/email.ts delete mode 100644 apps/backend/tsconfig.json delete mode 100644 apps/backend/vitest.config.ts create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 docs/DENO_WORKSPACE_SCOPE.md create mode 100644 packages/db/deno.json create mode 100644 packages/db/migrations/0001_curious_fantastic_four.sql create mode 100644 packages/db/migrations/meta/0001_snapshot.json delete mode 100644 packages/db/tsconfig.json create mode 100644 packages/email-templates/deno.json delete mode 100644 packages/email-templates/tsconfig.json delete mode 100644 packages/email-templates/vitest.config.ts create mode 100644 packages/shared/deno.json delete mode 100644 packages/shared/tsconfig.json delete mode 100644 packages/shared/vitest.config.ts delete mode 100644 pnpm-lock.yaml delete mode 100644 tsconfig.base.json delete mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore index c4b9c33..b6bca62 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,11 +2,18 @@ node_modules/ **/node_modules/ -# Build +# Test files (not needed at runtime) +**/__tests__/ +**/*.test.ts + +# Frontend (not needed for backend builds) +apps/frontend/ + +# Build artifacts dist/ **/dist/ -# Git +# Version control .git/ .gitignore @@ -14,8 +21,12 @@ dist/ .vscode/ .idea/ -# Misc -*.md +# Environment files .env .env.* !.env.example + +# Documentation & scripts +*.md +docs/ +scripts/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 807a42f..382666e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,34 +38,26 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: denoland/setup-deno@v2 with: - version: 9.15.0 + deno-version: v2.x - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build packages - run: pnpm build:packages + - name: Cache dependencies + run: deno cache apps/backend/src/index.ts apps/backend/src/jobs/worker.ts - name: Lint - run: pnpm lint + run: deno lint - name: Type check - run: pnpm typecheck + run: deno check apps/backend/src/index.ts - name: Run migrations - run: pnpm --filter @repo/db db:push:ci + run: deno run --allow-env --allow-net --allow-read --allow-sys apps/backend/src/db/migrate.ts env: DATABASE_URL: postgres://test:test@localhost:5432/test - name: Test - run: pnpm test + run: deno test -A env: NODE_ENV: test DATABASE_URL: postgres://test:test@localhost:5432/test diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 13f0257..fd60a38 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -118,7 +118,7 @@ jobs: # Run database migrations using the NEW backend image # --rm prevents orphaned containers - IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml run --rm backend node src/db/migrate.js + IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml run --rm backend deno run --allow-env --allow-net --allow-read --allow-sys apps/backend/src/db/migrate.ts # Update backend container # Only backend is recreated → DB/Redis remain untouched diff --git a/.gitignore b/.gitignore index 3491995..9199243 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ dist/ .tsbuildinfo tsconfig.tsbuildinfo +# pnpm (migrated to Deno) +pnpm-lock.yaml + # Environment files .env .env.local diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3862542..0974e40 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,16 +1,18 @@ { - // Extensions recommended for everyone working in this repo. - // VS Code will prompt teammates to install these when they open the workspace. - "recommendations": [ - // Biome — formatter, linter, import sorter. Replaces Prettier + ESLint. - "biomejs.biome", - // TanStack Router — file-based route generation awareness - "tanstack.router-vscode-plugin", - // Tailwind CSS IntelliSense — autocomplete for utility classes - "bradlc.vscode-tailwindcss", - // Prisma / Drizzle don't have great tooling yet, but these help with SQL - "inferrinizzard.prettier-sql-vscode", - // Dot-env syntax highlighting - "mikestead.dotenv" - ] + // Extensions recommended for everyone working in this repo. + // VS Code will prompt teammates to install these when they open the workspace. + "recommendations": [ + // Deno — runtime, language server, and type support for backend files. + "denoland.vscode-deno", + // Biome — formatter, linter, import sorter. Replaces Prettier + ESLint. + "biomejs.biome", + // TanStack Router — file-based route generation awareness + "tanstack.router-vscode-plugin", + // Tailwind CSS IntelliSense — autocomplete for utility classes + "bradlc.vscode-tailwindcss", + // Prisma / Drizzle don't have great tooling yet, but these help with SQL + "inferrinizzard.prettier-sql-vscode", + // Dot-env syntax highlighting + "mikestead.dotenv" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 76afa82..098746b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,80 +1,51 @@ { - // ─── Biome ─────────────────────────────────────────────────────────────── - // - // Only activate Biome LSP for workspace folders that contain a biome.json. - // This prevents Biome from trying to lint unrelated folders you may open. - "biome.requireConfiguration": true, + // ─── Biome ─────────────────────────────────────────────────────────────── + // + // Only activate Biome LSP for workspace folders that contain a biome.json. + // This prevents Biome from trying to lint unrelated folders you may open. + "biome.requireConfiguration": true, - // ─── Editor: format & fix on save ──────────────────────────────────────── - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - // Apply all safe Biome fixes (no-unused-vars removal, etc.) on save. - "source.fixAll.biome": "explicit", - // Sort imports via Biome's organizeImports on save. - // This replaces the need for a separate import-sorter extension. - "source.organizeImports.biome": "explicit" - }, + // ─── Editor: format & fix on save ──────────────────────────────────────── + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + // Apply all safe Biome fixes (no-unused-vars removal, etc.) on save. + "source.fixAll.biome": "explicit", + // Sort imports via Biome's organizeImports on save. + // This replaces the need for a separate import-sorter extension. + "source.organizeImports.biome": "explicit" + }, - // ─── Default formatter per language ────────────────────────────────────── - // - // Biome handles: JS · TS · JSX · TSX · JSON · JSONC · CSS · GraphQL. - // Setting it explicitly per language avoids the "multiple formatters" - // prompt and ensures Biome wins even if Prettier is installed globally. - "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[json]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[jsonc]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[css]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[graphql]": { - "editor.defaultFormatter": "biomejs.biome" - }, + // ─── Deno ────────────────────────────────────────────────────────────────── + // Enable the Deno language server for the backend. + "deno.enable": true, + "deno.lint": true, + // Deno config — points to the root deno.json with import maps. + "deno.config": "./deno.json", - // ─── TypeScript ─────────────────────────────────────────────────────────── - // Use the workspace TypeScript version (from node_modules) rather than - // VS Code's bundled one. Keeps type-checking consistent with tsc in CI. - "typescript.tsdk": "node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, + // ─── Tailwind CSS IntelliSense ──────────────────────────────────────────── + // Tailwind v4 uses CSS @theme blocks instead of tailwind.config.js. + // Point the extension at index.css so it finds the @theme source. + "tailwindCSS.experimental.configFile": "apps/frontend/src/index.css", + // Treat clsx() and cn() arguments as Tailwind class lists for completion. + "tailwindCSS.classFunctions": ["clsx", "cn", "cva", "cx"], - // ─── Tailwind CSS IntelliSense ──────────────────────────────────────────── - // Tailwind v4 uses CSS @theme blocks instead of tailwind.config.js. - // Point the extension at index.css so it finds the @theme source. - "tailwindCSS.experimental.configFile": "apps/frontend/src/index.css", - // Treat clsx() and cn() arguments as Tailwind class lists for completion. - "tailwindCSS.classFunctions": ["clsx", "cn", "cva", "cx"], + // ─── Files ──────────────────────────────────────────────────────────────── + // Don't show generated/build artefacts in the explorer; Biome also skips + // linting these (see overrides in biome.json). + "files.exclude": { + "**/node_modules": true, + "**/.git": true + }, + "search.exclude": { + "**/node_modules": true, + "**/dist": true, + "**/.turbo": true, + "apps/frontend/src/routeTree.gen.ts": true, + "packages/db/migrations": true, + "pnpm-lock.yaml": true + }, - // ─── Files ──────────────────────────────────────────────────────────────── - // Don't show generated/build artefacts in the explorer; Biome also skips - // linting these (see overrides in biome.json). - "files.exclude": { - "**/node_modules": true, - "**/.git": true - }, - "search.exclude": { - "**/node_modules": true, - "**/dist": true, - "**/.turbo": true, - "apps/frontend/src/routeTree.gen.ts": true, - "packages/db/migrations": true, - "pnpm-lock.yaml": true - }, - - // ─── Explorer ───────────────────────────────────────────────────────────── - // Compact single-child folders (e.g. apps/frontend/src) in the tree. - "explorer.compactFolders": false + // ─── Explorer ───────────────────────────────────────────────────────────── + // Compact single-child folders (e.g. apps/frontend/src) in the tree. + "explorer.compactFolders": false } diff --git a/AGENTS.md b/AGENTS.md index 6239fad..58ada14 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,24 +1,37 @@ # AGENTS.md -Instructions for AI agents working in this codebase. Read this before touching anything. +Instructions for AI agents working in this codebase. Read this before touching +anything. -Also read: [`CLAUDE.md`](CLAUDE.md) for commands and architecture, [`docs/WRITING.md`](docs/WRITING.md) for documentation voice, [`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md) for the beliefs behind every decision. +Also read: [`CLAUDE.md`](CLAUDE.md) for commands and architecture, +[`docs/WRITING.md`](docs/WRITING.md) for documentation voice, +[`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md) for the beliefs behind every +decision. --- ## Philosophy -This codebase follows a simple principle: **simple is not easy, but it's the only thing that scales**. +This codebase follows a simple principle: **simple is not easy, but it's the +only thing that scales**. -We take inspiration from 37signals, DHH, Jason Fried, and the Primeagen. Bias toward the boring solution. Complexity is a cost you pay with every future change. Don't add indirection that doesn't earn its keep. Don't build for hypothetical requirements. Don't make ten changes when one would do. +We take inspiration from 37signals, DHH, Jason Fried, and the Primeagen. Bias +toward the boring solution. Complexity is a cost you pay with every future +change. Don't add indirection that doesn't earn its keep. Don't build for +hypothetical requirements. Don't make ten changes when one would do. -The four principles — **principles over tools**, **progressive abstraction**, **craftsmanship**, **human experience first** — are documented in full in [`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md). Read it once. It's the question to ask before you add anything. +The four principles — **principles over tools**, **progressive abstraction**, +**craftsmanship**, **human experience first** — are documented in full in +[`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md). Read it once. It's the question to +ask before you add anything. --- ## The Core Rule: Work in Coherent Units -A coherent unit is one logical change with a clear boundary. It has a name. It could be described in a single sentence. It has tests. It gets a commit. If it's non-trivial, it gets a branch and a PR. +A coherent unit is one logical change with a clear boundary. It has a name. It +could be described in a single sentence. It has tests. It gets a commit. If it's +non-trivial, it gets a branch and a PR. **A coherent unit is:** @@ -34,13 +47,15 @@ A coherent unit is one logical change with a clear boundary. It has a name. It c - "All the things I noticed while looking at the file" - Every change across the entire repo triggered by one small ask -When you find something adjacent that needs fixing, note it. Finish the current unit. Then address it separately. +When you find something adjacent that needs fixing, note it. Finish the current +unit. Then address it separately. --- ## Branching -Never commit directly to `main` or `dev` for anything beyond a typo fix. New work = new branch. +Never commit directly to `main` or `dev` for anything beyond a typo fix. New +work = new branch. ```bash git checkout dev @@ -50,15 +65,16 @@ git checkout -b / Branch naming: -| Type | Pattern | Example | -|------|---------|---------| -| New feature | `feature/` | `feature/post-pagination` | -| Bug fix | `fix/` | `fix/session-cookie-expiry` | -| Refactor | `refactor/` | `refactor/error-handler-cleanup` | -| Chore | `chore/` | `chore/update-drizzle` | -| Documentation | `docs/` | `docs/better-auth-schema` | +| Type | Pattern | Example | +| ------------- | ----------------- | -------------------------------- | +| New feature | `feature/` | `feature/post-pagination` | +| Bug fix | `fix/` | `fix/session-cookie-expiry` | +| Refactor | `refactor/` | `refactor/error-handler-cleanup` | +| Chore | `chore/` | `chore/update-drizzle` | +| Documentation | `docs/` | `docs/better-auth-schema` | -One branch = one coherent unit. Don't accumulate unrelated changes on a branch because they happen to be open at the same time. +One branch = one coherent unit. Don't accumulate unrelated changes on a branch +because they happen to be open at the same time. --- @@ -93,9 +109,12 @@ fixed some things and also added the new feature and updated docs wip ``` -Commit size: one concern per commit. If the message needs "and", it's probably two commits. If the diff spans five unrelated files, stop and ask what the actual unit is. +Commit size: one concern per commit. If the message needs "and", it's probably +two commits. If the diff spans five unrelated files, stop and ask what the +actual unit is. -Keep commits logical even on a feature branch — they tell the story of how you got there. +Keep commits logical even on a feature branch — they tell the story of how you +got there. --- @@ -103,30 +122,34 @@ Keep commits logical even on a feature branch — they tell the story of how you Tests are part of the work unit, not an afterthought. -- Write or update tests **before committing** — don't leave a commit that breaks the suite -- For new backend modules: at minimum, a scaffolded `handlers.test.ts` with the first test passing +- Write or update tests **before committing** — don't leave a commit that breaks + the suite +- For new backend modules: at minimum, a scaffolded `handlers.test.ts` with the + first test passing - For logic that branches: test every branch - For bug fixes: write the test that would have caught it first, then fix it ```bash -pnpm test # All tests -pnpm typecheck # TypeScript -pnpm lint # Biome +deno test -A # All tests +deno check # TypeScript +deno lint # Lint backend ``` -All three must pass before you commit. CI will catch it anyway — save the round trip. +All three must pass before you commit. CI will catch it anyway — save the round +trip. --- ## Pull Requests -PR per coherent unit. Not per day. Not "everything I did this session". One unit, one PR. +PR per coherent unit. Not per day. Not "everything I did this session". One +unit, one PR. **Before opening a PR:** -- [ ] All tests pass (`pnpm test`) -- [ ] No type errors (`pnpm typecheck`) -- [ ] No lint warnings (`pnpm lint`) +- [ ] All tests pass (`deno test -A`) +- [ ] No type errors (`deno check`) +- [ ] No lint warnings (`deno lint`) - [ ] Branch is rebased on latest `dev` - [ ] Commit history tells a legible story @@ -134,22 +157,30 @@ PR per coherent unit. Not per day. Not "everything I did this session". One unit ```md ## What + Brief description of the change. One to three sentences. ## Why + The user problem or technical need this solves. Link to issue if one exists. ## How -Key architectural or implementation decisions. What alternatives were considered. + +Key architectural or implementation decisions. What alternatives were +considered. ## Testing + How to verify this works. Specific steps, not "it works". ## Notes -Anything a reviewer should know. Technical debt introduced. Follow-up work needed. + +Anything a reviewer should know. Technical debt introduced. Follow-up work +needed. ``` -PR size: reviewable in under 30 minutes. If the diff is 1000+ lines, it's probably two PRs. Split by layer or by phase of the work. +PR size: reviewable in under 30 minutes. If the diff is 1000+ lines, it's +probably two PRs. Split by layer or by phase of the work. --- @@ -157,29 +188,52 @@ PR size: reviewable in under 30 minutes. If the diff is 1000+ lines, it's probab These are the failure modes to actively avoid: -**Don't batch unrelated changes.** If you're asked to add pagination to the posts endpoint, don't also fix the users handler, rename a variable you noticed, and update three docs files. Do the thing asked. Commit it. Note the rest. +**Don't batch unrelated changes.** If you're asked to add pagination to the +posts endpoint, don't also fix the users handler, rename a variable you noticed, +and update three docs files. Do the thing asked. Commit it. Note the rest. -**Don't refactor while adding a feature.** If the existing code is messy, open a refactor PR first, then build the feature on top of clean ground. Mixing the two makes both harder to review and harder to revert. +**Don't refactor while adding a feature.** If the existing code is messy, open a +refactor PR first, then build the feature on top of clean ground. Mixing the two +makes both harder to review and harder to revert. -**Don't create summary documents.** Don't create a `CHANGES.md`, `SUMMARY.md`, or `TODO.md` unless explicitly asked. If you need to track state across a long task, use the task list in your head or ask the user. +**Don't create summary documents.** Don't create a `CHANGES.md`, `SUMMARY.md`, +or `TODO.md` unless explicitly asked. If you need to track state across a long +task, use the task list in your head or ask the user. -**Don't touch files you weren't asked to touch** unless they're directly load-bearing for the change. Noticing something is not the same as being asked to fix it. +**Don't touch files you weren't asked to touch** unless they're directly +load-bearing for the change. Noticing something is not the same as being asked +to fix it. -**Don't leave the codebase in a half-done state.** A partially implemented feature is worse than no feature — it creates confusion and merge conflicts. Either complete the unit or don't start it. If something is larger than expected, surface that before diving in. +**Don't leave the codebase in a half-done state.** A partially implemented +feature is worse than no feature — it creates confusion and merge conflicts. +Either complete the unit or don't start it. If something is larger than +expected, surface that before diving in. --- ## Code Patterns -The patterns are documented; use them. Don't invent new ones without documenting them. +The patterns are documented; use them. Don't invent new ones without documenting +them. -**Error handling:** every function that can fail returns `Result`. No `throw` in repository or use-case code. Infrastructure catches go through `tryInfra`. Domain errors are typed discriminated unions in `*.errors.ts`. See [`apps/backend/docs/DECISIONS.md`](apps/backend/docs/DECISIONS.md). +**Error handling:** every function that can fail returns `Result`. No +`throw` in repository or use-case code. Infrastructure catches go through +`tryInfra`. Domain errors are typed discriminated unions in `*.errors.ts`. See +[`apps/backend/docs/DECISIONS.md`](apps/backend/docs/DECISIONS.md). -**Module structure:** use the scaffolder. `pnpm new:module ` generates the correct file layout. The pattern is: route → handler → repository → error types → use-cases. See [`apps/backend/docs/ARCHITECTURE.md`](apps/backend/docs/ARCHITECTURE.md). +**Module structure:** use the scaffolder. `./scripts/new-module.sh ` +generates the correct file layout. The pattern is: route → handler → repository +→ error types → use-cases. See +[`apps/backend/docs/ARCHITECTURE.md`](apps/backend/docs/ARCHITECTURE.md). -**Frontend components:** Base UI primitives wrapped with CVA variants. Design tokens from CSS custom properties (`--color-primary`, etc.). No hardcoded `gray-*` Tailwind classes. See [`apps/frontend/docs/DECISIONS.md`](apps/frontend/docs/DECISIONS.md). +**Frontend components:** Base UI primitives wrapped with CVA variants. Design +tokens from CSS custom properties (`--color-primary`, etc.). No hardcoded +`gray-*` Tailwind classes. See +[`apps/frontend/docs/DECISIONS.md`](apps/frontend/docs/DECISIONS.md). -**Adding a Better Auth plugin:** run schema generation before migrating. See the Better Auth section in [`apps/backend/docs/DECISIONS.md`](apps/backend/docs/DECISIONS.md). +**Adding a Better Auth plugin:** run schema generation before migrating. See the +Better Auth section in +[`apps/backend/docs/DECISIONS.md`](apps/backend/docs/DECISIONS.md). --- @@ -194,14 +248,21 @@ You're done with a unit when: 5. The branch is clean and rebased 6. The PR description would let a stranger understand the change in five minutes -Quality is not completeness. Shipping ten half-finished things is worse than shipping three finished ones. Finish what you start. Leave the codebase better than you found it. +Quality is not completeness. Shipping ten half-finished things is worse than +shipping three finished ones. Finish what you start. Leave the codebase better +than you found it. --- ## Reference -- [Engineering Philosophy](https://adjanour.github.io/docs-site/engineering/engineering-philosophy/) — principles that anchor all decisions -- [Engineering Playbook](https://adjanour.github.io/docs-site/engineering/engineering-playbook/) — practices and rituals -- [Git Workflow](https://adjanour.github.io/docs-site/engineering/git-workflow/) — branching and commit conventions -- [PR Guidelines](https://adjanour.github.io/docs-site/engineering/pr-guidelines/) — what a good PR looks like -- [Orcta Workflow](https://docs.orctatech.com/orcta-workflow.html) — end-to-end development workflow +- [Engineering Philosophy](https://adjanour.github.io/docs-site/engineering/engineering-philosophy/) + — principles that anchor all decisions +- [Engineering Playbook](https://adjanour.github.io/docs-site/engineering/engineering-playbook/) + — practices and rituals +- [Git Workflow](https://adjanour.github.io/docs-site/engineering/git-workflow/) + — branching and commit conventions +- [PR Guidelines](https://adjanour.github.io/docs-site/engineering/pr-guidelines/) + — what a good PR looks like +- [Orcta Workflow](https://docs.orctatech.com/orcta-workflow.html) — end-to-end + development workflow diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index a05d952..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,114 +0,0 @@ -# CLAUDE.md - -Quick reference for working with this codebase. Read [`AGENTS.md`](AGENTS.md) for work discipline, branching, and commit rules before starting any task. - -## Docs - -| Doc | What it covers | -|-----|----------------| -| `AGENTS.md` | **Work discipline, branching, commits, PRs — read first** | -| `docs/PHILOSOPHY.md` | The beliefs behind every decision — why the codebase is shaped this way | -| `CONTRIBUTING.md` | Full workflow for humans and agents | -| `docs/BATTERIES.md` | Built-in utilities (auth, jobs, caching, etc.) | -| `docs/DEPLOYMENT.md` | Production deployment guide | -| `docs/WRITING.md` | Writing voice, style guide, and influences | -| `apps/backend/docs/` | Backend architecture, patterns, decisions | -| `apps/frontend/docs/` | Frontend patterns and decisions | - -## Commands - -```bash -pnpm dev # Run backend (:9999) + frontend (:5173) -pnpm test # Run tests -pnpm lint # Lint with Biome -pnpm typecheck # Type check everything -pnpm db:migrate # Apply database migrations -pnpm db:generate # Generate migration from schema changes -pnpm new:module NAME # Scaffold a new backend module -``` - -## Architecture - -Backend uses clean architecture. The key rule: - -**Use-cases return discriminated unions, not exceptions.** - -```typescript -type Result = - | { type: "SUCCESS"; data: T } - | { type: "NOT_FOUND" } - | { type: "ALREADY_EXISTS" }; -``` - -Handlers switch on `result.type` and map to HTTP responses. - -## File Locations - -| What | Where | -|------|-------| -| Backend modules | `apps/backend/src/modules/{name}/` | -| Database schemas | `packages/db/src/schema/` | -| Frontend pages | `apps/frontend/src/routes/` | -| Shared types | `packages/shared/src/` | -| Backend utilities | `apps/backend/src/lib/` | - -## Module Structure - -```bash -modules/{name}/ - routes.ts # OpenAPI route definitions + exported route types - handlers.ts # HTTP handlers (imperative shell) - index.ts # Creates router, wires routes → handlers - {name}.errors.ts # Domain error type variants - {name}.repository.ts # Data access — tryInfra, Result, never throws - usecases/ - {name}.usecases.ts # Pure business logic — no DB, no async - __tests__/ - handlers.test.ts # Integration tests via Hono testClient -``` - -Generate with `pnpm new:module `. The `usecases/` file is optional — add it when business rules are worth testing in isolation. - -## Common Patterns - -### Adding a route - -1. Define in `routes.ts` with Zod schemas -2. Create handler in `handlers.ts` -3. Wire in `index.ts` -4. Register module in `apps/backend/src/routes/index.ts` - -### Adding a database table - -1. Create schema in `packages/db/src/schema/{table}.ts` -2. Export from `packages/db/src/schema/index.ts` -3. Run `pnpm db:generate && pnpm db:migrate` - -### Using batteries - -```typescript -// File uploads -import { getUploadUrl } from "@/lib/storage"; - -// WebSockets -import { wsManager } from "@/lib/ws"; - -// Rate limiting -import { rateLimit } from "@/lib/rate-limit"; - -// Background jobs -import { addJob } from "@/jobs"; -``` - -## Environment - -Required in `.env`: - -- `DATABASE_URL` -- `BETTER_AUTH_SECRET` (32+ chars) - -Optional: - -- `REDIS_URL` — for jobs/caching -- `S3_*` — for file uploads -- `RESEND_API_KEY` — for email diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97ea016..45b0724 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,12 @@ Thanks for helping out. Here's how to do it well. -Before diving in, read [`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md) — the beliefs behind every decision in this codebase. The workflow below will make more sense once you understand the *why*. +Before diving in, read [`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md) — the beliefs +behind every decision in this codebase. The workflow below will make more sense +once you understand the _why_. -If you're working with an AI agent on this project, also read [`AGENTS.md`](AGENTS.md). +If you're working with an AI agent on this project, also read +[`AGENTS.md`](AGENTS.md). --- @@ -13,13 +16,22 @@ If you're working with an AI agent on this project, also read [`AGENTS.md`](AGEN This is a GitHub template repo. You already have your own copy. ```bash -pnpm setup # Install dependencies + generate .env +deno --version # Ensure Deno 2+ is installed +pnpm install # Install frontend dependencies +./scripts/setup.sh # Generate .env with auth secret docker compose up -d -pnpm db:migrate -pnpm dev +deno task db:migrate +deno task dev # Backend on :9999 ``` -Backend on [localhost:9999/docs](http://localhost:9999/docs). Frontend on [localhost:5173](http://localhost:5173). +In a separate terminal: + +```bash +deno task dev:frontend # Frontend on :5173 via Vite +``` + +Backend on [localhost:9999/docs](http://localhost:9999/docs). Frontend on +[localhost:5173](http://localhost:5173). Run both together or independently. --- @@ -35,28 +47,33 @@ git checkout -b feature/your-feature-name Never work directly on `main` or `dev`. Every change gets its own branch. -| Type | Branch pattern | -|------|----------------| -| New feature | `feature/` | -| Bug fix | `fix/` | -| Refactor | `refactor/` | -| Chore / deps | `chore/` | -| Docs | `docs/` | +| Type | Branch pattern | +| ------------ | ----------------- | +| New feature | `feature/` | +| Bug fix | `fix/` | +| Refactor | `refactor/` | +| Chore / deps | `chore/` | +| Docs | `docs/` | ### 2. Build one thing -A feature, a fix, a refactor — not all three at once. If you notice something adjacent that needs fixing, note it and address it separately. Mixing concerns makes every change harder to review, harder to revert, and harder to understand later. +A feature, a fix, a refactor — not all three at once. If you notice something +adjacent that needs fixing, note it and address it separately. Mixing concerns +makes every change harder to review, harder to revert, and harder to understand +later. -For anything non-trivial: think through the approach before writing code. A five-minute outline is faster than a misdirected hour of implementation. +For anything non-trivial: think through the approach before writing code. A +five-minute outline is faster than a misdirected hour of implementation. ### 3. Write tests with the code -Tests are part of the feature, not something you add at the end. Before you commit: +Tests are part of the feature, not something you add at the end. Before you +commit: ```bash -pnpm test # All tests -pnpm typecheck # TypeScript -pnpm lint # Biome +deno test -A # All tests +deno check # TypeScript +deno lint # Lint backend ``` CI runs these on every PR. It will catch it — fix it locally first. @@ -71,7 +88,7 @@ Conventional commits: ``` -Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `perf` +Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `perf`\ Scopes: `backend`, `frontend`, `shared`, `db`, `auth`, `jobs`, `scripts` ```bash @@ -97,30 +114,39 @@ Use this template: ```md ## What + Brief description. One to three sentences. ## Why + The user problem or technical need. Link to issue if one exists. ## How + Key decisions made. What alternatives were considered. ## Testing + How to verify this works. Specific steps. ## Notes -Technical debt introduced. Follow-up work needed. Anything a reviewer should know. + +Technical debt introduced. Follow-up work needed. Anything a reviewer should +know. ``` ### 6. Address review feedback -Respond to all comments within 24 hours — even a simple "done" or "disagree because X". If you disagree, explain why. Reviews are collaborative, not adversarial. +Respond to all comments within 24 hours — even a simple "done" or "disagree +because X". If you disagree, explain why. Reviews are collaborative, not +adversarial. Mark conversations as resolved when addressed. Request re-review when ready. ### 7. After merging -Monitor your changes in production for at least an hour. Delete the feature branch. Close related issues. +Monitor your changes in production for at least an hour. Delete the feature +branch. Close related issues. --- @@ -128,9 +154,13 @@ Monitor your changes in production for at least an hour. Delete the feature bran ### Backend -- **Errors are values.** Use-cases and repositories return `Result` — never throw. See [`apps/backend/docs/DECISIONS.md`](apps/backend/docs/DECISIONS.md). -- **Handlers are thin.** They read input, call the repository or use-case, map the Result to HTTP. No business logic in handlers. -- **Infrastructure has one catch boundary.** All DB/Redis calls go through `tryInfra`. That's the only `try/catch` in repository code. +- **Errors are values.** Use-cases and repositories return `Result` — + never throw. See + [`apps/backend/docs/DECISIONS.md`](apps/backend/docs/DECISIONS.md). +- **Handlers are thin.** They read input, call the repository or use-case, map + the Result to HTTP. No business logic in handlers. +- **Infrastructure has one catch boundary.** All DB/Redis calls go through + `tryInfra`. That's the only `try/catch` in repository code. ```typescript // Good @@ -151,13 +181,17 @@ try { ### Frontend -- **Server state lives in React Query.** UI state lives in Zustand. Don't create a context for server data. -- **Auth guards go in `beforeLoad`**, not inside the component. No flash of unauthenticated content. -- **Components wrap Base UI primitives.** Style with design tokens (`--color-primary`, etc.) not hardcoded Tailwind colors. +- **Server state lives in React Query.** UI state lives in Zustand. Don't create + a context for server data. +- **Auth guards go in `beforeLoad`**, not inside the component. No flash of + unauthenticated content. +- **Components wrap Base UI primitives.** Style with design tokens + (`--color-primary`, etc.) not hardcoded Tailwind colors. ### General -- No `any`. Configure a `biome-ignore` comment with a reason if you absolutely need one. +- No `any`. Configure a `// deno-lint-ignore` comment (backend) or + `// biome-ignore` comment (frontend) with a reason if you absolutely need one. - Name things clearly. `getUserById` not `get` or `fetchData`. - Delete dead code. Don't comment it out. - Leave the codebase better than you found it — but save it for its own commit. @@ -169,30 +203,32 @@ try { ### A backend module ```bash -pnpm new:module posts +./scripts/new-module.sh posts ``` -Then register in `apps/backend/src/routes/index.ts`. See the [README](README.md) for the full walkthrough. +Then register in `apps/backend/src/routes/index.ts`. See the [README](README.md) +for the full walkthrough. ### A database table ```bash # Edit packages/db/src/schema/your-table.ts -pnpm db:generate # Generates migration -pnpm db:migrate # Applies it +deno task db:generate # Generates migration +deno task db:migrate # Applies it ``` ### A Better Auth plugin ```bash -npx @better-auth/cli generate # Updates schema from auth config -pnpm db:generate -pnpm db:migrate +deno run -A npm:@better-auth/cli generate # Updates schema from auth config +deno task db:generate +deno task db:migrate ``` ### A frontend page -Create `apps/frontend/src/routes/your-page.tsx`. TanStack Router picks it up automatically. +Create `apps/frontend/src/routes/your-page.tsx`. TanStack Router picks it up +automatically. --- @@ -206,7 +242,8 @@ You're done with a unit when: - The commit message describes what and why - A stranger could understand the PR in five minutes -Finish what you start. Shipping three complete things beats ten half-done ones every time. +Finish what you start. Shipping three complete things beats ten half-done ones +every time. --- diff --git a/README.md b/README.md index b43d7c0..63540cf 100644 --- a/README.md +++ b/README.md @@ -3,52 +3,62 @@ A production-ready TypeScript monorepo. Ship fast, sleep well. ```bash -pnpm setup && docker compose up -d && pnpm db:migrate && pnpm dev +./scripts/setup.sh && docker compose up -d && deno task db:migrate && deno task dev ``` -Backend runs on [localhost:9999](http://localhost:9999/docs). Frontend on [localhost:5173](http://localhost:5173). +Backend runs on [localhost:9999](http://localhost:9999/docs). Frontend on +[localhost:5173](http://localhost:5173). ## What's Inside -**Backend** — Hono, Drizzle, PostgreSQL, better-auth -**Frontend** — React 19, TanStack Router, Tailwind v4 -**Extras** — File uploads, WebSockets, background jobs, rate limiting +**Backend** — Hono, Drizzle, PostgreSQL, better-auth **Frontend** — React 19, +TanStack Router, Tailwind v4 **Extras** — File uploads, WebSockets, background +jobs, rate limiting ## Get Started -This is a GitHub template. Click **Use this template** → **Create a new repository** on GitHub, then clone your new repo. +This is a GitHub template. Click **Use this template** → **Create a new +repository** on GitHub, then clone your new repo. -You need Node 20+ and pnpm. +You need Deno 2+ (for the backend) and pnpm (for frontend dependencies). ```bash git clone https://github.com// my-app cd my-app -pnpm setup +deno --version # Verify Deno 2+ +pnpm install # Install frontend deps +./scripts/setup.sh # Generate .env with auth secret ``` -`pnpm setup` installs dependencies and writes a `.env` file with a generated auth secret. +`./scripts/setup.sh` writes a `.env` file with a generated auth secret. Start the database: ```bash -docker compose up -d # Starts PostgreSQL + Redis -pnpm db:migrate # Creates the initial tables +docker compose up -d # Starts PostgreSQL + Redis +deno task db:migrate # Creates the initial tables ``` -> **Note on auth tables:** Better Auth manages its own tables (users, sessions, accounts). The initial migration already includes them. If you add Better Auth plugins later (2FA, API keys, organisations, etc.), regenerate the schema first: +> **Note on auth tables:** Better Auth manages its own tables (users, sessions, +> accounts). The initial migration already includes them. If you add Better Auth +> plugins later (2FA, API keys, organisations, etc.), regenerate the schema +> first: > > ```bash -> npx @better-auth/cli generate # Updates packages/db/src/schema/ from your auth config -> pnpm db:generate # Creates the migration -> pnpm db:migrate # Applies it +> deno run -A npm:@better-auth/cli generate # Updates packages/db/src/schema/ from your auth config +> deno task db:generate # Creates the migration +> deno task db:migrate # Applies it > ``` > -> See the [Better Auth database docs](https://www.better-auth.com/docs/concepts/database) for the full reference. +> See the +> [Better Auth database docs](https://www.better-auth.com/docs/concepts/database) +> for the full reference. Run everything: ```bash -pnpm dev +deno task dev # Backend on :9999 +deno task dev:frontend # Frontend on :5173 ``` Open [localhost:5173](http://localhost:5173). You're live. @@ -56,10 +66,11 @@ Open [localhost:5173](http://localhost:5173). You're live. ## Daily Commands ```bash -pnpm dev # Run everything -pnpm test # Run tests -pnpm lint # Check code -pnpm typecheck # Check types +deno task dev # Run backend +deno task dev:frontend # Run frontend +deno test -A # Run tests +deno lint # Check code +deno check # Check types ``` ## Build Something @@ -67,20 +78,20 @@ pnpm typecheck # Check types ### Add a Backend Module ```bash -pnpm new:module posts +./scripts/new-module.sh posts ``` This scaffolds a complete module at `apps/backend/src/modules/posts/`: -| File | Purpose | -|------|---------| -| `routes.ts` | OpenAPI route definitions with Zod schemas | -| `handlers.ts` | HTTP handlers — reads input, calls repo, maps Result to response | -| `posts.repository.ts` | Data access — uses `tryInfra`, returns `Result`, never throws | -| `posts.errors.ts` | Typed domain error variants (`PostNotFound`, etc.) | -| `usecases/` | Pure business logic — no DB, no async, fully unit-testable | -| `__tests__/` | Integration test stubs | -| `index.ts` | Wires routes to handlers, exports the router | +| File | Purpose | +| --------------------- | ---------------------------------------------------------------- | +| `routes.ts` | OpenAPI route definitions with Zod schemas | +| `handlers.ts` | HTTP handlers — reads input, calls repo, maps Result to response | +| `posts.repository.ts` | Data access — uses `tryInfra`, returns `Result`, never throws | +| `posts.errors.ts` | Typed domain error variants (`PostNotFound`, etc.) | +| `usecases/` | Pure business logic — no DB, no async, fully unit-testable | +| `__tests__/` | Integration test stubs | +| `index.ts` | Wires routes to handlers, exports the router | Register it in `apps/backend/src/routes/index.ts`: @@ -94,7 +105,8 @@ export const routes = [users, posts]; export const publicRoutes = [health, posts]; ``` -Then flesh out the repository with real Drizzle queries and add your DB schema to `packages/db/src/schema/`. +Then flesh out the repository with real Drizzle queries and add your DB schema +to `packages/db/src/schema/`. ### Add a Frontend Page @@ -115,8 +127,8 @@ Done. TanStack Router handles the rest. Edit `packages/db/src/schema/` and run: ```bash -pnpm db:generate # Creates migration -pnpm db:migrate # Applies it +deno task db:generate # Creates migration +deno task db:migrate # Applies it ``` ## Use the Batteries @@ -124,7 +136,7 @@ pnpm db:migrate # Applies it ### Upload Files ```typescript -import { getUploadUrl, generateKey } from "@/lib/storage"; +import { generateKey, getUploadUrl } from "@/lib/storage"; // Generate presigned upload URL const key = generateKey("photo.jpg", "avatars"); @@ -158,12 +170,13 @@ await addJob("email", { }); ``` -Run workers: `pnpm --filter backend jobs` +Run workers: +`deno run --env-file=apps/backend/.env -A apps/backend/src/jobs/worker.ts` ### Rate Limit Routes ```typescript -import { rateLimit, authRateLimit } from "@/lib/rate-limit"; +import { authRateLimit, rateLimit } from "@/lib/rate-limit"; // 100 requests per minute app.use("/api/*", rateLimit()); @@ -174,9 +187,8 @@ app.post("/api/auth/login", authRateLimit, loginHandler); ## Deploy -**Backend** → Docker on any VPS, or Railway/Render -**Frontend** → Vercel (zero config) -**Database** → Supabase, Neon, or Railway +**Backend** → Docker on any VPS, or Railway/Render **Frontend** → Vercel (zero +config) **Database** → Supabase, Neon, or Railway See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for the full guide. @@ -202,21 +214,30 @@ packages/ ## Learn More -- [Architecture Guide](apps/backend/docs/ARCHITECTURE.md) — How the backend is structured -- [Backend Decisions](apps/backend/docs/DECISIONS.md) — Why each backend choice was made -- [Frontend Decisions](apps/frontend/docs/DECISIONS.md) — Why each frontend choice was made -- [Batteries Included](docs/BATTERIES.md) — All built-in utilities with usage examples +- [Architecture Guide](apps/backend/docs/ARCHITECTURE.md) — How the backend is + structured +- [Backend Decisions](apps/backend/docs/DECISIONS.md) — Why each backend choice + was made +- [Frontend Decisions](apps/frontend/docs/DECISIONS.md) — Why each frontend + choice was made +- [Batteries Included](docs/BATTERIES.md) — All built-in utilities with usage + examples - [Deployment Guide](docs/DEPLOYMENT.md) — Ship to production - [Writing Style Guide](docs/WRITING.md) — How we write docs and articles - [API Docs](http://localhost:9999/docs) — Auto-generated from your code ## This is a Template, Not a Framework -When you create a repo from this template, you own it. There is no upstream to pull from. Delete what you don't need, rename what makes sense to rename, and diverge freely. +When you create a repo from this template, you own it. There is no upstream to +pull from. Delete what you don't need, rename what makes sense to rename, and +diverge freely. -What to keep: the `packages/shared` Result type, the `tryInfra` pattern, the module scaffolder, the Biome config. +What to keep: the `packages/shared` Result type, the `tryInfra` pattern, the +module scaffolder, the Deno config (`deno.json`), and the Biome config (frontend +linting). -What to replace: the example `users` module with your own domain, the license, this README. +What to replace: the example `users` module with your own domain, the license, +this README. --- @@ -224,4 +245,5 @@ What to replace: the example `users` module with your own domain, the license, t Proprietary. Copyright © 2026 Orcta. All rights reserved. -This codebase is not open source. Do not distribute, sublicense, or use outside the organisation without written permission. +This codebase is not open source. Do not distribute, sublicense, or use outside +the organisation without written permission. diff --git a/apps/backend/.swcrc b/apps/backend/.swcrc deleted file mode 100644 index d97f262..0000000 --- a/apps/backend/.swcrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "jsc": { - "parser": { - "syntax": "typescript", - "tsx": true, - "decorators": true, - "dynamicImport": true - }, - "paths": { - "@/*": ["./src/*"] - }, - "baseUrl": "./", - "target": "es2022", - "transform": { - "legacyDecorator": true, - "decoratorMetadata": true - } - }, - "module": { - "type": "es6" - }, - "sourceMaps": true -} diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile index 5f3bba3..1ead970 100644 --- a/apps/backend/Dockerfile +++ b/apps/backend/Dockerfile @@ -1,42 +1,48 @@ -# Build stage -FROM node:20-alpine AS builder -RUN corepack enable pnpm +# ─── Build ───────────────────────────────────────────────────────────── +FROM denoland/deno:alpine-2.1.4 AS builder WORKDIR /app -# Copy workspace files -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY tsconfig.base.json ./ -COPY packages ./packages -COPY apps/backend ./apps/backend +# Layer 1: Configuration (changes infrequently — preserves dep cache) +COPY deno.json deno.lock ./ +COPY apps/backend/deno.json apps/backend/deno.lock* ./apps/backend/ +COPY packages/shared/deno.json ./packages/shared/ +COPY packages/db/deno.json ./packages/db/ +COPY packages/email-templates/deno.json ./packages/email-templates/ +RUN deno cache apps/backend/src/index.ts -# Install dependencies -RUN pnpm install --frozen-lockfile +# Layer 2: Shared packages (change less often than app code) +COPY packages/shared/ ./packages/shared/ +COPY packages/db/ ./packages/db/ +COPY packages/email-templates/ ./packages/email-templates/ +RUN deno cache apps/backend/src/index.ts -# Build packages and backend -RUN pnpm build:packages -RUN pnpm --filter backend build +# Layer 3: Application source +COPY apps/backend/ ./apps/backend/ +RUN deno cache apps/backend/src/index.ts -# Produce a flat, symlink-free node_modules for the backend only. -# This is the canonical pnpm approach for Docker — avoids broken symlinks -# when the workspace virtual store is copied without its sibling package dirs. -RUN pnpm --filter backend deploy --prod /app/deploy +# Compile to standalone binary (includes Deno runtime + all deps) +RUN deno compile \ + --allow-env \ + --allow-net \ + --allow-read \ + --allow-sys \ + --allow-ffi \ + --output /usr/local/bin/orcta-backend \ + apps/backend/src/index.ts -# Production stage -FROM node:20-alpine AS runner +# ─── Run ────────────────────────────────────────────────────────────── +FROM alpine:3.20 -WORKDIR /app +RUN apk add --no-cache ca-certificates && \ + addgroup -S app && adduser -S app -G app + +COPY --from=builder /usr/local/bin/orcta-backend /usr/local/bin/orcta-backend -# Compiled application sources -COPY --from=builder /app/apps/backend/dist ./ -# Flat node_modules from pnpm deploy (includes all workspace packages resolved) -COPY --from=builder /app/deploy/node_modules ./node_modules -# SQL migration files (needed by src/db/migrate.js at runtime) -COPY --from=builder /app/packages/db/migrations ./migrations +USER app -ENV NODE_ENV=production ENV PORT=9999 EXPOSE 9999 -CMD ["node", "src/index.js"] +CMD ["orcta-backend"] diff --git a/apps/backend/deno.json b/apps/backend/deno.json new file mode 100644 index 0000000..625610f --- /dev/null +++ b/apps/backend/deno.json @@ -0,0 +1,97 @@ +{ + "name": "backend", + "version": "0.1.0", + "exports": "./src/index.ts", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + }, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "skipLibCheck": true + }, + "tasks": { + "dev": "deno run --watch --env-file=.env -A src/index.ts", + "start": "deno run --env-file=.env -A src/index.ts", + "worker": "deno run --env-file=.env -A src/jobs/worker.ts", + "test": "deno test --env-file=.env -A", + "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", + "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", + "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" + }, + "imports": { + "@/": "./src/", + "@/app": "./src/app.ts", + "@/db": "./src/db/index.ts", + "@/env": "./src/env.ts", + "@/lib/auth": "./src/lib/auth.ts", + "@/lib/create-app": "./src/lib/create-app.ts", + "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", + "@/lib/types": "./src/lib/types.ts", + "@/lib/redis": "./src/lib/redis.ts", + "@/lib/error": "./src/lib/error.ts", + "@/lib/infra": "./src/lib/infra.ts", + "@/lib/cache": "./src/lib/cache.ts", + "@/lib/storage": "./src/lib/storage.ts", + "@/lib/rate-limit": "./src/lib/rate-limit.ts", + "@/lib/ws": "./src/lib/ws.ts", + "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", + "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", + "@/middlewares/auth": "./src/middlewares/auth.ts", + "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", + "@/modules/health": "./src/modules/health/index.ts", + "@/modules/health/handlers": "./src/modules/health/handlers.ts", + "@/modules/health/routes": "./src/modules/health/routes.ts", + "@/modules/users": "./src/modules/users/index.ts", + "@/modules/users/handlers": "./src/modules/users/handlers.ts", + "@/modules/users/routes": "./src/modules/users/routes.ts", + "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", + "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", + "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", + "@/jobs/index": "./src/jobs/index.ts", + "@/jobs/worker": "./src/jobs/worker.ts", + "hono": "npm:hono", + "hono/cors": "npm:hono/cors", + "hono/dev": "npm:hono/dev", + "hono/ws": "npm:hono/ws", + "@hono/zod-openapi": "npm:@hono/zod-openapi", + "@hono/swagger-ui": "npm:@hono/swagger-ui", + "@hono/zod-validator": "npm:@hono/zod-validator", + "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", + "better-auth": "npm:better-auth", + "better-auth/adapters": "npm:better-auth/adapters", + "better-auth/plugins": "npm:better-auth/plugins", + "better-auth/plugins/two-factor": "npm:better-auth/plugins/two-factor", + "ioredis": "npm:ioredis", + "bullmq": "npm:bullmq", + "pino": "npm:pino", + "pino-pretty": "npm:pino-pretty", + "hono-pino": "npm:hono-pino", + "stoker": "npm:stoker", + "stoker/middlewares": "npm:stoker/middlewares", + "stoker/openapi": "npm:stoker/openapi", + "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", + "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", + "zod": "npm:zod", + "drizzle-kit": "npm:drizzle-kit", + "drizzle-orm": "npm:drizzle-orm", + "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", + "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", + "drizzle-orm/postgres-js/migrator": "npm:drizzle-orm/postgres-js/migrator", + "postgres": "npm:postgres", + "drizzle-zod": "npm:drizzle-zod", + "@node-rs/argon2": "npm:@node-rs/argon2", + "resend": "npm:resend", + "dotenv": "npm:dotenv", + "dotenv-expand": "npm:dotenv-expand", + "@axiomhq/pino": "npm:@axiomhq/pino" + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any", "no-non-null-assertion"] + } + }, + "test": { + "include": ["src/**/*.test.ts"] + } +} diff --git a/apps/backend/docs/ARCHITECTURE.md b/apps/backend/docs/ARCHITECTURE.md index 777ae11..91e3517 100644 --- a/apps/backend/docs/ARCHITECTURE.md +++ b/apps/backend/docs/ARCHITECTURE.md @@ -10,12 +10,16 @@ Simple rules, predictable code. Request → Route → Handler → [Use-Case →] Repository → Database ``` -**Route** — Validates input with Zod, defines OpenAPI spec -**Handler** — Imperative shell. Calls use-case or repository, maps `Result` to HTTP -**Use-Case** — Functional core. Pure business logic. No HTTP, no direct DB calls. -**Repository** — Imperative shell. Data access via `tryInfra`. Never throws. Returns `Result`. +**Route** — Validates input with Zod, defines OpenAPI spec\ +**Handler** — Imperative shell. Calls use-case or repository, maps `Result` to +HTTP\ +**Use-Case** — Functional core. Pure business logic. No HTTP, no direct DB +calls.\ +**Repository** — Imperative shell. Data access via `tryInfra`. Never throws. +Returns `Result`. -The use-case layer is **optional per route**, not optional per module. Use it when business logic exists. Skip it when a handler is just calling a repository. +The use-case layer is **optional per route**, not optional per module. Use it +when business logic exists. Skip it when a handler is just calling a repository. --- @@ -24,14 +28,17 @@ The use-case layer is **optional per route**, not optional per module. Use it wh The architecture enforces this boundary at the type level. **Imperative shell** (handlers, repositories) — talks to the outside world: + - Receives HTTP requests, persists data, calls external services - Produces `Result` values from messy reality **Functional core** (use-cases) — pure functions over domain values: + - Receives already-loaded data as arguments - Applies business rules - Returns `Result` — no side effects, no async DB calls -- Trivially testable: call with plain values, assert on the returned `Result` — no mocks, no DB +- Trivially testable: call with plain values, assert on the returned `Result` — + no mocks, no DB ``` ┌──────────────────────────────────────────────┐ @@ -53,10 +60,10 @@ The architecture enforces this boundary at the type level. **Never throw. Encode all failures in the return type.** -| Category | Type | Produced by | Handled by | -|---|---|---|---| -| Domain | Typed discriminated union | Repository functions | Handlers — switch/match exhaustively | -| Infrastructure | `InfrastructureError` | `tryInfra()` | Handlers — `isInfraError()` guard → 500 | +| Category | Type | Produced by | Handled by | +| -------------- | ------------------------- | -------------------- | --------------------------------------- | +| Domain | Typed discriminated union | Repository functions | Handlers — switch/match exhaustively | +| Infrastructure | `InfrastructureError` | `tryInfra()` | Handlers — `isInfraError()` guard → 500 | ```typescript // ❌ Don't — hidden control flow, nothing typed at the call site @@ -70,8 +77,9 @@ async function getPost(id: string): Promise { async function findPostById( id: string, ): Promise> { - const result = await tryInfra("find post by id", () => - db.query.posts.findFirst({ where: eq(posts.id, id) }), + const result = await tryInfra( + "find post by id", + () => db.query.posts.findFirst({ where: eq(posts.id, id) }), ); if (!result.ok) return result; if (!result.value) return err({ type: "POST_NOT_FOUND", lookup: id }); @@ -94,74 +102,111 @@ export const getPostHandler: AppRouteHandler = async (c) => { ok: (post) => c.json(success(post), OK), err: (e) => isInfraError(e) - ? c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR) - : c.json(failure({ code: "NOT_FOUND", message: "Post not found" }), NOT_FOUND), + ? c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ) + : c.json( + failure({ code: "NOT_FOUND", message: "Post not found" }), + NOT_FOUND, + ), }); }; ``` **Add it** when any of these are true: + - Logic spans multiple repository results - A rule can be expressed as a pure function over domain values - The logic is worth testing in isolation, without touching the DB ```typescript // posts.usecases.ts — pure functions, no imports from @/db -import type { User, Post } from "@repo/db"; +import type { Post, User } from "@repo/db"; import type { NotPostAuthor } from "./posts.errors"; -import { ok, err, type Result } from "@repo/shared"; +import { err, ok, type Result } from "@repo/shared"; export function authorizePostUpdate( user: User, post: Post, ): Result { - if (post.authorId !== user.id) + if (post.authorId !== user.id) { return err({ type: "NOT_POST_AUTHOR", userId: user.id, postId: post.id }); + } return ok(post); } ``` ```typescript // posts/handlers.ts — handler runs the imperative shell, calls use-case for the rule -export const updatePostHandler: AppRouteHandler = async (c) => { +export const updatePostHandler: AppRouteHandler = async ( + c, +) => { const user = c.get("user"); const { id } = c.req.valid("param"); const body = c.req.valid("json"); const found = await findPostById(id); - if (!found.ok) - return c.json(failure({ code: "NOT_FOUND", message: "Post not found" }), NOT_FOUND); + if (!found.ok) { + return c.json( + failure({ code: "NOT_FOUND", message: "Post not found" }), + NOT_FOUND, + ); + } const authorized = authorizePostUpdate(user, found.value); - if (!authorized.ok) - return c.json(failure({ code: "FORBIDDEN", message: "Not your post" }), FORBIDDEN); + if (!authorized.ok) { + return c.json( + failure({ code: "FORBIDDEN", message: "Not your post" }), + FORBIDDEN, + ); + } const result = await updatePost(id, body); return match(result, { ok: (post) => c.json(success(post), OK), - err: () => c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR), + err: () => + c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ), }); }; ``` -Use-cases receive already-loaded data as arguments — they never import from `@/db` or call repository functions. The handler is the orchestrator: it calls the repositories (imperative shell), then passes the results into the use-case (functional core). Because use-cases are pure functions, they require no mocking to test: call them with plain values and assert on the `Result`. +Use-cases receive already-loaded data as arguments — they never import from +`@/db` or call repository functions. The handler is the orchestrator: it calls +the repositories (imperative shell), then passes the results into the use-case +(functional core). Because use-cases are pure functions, they require no mocking +to test: call them with plain values and assert on the `Result`. --- ## Result Helpers ```typescript -import { ok, err, map, andThen, andThenAsync, match, isOk, isErr } from "@repo/shared"; +import { + andThen, + andThenAsync, + err, + isErr, + isOk, + map, + match, + ok, +} from "@repo/shared"; ``` -| Helper | Use when | -|---|---| -| `map(result, fn)` | Transform the value, pass error through unchanged | -| `andThen(result, fn)` | Chain a sync Result-returning function | -| `andThenAsync(result, fn)` | Chain an async Result-returning function | +| Helper | Use when | +| ---------------------------- | ----------------------------------------------------------- | +| `map(result, fn)` | Transform the value, pass error through unchanged | +| `andThen(result, fn)` | Chain a sync Result-returning function | +| `andThenAsync(result, fn)` | Chain an async Result-returning function | | `match(result, { ok, err })` | Handle both branches exhaustively — primary handler pattern | -The `if (!result.ok) return result` pattern is still fine inside repositories when you need to inspect intermediate values. Use helpers when they reduce noise, not to be clever. +The `if (!result.ok) return result` pattern is still fine inside repositories +when you need to inspect intermediate values. Use helpers when they reduce +noise, not to be clever. ```typescript // match in a handler — both branches handled, compiler enforces exhaustiveness @@ -169,8 +214,14 @@ return match(result, { ok: (user) => c.json(success(user), OK), err: (e) => isInfraError(e) - ? c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR) - : c.json(failure({ code: "NOT_FOUND", message: "User not found" }), NOT_FOUND), + ? c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ) + : c.json( + failure({ code: "NOT_FOUND", message: "User not found" }), + NOT_FOUND, + ), }); // andThenAsync — chain two async repo calls, short-circuits on first error @@ -215,38 +266,43 @@ modules/posts/ handlers.test.ts ← HTTP integration tests (full stack) ``` -Test files live in `__tests__/` alongside the module they test, not in a top-level `tests/` directory. The vitest config uses `src/**/*.test.ts` to pick them up automatically. +Test files live in `__tests__/` alongside the module they test, not in a +top-level `tests/` directory. The Deno test config (in `deno.json`) uses +`**/*.test.ts` to pick them up automatically. --- ## Key Primitives -| Import | From | Purpose | -|---|---|---| -| `Result`, `ok`, `err`, `map`, `andThen`, `andThenAsync`, `match`, `isOk`, `isErr` | `@repo/shared` | Result type and combinators | -| `apiSuccessSchema(dataSchema)` | `@repo/shared` | Canonical `{ success: true, data }` Zod schema for route responses | -| `apiErrorSchema` | `@repo/shared` | Canonical `{ success: false, error }` Zod schema for route responses | -| `tryInfra` | `@/lib/infra` | Single catch boundary for all repositories | -| `InfrastructureError` | `@/lib/error` | Wraps unknown infrastructure throws | -| `isInfraError` | `@/lib/types` | Type guard for handlers | -| `success`, `failure` | `@/lib/types` | HTTP response shape helpers | -| `jsonRes(schema, description)` | `@/lib/types` | Collapse `{ content: { "application/json": { schema } }, description }` in route definitions | -| `jsonBody(schema)` | `@/lib/types` | Collapse `{ content: { "application/json": { schema } } }` for request bodies | -| `OK`, `CREATED`, `NOT_FOUND`, `INTERNAL_SERVER_ERROR`, … | `@/lib/types` | Named HTTP status constants — re-exported from `src/lib/http-status-codes.ts` | -| `AppRouteHandler` | `@/lib/types` | Type-safe handler type | -| `WideEvent` | `@/lib/types` | Type for the per-request canonical log event | -| `addToEvent(c, fields)` | `@/lib/types` | Merge business context into the in-flight wide event | -| `authMiddleware` | `@/middlewares/auth` | Auth middleware (plain function, not factory) | -| `requireRole` | `@/middlewares/auth` | Role guard (factory — takes role strings) | -| `wideEventMiddleware` | `@/middlewares/wide-event` | Canonical log line accumulator — emits one wide event per request | +| Import | From | Purpose | +| --------------------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------- | +| `Result`, `ok`, `err`, `map`, `andThen`, `andThenAsync`, `match`, `isOk`, `isErr` | `@repo/shared` | Result type and combinators | +| `apiSuccessSchema(dataSchema)` | `@repo/shared` | Canonical `{ success: true, data }` Zod schema for route responses | +| `apiErrorSchema` | `@repo/shared` | Canonical `{ success: false, error }` Zod schema for route responses | +| `tryInfra` | `@/lib/infra` | Single catch boundary for all repositories | +| `InfrastructureError` | `@/lib/error` | Wraps unknown infrastructure throws | +| `isInfraError` | `@/lib/types` | Type guard for handlers | +| `success`, `failure` | `@/lib/types` | HTTP response shape helpers | +| `jsonRes(schema, description)` | `@/lib/types` | Collapse `{ content: { "application/json": { schema } }, description }` in route definitions | +| `jsonBody(schema)` | `@/lib/types` | Collapse `{ content: { "application/json": { schema } } }` for request bodies | +| `OK`, `CREATED`, `NOT_FOUND`, `INTERNAL_SERVER_ERROR`, … | `@/lib/types` | Named HTTP status constants — re-exported from `src/lib/http-status-codes.ts` | +| `AppRouteHandler` | `@/lib/types` | Type-safe handler type | +| `WideEvent` | `@/lib/types` | Type for the per-request canonical log event | +| `addToEvent(c, fields)` | `@/lib/types` | Merge business context into the in-flight wide event | +| `authMiddleware` | `@/middlewares/auth` | Auth middleware (plain function, not factory) | +| `requireRole` | `@/middlewares/auth` | Role guard (factory — takes role strings) | +| `wideEventMiddleware` | `@/middlewares/wide-event` | Canonical log line accumulator — emits one wide event per request | --- ## Observability -This codebase uses the **wide event / canonical log line** pattern from [loggingsucks.com](https://loggingsucks.com). +This codebase uses the **wide event / canonical log line** pattern from +[loggingsucks.com](https://loggingsucks.com). -Instead of emitting many small log statements throughout a request, a single rich event is built up over the request lifecycle and emitted once at the end with everything needed to answer any debugging question. +Instead of emitting many small log statements throughout a request, a single +rich event is built up over the request lifecycle and emitted once at the end +with everything needed to answer any debugging question. ``` Request arrives @@ -269,7 +325,9 @@ wideEventMiddleware ← appends: status_code, duration_ms, outcome → logg ```typescript import { addToEvent } from "@/lib/types"; -export const createOrderHandler: AppRouteHandler = async (c) => { +export const createOrderHandler: AppRouteHandler = async ( + c, +) => { const body = c.req.valid("json"); const result = await createOrder(body); @@ -281,25 +339,31 @@ export const createOrderHandler: AppRouteHandler = async (c) = return c.json(success(result.value), CREATED); } - return c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR); + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); }; ``` -Every authenticated request automatically carries `user.id` and `user.role` — `authMiddleware` adds them without the handler needing to. +Every authenticated request automatically carries `user.id` and `user.role` — +`authMiddleware` adds them without the handler needing to. ### Sampling -The wide event middleware uses **tail-based sampling** — the decision is made after the request completes: +The wide event middleware uses **tail-based sampling** — the decision is made +after the request completes: -| Condition | Kept? | -|---|---| -| `status_code >= 500` | Always | -| `outcome === "error"` | Always | -| `duration_ms > 2000` | Always | -| `user.role === "admin"` | Always | -| Everything else | 5% random sample | +| Condition | Kept? | +| ----------------------- | ---------------- | +| `status_code >= 500` | Always | +| `outcome === "error"` | Always | +| `duration_ms > 2000` | Always | +| `user.role === "admin"` | Always | +| Everything else | 5% random sample | -This keeps Axiom ingest costs low while guaranteeing 100% capture of the events that matter. +This keeps Axiom ingest costs low while guaranteeing 100% capture of the events +that matter. ### Axiom setup @@ -312,4 +376,6 @@ SERVICE_VERSION= REGION=eu-west-1 ``` -In development these vars are absent and logs go to stdout with `pino-pretty`. In production, pino writes to both stdout (captured by Docker) and Axiom simultaneously. +In development these vars are absent and logs go to stdout with `pino-pretty`. +In production, pino writes to both stdout (captured by Docker) and Axiom +simultaneously. diff --git a/apps/backend/docs/DECISIONS.md b/apps/backend/docs/DECISIONS.md index 3a670f9..b19c0a8 100644 --- a/apps/backend/docs/DECISIONS.md +++ b/apps/backend/docs/DECISIONS.md @@ -10,15 +10,20 @@ Why we chose what we chose. **Why**: -1. **Native TypeScript** — Written in TypeScript, not typed after the fact. Types are accurate and complete. +1. **Native TypeScript** — Written in TypeScript, not typed after the fact. + Types are accurate and complete. -2. **Web Standards** — Uses `Request`/`Response` from the Fetch API. Your code works in Node, Deno, Bun, and edge runtimes without changes. +2. **Web Standards** — Uses `Request`/`Response` from the Fetch API. Your code + works in Node, Deno, Bun, and edge runtimes without changes. -3. **First-class OpenAPI** — `@hono/zod-openapi` integrates Zod schemas directly into route definitions. One source of truth for validation and docs. +3. **First-class OpenAPI** — `@hono/zod-openapi` integrates Zod schemas directly + into route definitions. One source of truth for validation and docs. -4. **Performance** — Faster than Express. Comparable to Fastify. Uses a fast RegExp-based router. +4. **Performance** — Faster than Express. Comparable to Fastify. Uses a fast + RegExp-based router. -5. **Minimal** — No opinions about structure. No magic. You see exactly what's happening. +5. **Minimal** — No opinions about structure. No magic. You see exactly what's + happening. **Trade-offs**: @@ -30,20 +35,24 @@ Why we chose what we chose. ```typescript // Express — types are bolted on, validation is separate -app.post('/users', validateBody(schema), (req: Request, res: Response) => { +app.post("/users", validateBody(schema), (req: Request, res: Response) => { const body = req.body; // any, unless you cast }); // Hono — types flow from schema, validation is declarative const route = createRoute({ - method: 'post', - path: '/users', - request: { body: { content: { 'application/json': { schema: userSchema } } } }, - responses: { 201: { content: { 'application/json': { schema: userResponseSchema } } } }, + method: "post", + path: "/users", + request: { + body: { content: { "application/json": { schema: userSchema } } }, + }, + responses: { + 201: { content: { "application/json": { schema: userResponseSchema } } }, + }, }); app.openapi(route, (c) => { - const body = c.req.valid('json'); // Fully typed from schema + const body = c.req.valid("json"); // Fully typed from schema }); ``` @@ -55,30 +64,35 @@ app.openapi(route, (c) => { **Why**: -1. **SQL-first** — Drizzle queries look like SQL. If you know SQL, you know Drizzle. +1. **SQL-first** — Drizzle queries look like SQL. If you know SQL, you know + Drizzle. ```typescript // Drizzle — reads like SQL const users = await db .select() .from(usersTable) - .where(eq(usersTable.status, 'active')) + .where(eq(usersTable.status, "active")) .limit(10); // Prisma — proprietary API const users = await prisma.user.findMany({ - where: { status: 'active' }, + where: { status: "active" }, take: 10, }); ``` -1. **No code generation** — Prisma requires `prisma generate` after schema changes. Drizzle schemas are just TypeScript. Change and go. +1. **No code generation** — Prisma requires `prisma generate` after schema + changes. Drizzle schemas are just TypeScript. Change and go. -2. **Lightweight** — No engine binary. Drizzle is ~50KB. Prisma ships a Rust query engine. +2. **Lightweight** — No engine binary. Drizzle is ~50KB. Prisma ships a Rust + query engine. -3. **Better types** — `$inferSelect` and `$inferInsert` give you exact types from your schema. No drift. +3. **Better types** — `$inferSelect` and `$inferInsert` give you exact types + from your schema. No drift. -4. **Migrations as SQL** — Drizzle generates plain SQL migrations. You can read and edit them. Prisma migrations are harder to customize. +4. **Migrations as SQL** — Drizzle generates plain SQL migrations. You can read + and edit them. Prisma migrations are harder to customize. **Trade-offs**: @@ -94,7 +108,8 @@ const users = await prisma.user.findMany({ **Why**: -1. **Explicit outcomes** — Every possible result is in the type signature. No hidden `throw` statements. +1. **Explicit outcomes** — Every possible result is in the type signature. No + hidden `throw` statements. ```typescript // Exceptions — caller doesn't know what can go wrong @@ -105,9 +120,9 @@ async function createUser(data): Promise { // Discriminated unions — outcomes are explicit async function createUser(data): Promise< - | { type: 'CREATED'; user: User } - | { type: 'EMAIL_EXISTS' } - | { type: 'INVALID_DATA'; errors: string[] } + | { type: "CREATED"; user: User } + | { type: "EMAIL_EXISTS" } + | { type: "INVALID_DATA"; errors: string[] } > { // Caller knows exactly what to handle } @@ -117,17 +132,22 @@ async function createUser(data): Promise< ```typescript switch (result.type) { - case 'CREATED': return c.json(result.user, 201); - case 'EMAIL_EXISTS': return c.json({ error: 'Email taken' }, 409); - // TypeScript: "Property 'INVALID_DATA' is missing" + case "CREATED": + return c.json(result.user, 201); + case "EMAIL_EXISTS": + return c.json({ error: "Email taken" }, 409); + // TypeScript: "Property 'INVALID_DATA' is missing" } ``` -1. **No try-catch chains** — Exceptions bubble up. You need try-catch everywhere or risk unhandled errors. Unions are handled where they're used. +1. **No try-catch chains** — Exceptions bubble up. You need try-catch everywhere + or risk unhandled errors. Unions are handled where they're used. -2. **Better for async** — Exceptions in async code are easy to lose. Forgotten `await`, missing `.catch()`. Unions are just data. +2. **Better for async** — Exceptions in async code are easy to lose. Forgotten + `await`, missing `.catch()`. Unions are just data. -3. **Testing is simpler** — Return values are easier to assert than thrown errors. +3. **Testing is simpler** — Return values are easier to assert than thrown + errors. **Trade-offs**: @@ -143,13 +163,15 @@ switch (result.type) { **Why**: -The classic port/adapter split (a TypeScript interface + a separate Drizzle implementation) adds indirection without payoff at this scale: +The classic port/adapter split (a TypeScript interface + a separate Drizzle +implementation) adds indirection without payoff at this scale: - We never swap the underlying database at runtime - The DB can run locally in tests — mocking is not needed - One extra file per module accumulates quickly -Instead, repositories are plain async functions in a single `*.repository.ts` file. Each function: +Instead, repositories are plain async functions in a single `*.repository.ts` +file. Each function: - Returns `Result` — never throws - Wraps all DB calls in `tryInfra` — single catch boundary @@ -160,8 +182,9 @@ Instead, repositories are plain async functions in a single `*.repository.ts` fi export async function findUserById( id: string, ): Promise> { - const result = await tryInfra(`fetch user ${id}`, () => - db.query.users.findFirst({ where: eq(users.id, id) }), + const result = await tryInfra( + `fetch user ${id}`, + () => db.query.users.findFirst({ where: eq(users.id, id) }), ); if (!result.ok) return result; if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: id }); @@ -169,18 +192,23 @@ export async function findUserById( } ``` -**Testing without mocks**: repositories are integration-tested against a real local DB. -The `Result` return type makes assertions straightforward without any mock setup: +**Testing without mocks**: repositories are integration-tested against a real +local DB. The `Result` return type makes assertions straightforward without any +mock setup: ```typescript const result = await findUserById("nonexistent"); -expect(result).toEqual({ ok: false, error: { type: "USER_NOT_FOUND", lookup: "nonexistent" } }); +expect(result).toEqual({ + ok: false, + error: { type: "USER_NOT_FOUND", lookup: "nonexistent" }, +}); ``` **Trade-offs**: - No ability to inject a fake repo (not needed — real DB is fast and simple) -- Tight coupling to Drizzle (acceptable — swap cost is low when it's just functions) +- Tight coupling to Drizzle (acceptable — swap cost is low when it's just + functions) --- @@ -190,48 +218,60 @@ expect(result).toEqual({ ok: false, error: { type: "USER_NOT_FOUND", lookup: "no **Why**: -1. **Database agnostic** — Works with any database via adapters. Drizzle adapter included. +1. **Database agnostic** — Works with any database via adapters. Drizzle adapter + included. -2. **Session-based by default** — JWTs are stateless but hard to revoke. Sessions are simpler and more secure. +2. **Session-based by default** — JWTs are stateless but hard to revoke. + Sessions are simpler and more secure. -3. **Built-in features** — Email/password, OAuth, email verification, password reset. No assembly required. +3. **Built-in features** — Email/password, OAuth, email verification, password + reset. No assembly required. 4. **TypeScript native** — Types are correct and complete. -5. **Framework agnostic** — Works with Hono, Express, anything with Request/Response. +5. **Framework agnostic** — Works with Hono, Express, anything with + Request/Response. **Configuration**: ```typescript export const auth = betterAuth({ - database: drizzleAdapter(db, { provider: 'pg' }), + database: drizzleAdapter(db, { provider: "pg" }), emailAndPassword: { enabled: true }, session: { expiresIn: 60 * 60 * 24 * 7, // 7 days - updateAge: 60 * 60 * 24, // Refresh daily + updateAge: 60 * 60 * 24, // Refresh daily }, }); ``` **Schema generation**: -Better Auth manages its own tables: `user`, `session`, `account`, `verification`. The initial migration in `packages/db/migrations/` already includes these. If you add plugins (2FA, API keys, organisations, passkeys, etc.), each plugin adds its own tables. Regenerate the schema before migrating: +Better Auth manages its own tables: `user`, `session`, `account`, +`verification`. The initial migration in `packages/db/migrations/` already +includes these. If you add plugins (2FA, API keys, organisations, passkeys, +etc.), each plugin adds its own tables. Regenerate the schema before migrating: ```bash -npx @better-auth/cli generate # Introspects auth config, updates packages/db/src/schema/ -pnpm db:generate # Drizzle diffing → new migration file -pnpm db:migrate # Applies migration to the database +deno run -A npm:@better-auth/cli generate # Introspects auth config, updates packages/db/src/schema/ +deno task db:generate # Drizzle diffing → new migration file +deno task db:migrate # Applies migration to the database ``` -Run this any time you add or remove a Better Auth plugin. The CLI reads your auth config from `apps/backend/src/lib/auth.ts` directly — it doesn't need a running server. +Run this any time you add or remove a Better Auth plugin. The CLI reads your +auth config from `apps/backend/src/lib/auth.ts` directly — it doesn't need a +running server. -See the [Better Auth database docs](https://www.better-auth.com/docs/concepts/database) for the full table reference and plugin schema additions. +See the +[Better Auth database docs](https://www.better-auth.com/docs/concepts/database) +for the full table reference and plugin schema additions. **Trade-offs**: - Newer library, smaller community than Auth.js/Lucia - Some advanced features still in development -- Schema is Better Auth-owned — don't add custom columns to auth tables; extend the `user` table via the Drizzle schema separately +- Schema is Better Auth-owned — don't add custom columns to auth tables; extend + the `user` table via the Drizzle schema separately --- @@ -241,7 +281,8 @@ See the [Better Auth database docs](https://www.better-auth.com/docs/concepts/da **Why**: -1. **One schema, many uses** — Validate requests, generate OpenAPI, infer TypeScript types. +1. **One schema, many uses** — Validate requests, generate OpenAPI, infer + TypeScript types. ```typescript const userSchema = z.object({ @@ -249,7 +290,7 @@ const userSchema = z.object({ name: z.string().min(2), }); -type User = z.infer; // TypeScript type +type User = z.infer; // TypeScript type // Also used for request validation and OpenAPI spec ``` @@ -280,11 +321,11 @@ const publicUserSchema = userSchema.omit({ email: true }); Not all errors are equal: -| Type | Example | Handling | -|------|---------|----------| +| Type | Example | Handling | +| -------------- | --------------------------- | ------------------------------ | | Infrastructure | DB timeout, network failure | 500, log, don't expose details | -| Domain | User not found, email taken | 4xx, return specific message | -| Validation | Invalid email format | 400, return field errors | +| Domain | User not found, email taken | 4xx, return specific message | +| Validation | Invalid email format | 400, return field errors | **Implementation**: @@ -306,13 +347,13 @@ class InfrastructureError extends Error { ```typescript switch (result.type) { - case 'USER_NOT_FOUND': + case "USER_NOT_FOUND": // Domain error — tell them what happened - return c.json({ error: 'User not found' }, 404); - case 'INFRASTRUCTURE_ERROR': + return c.json({ error: "User not found" }, 404); + case "INFRASTRUCTURE_ERROR": // Infrastructure — log it, give generic response logger.error(result.error); - return c.json({ error: 'Internal error' }, 500); + return c.json({ error: "Internal error" }, 500); } ``` @@ -324,9 +365,11 @@ switch (result.type) { **Why**: -1. **Colocation** — Everything for a feature is in one folder. No jumping between `/controllers`, `/services`, `/repositories`. +1. **Colocation** — Everything for a feature is in one folder. No jumping + between `/controllers`, `/services`, `/repositories`. -2. **Predictability** — Same structure everywhere. Once you've seen one module, you've seen them all. +2. **Predictability** — Same structure everywhere. Once you've seen one module, + you've seen them all. 3. **Encapsulation** — Modules can be moved, deleted, or extracted to a package. @@ -346,18 +389,19 @@ modules/users/ handlers.test.ts # HTTP integration tests (full stack via app.request) ``` -The `usecases.ts` file is **optional** — add it when business rules exist that are worth testing in isolation. Skip it for pure CRUD modules. +The `usecases.ts` file is **optional** — add it when business rules exist that +are worth testing in isolation. Skip it for pure CRUD modules. **Naming conventions**: -| File | Purpose | -|------|---------| -| `*.errors.ts` | Domain error discriminated union types | -| `*.repository.ts` | Data access — `tryInfra`, `Result`, never throws | -| `*.usecases.ts` | Pure business logic — no DB imports, no async | -| `routes.ts` | `createRoute` definitions + exported route types | -| `handlers.ts` | `AppRouteHandler` implementations | -| `index.ts` | Creates router, wires routes → handlers, default export | +| File | Purpose | +| ----------------- | ------------------------------------------------------- | +| `*.errors.ts` | Domain error discriminated union types | +| `*.repository.ts` | Data access — `tryInfra`, `Result`, never throws | +| `*.usecases.ts` | Pure business logic — no DB imports, no async | +| `routes.ts` | `createRoute` definitions + exported route types | +| `handlers.ts` | `AppRouteHandler` implementations | +| `index.ts` | Creates router, wires routes → handlers, default export | --- @@ -367,35 +411,51 @@ The `usecases.ts` file is **optional** — add it when business rules exist that **Why**: -Repositories and use-cases are module-level functions, not classes. Their dependencies (the `db` connection, `env` config) are imported directly at module scope. +Repositories and use-cases are module-level functions, not classes. Their +dependencies (the `db` connection, `env` config) are imported directly at module +scope. This works because: - The DB is a single Postgres connection pool shared across the process -- Tests use environment-specific config (`.env.test` → different DB URL in CI if needed) +- Tests use environment-specific config (`.env.test` → different DB URL in CI if + needed) - Use-cases are pure functions with no dependencies at all - There is nothing to swap at runtime -Adding a DI container (tsyringe, inversify, etc.) would require decorators, a reflect-metadata polyfill, class-based repositories, and configuration that provides no practical benefit over direct imports at this scale. +Adding a DI container (tsyringe, inversify, etc.) would require decorators, a +reflect-metadata polyfill, class-based repositories, and configuration that +provides no practical benefit over direct imports at this scale. **Trade-offs**: - No runtime swapping of implementations -- Tighter coupling between repository functions and the `db` singleton (acceptable — it's the intended deployment model) +- Tighter coupling between repository functions and the `db` singleton + (acceptable — it's the intended deployment model) --- ## Observability: Wide Events over Logs + Metrics -**Choice**: One structured wide event per request → Axiom via `@axiomhq/pino`, with tail-based sampling. +**Choice**: One structured wide event per request → Axiom via `@axiomhq/pino`, +with tail-based sampling. ### Sources This decision is grounded in two bodies of work: -1. **[loggingsucks.com](https://loggingsucks.com)** by Boris Tane (2024) — a practical synthesis of the wide events pattern with a concrete implementation walkthrough. The article coined the framing we use: "instead of logging what your code is doing, log what happened to this request." +1. **[loggingsucks.com](https://loggingsucks.com)** by Boris Tane (2024) — a + practical synthesis of the wide events pattern with a concrete implementation + walkthrough. The article coined the framing we use: "instead of logging what + your code is doing, log what happened to this request." -2. **Charity Majors** (CTO, Honeycomb) — the originator of the "high-cardinality, high-dimensionality observability" approach. Her key argument: traditional APM and log aggregators are optimized for *writing* (counters, pre-aggregated metrics, plain strings), not *querying*. When something breaks, you don't know in advance which dimensions you'll need to slice on. You need to be able to ask arbitrary questions of your production data. +2. **Charity Majors** (CTO, Honeycomb) — the originator of the + "high-cardinality, high-dimensionality observability" approach. Her key + argument: traditional APM and log aggregators are optimized for _writing_ + (counters, pre-aggregated metrics, plain strings), not _querying_. When + something breaks, you don't know in advance which dimensions you'll need to + slice on. You need to be able to ask arbitrary questions of your production + data. The pattern is also known as the **Canonical Log Line**, popularised by Stripe. @@ -403,7 +463,8 @@ The pattern is also known as the **Canonical Log Line**, popularised by Stripe. ### The problem this solves -Traditional logging against a single checkout request generates ~17 scattered log lines: +Traditional logging against a single checkout request generates ~17 scattered +log lines: ``` [INFO] Request received from 192.168.1.50 @@ -413,9 +474,14 @@ Traditional logging against a single checkout request generates ~17 scattered lo [INFO] Request completed status=200 ``` -These lines cannot be correlated without a trace_id. They don't contain the information you need to answer "why did user X's checkout fail?" — you'd need to grep across them, inferring context from timestamps. At 10,000 requests/second, that's 170,000 log lines/second, most of them saying nothing useful. +These lines cannot be correlated without a trace_id. They don't contain the +information you need to answer "why did user X's checkout fail?" — you'd need to +grep across them, inferring context from timestamps. At 10,000 requests/second, +that's 170,000 log lines/second, most of them saying nothing useful. -The core failure is that **logs are optimised for writing, not querying**. They're written by developers at 9am thinking "this might be useful" — not by someone debugging at 2am. +The core failure is that **logs are optimised for writing, not querying**. +They're written by developers at 9am thinking "this might be useful" — not by +someone debugging at 2am. --- @@ -423,33 +489,52 @@ The core failure is that **logs are optimised for writing, not querying**. They' These principles run through every implementation decision below: -**1. High cardinality** — the ability to filter by any unique value: `user_id`, `request_id`, `trace_id`, `session_id`. Legacy systems (ELK, hosted Datadog with metrics) pre-aggregate data and discard the individual values, making it impossible to answer "was this specific user affected?". +**1. High cardinality** — the ability to filter by any unique value: `user_id`, +`request_id`, `trace_id`, `session_id`. Legacy systems (ELK, hosted Datadog with +metrics) pre-aggregate data and discard the individual values, making it +impossible to answer "was this specific user affected?". -**2. High dimensionality** — many fields per event. A wide event with 40 fields can answer 40 independent questions. A 3-field log line can answer three. +**2. High dimensionality** — many fields per event. A wide event with 40 fields +can answer 40 independent questions. A 3-field log line can answer three. -**3. One event per request, emitted once** — not 17 log lines that require manual correlation. Build the event throughout the request lifecycle, emit in the `finally` block. +**3. One event per request, emitted once** — not 17 log lines that require +manual correlation. Build the event throughout the request lifecycle, emit in +the `finally` block. -**4. Structured events are non-negotiable** — JSON only, always. String-search treats logs as bags of characters. Structured querying treats them as rows in a table. +**4. Structured events are non-negotiable** — JSON only, always. String-search +treats logs as bags of characters. Structured querying treats them as rows in a +table. -**5. Tail-based sampling over head-based** — make the sampling decision *after* the request completes, when you know the outcome. Head-based (random at start) has a 90% chance of dropping the specific error you need at 1% sample rate. Tail-based keeps 100% of errors at any sample rate. +**5. Tail-based sampling over head-based** — make the sampling decision _after_ +the request completes, when you know the outcome. Head-based (random at start) +has a 90% chance of dropping the specific error you need at 1% sample rate. +Tail-based keeps 100% of errors at any sample rate. -**6. OTel is plumbing, not observability** — OpenTelemetry standardises delivery. It does not decide what to capture. You can emit bad telemetry in a standardised format. The mental model shift (emit events not log statements) matters far more than which protocol transports them. +**6. OTel is plumbing, not observability** — OpenTelemetry standardises +delivery. It does not decide what to capture. You can emit bad telemetry in a +standardised format. The mental model shift (emit events not log statements) +matters far more than which protocol transports them. --- ### Why Axiom over ELK / Datadog / Grafana Loki -| Concern | Axiom | ELK | Loki | -|---|---|---|---| -| High-cardinality queries | ✅ ClickHouse-backed | ❌ Elasticsearch chokes | ⚠️ Slow | -| Zero infra to run | ✅ SaaS | ❌ Run your own | ❌ Run your own | -| Structured event querying | ✅ APL (SQL-like) | ⚠️ Lucene syntax | ❌ Label-based only | -| Generous free tier | ✅ 500GB/month | ❌ Self-hosted cost | ⚠️ Hosted cost | -| pino transport available | ✅ `@axiomhq/pino` | ⚠️ filebeat/logstash | ⚠️ loki-logging-plugin | +| Concern | Axiom | ELK | Loki | +| ------------------------- | -------------------- | ----------------------- | ---------------------- | +| High-cardinality queries | ✅ ClickHouse-backed | ❌ Elasticsearch chokes | ⚠️ Slow | +| Zero infra to run | ✅ SaaS | ❌ Run your own | ❌ Run your own | +| Structured event querying | ✅ APL (SQL-like) | ⚠️ Lucene syntax | ❌ Label-based only | +| Generous free tier | ✅ 500GB/month | ❌ Self-hosted cost | ⚠️ Hosted cost | +| pino transport available | ✅ `@axiomhq/pino` | ⚠️ filebeat/logstash | ⚠️ loki-logging-plugin | -Axiom uses ClickHouse under the hood — a columnar database built for high-cardinality, high-dimensionality analytics. This is what Charity Majors' argument points at: the tooling has caught up. The bottleneck is now the mental model, not the storage engine. +Axiom uses ClickHouse under the hood — a columnar database built for +high-cardinality, high-dimensionality analytics. This is what Charity Majors' +argument points at: the tooling has caught up. The bottleneck is now the mental +model, not the storage engine. -When `AXIOM_TOKEN` is not set (local dev, CI), logs go to stdout only. When set, `pino` streams to both stdout and Axiom via a multistream. No sidecar, no agent, no extra infra. +When `AXIOM_TOKEN` is not set (local dev, CI), logs go to stdout only. When set, +`pino` streams to both stdout and Axiom via a multistream. No sidecar, no agent, +no extra infra. --- @@ -461,13 +546,18 @@ OTel is a protocol for exporting telemetry to a collector. Adding it means: - Configuring exporters to Axiom's OTLP endpoint - Writing spans instead of events -For a single-service VPS deployment, this complexity buys nothing. Our `trace_id` field is propagated from the `x-trace-id` request header if set by a gateway, which gives cross-service correlation without a full OTel setup. If we move to microservices, OTel would be the right next step — the `trace_id` field is already in place to hook into it. +For a single-service VPS deployment, this complexity buys nothing. Our +`trace_id` field is propagated from the `x-trace-id` request header if set by a +gateway, which gives cross-service correlation without a full OTel setup. If we +move to microservices, OTel would be the right next step — the `trace_id` field +is already in place to hook into it. --- ### What we built -The `WideEvent` type and `addToEvent` primitive implement the pattern directly from the article's "Implementing Wide Events" section, adapted to Hono: +The `WideEvent` type and `addToEvent` primitive implement the pattern directly +from the article's "Implementing Wide Events" section, adapted to Hono: ``` Request in @@ -479,29 +569,31 @@ Request in **The fields we instrumenting by default** (without any handler-level code): -| Field | Source | Why | -|---|---|---| -| `request_id` | UUID per request | High-cardinality, uniquely identifies this event | -| `trace_id` | `x-trace-id` header or new UUID | Multi-service correlation without OTel | -| `session_id` | better-auth session ID | Separate from user_id — one user, many sessions | -| `deployment_id` | `DEPLOYMENT_ID` env var | "Which deploy caused this regression?" | -| `service_version` | `SERVICE_VERSION` env var (git SHA) | Code version of the running process | -| `user.id` + `user.role` | auth middleware | Who made this request | -| `status_code`, `duration_ms` | response | Outcome and performance | -| `ip`, `user_agent` | request headers | Client context | +| Field | Source | Why | +| ---------------------------- | ----------------------------------- | ------------------------------------------------ | +| `request_id` | UUID per request | High-cardinality, uniquely identifies this event | +| `trace_id` | `x-trace-id` header or new UUID | Multi-service correlation without OTel | +| `session_id` | better-auth session ID | Separate from user_id — one user, many sessions | +| `deployment_id` | `DEPLOYMENT_ID` env var | "Which deploy caused this regression?" | +| `service_version` | `SERVICE_VERSION` env var (git SHA) | Code version of the running process | +| `user.id` + `user.role` | auth middleware | Who made this request | +| `status_code`, `duration_ms` | response | Outcome and performance | +| `ip`, `user_agent` | request headers | Client context | **Tail-based sampling rules** (applied after request completes): -| Condition | Rationale | -|---|---| -| `status_code >= 500` | Infrastructure or application error — never drop | -| `outcome === "error"` | Explicit error outcome — never drop | -| `duration_ms > 2000` | Latency outliers — never drop | -| `user.role === "admin"` | Low-volume, high-signal operational traffic | +| Condition | Rationale | +| ----------------------- | ------------------------------------------------------------- | +| `status_code >= 500` | Infrastructure or application error — never drop | +| `outcome === "error"` | Explicit error outcome — never drop | +| `duration_ms > 2000` | Latency outliers — never drop | +| `user.role === "admin"` | Low-volume, high-signal operational traffic | | `feature_flags` present | Request is part of a feature rollout — critical for debugging | -| Everything else | 5% random sample | +| Everything else | 5% random sample | -The feature_flags rule was added beyond the article's example. During a feature rollout, dropping 95% of flagged requests would make it impossible to analyse the new behaviour. Handlers annotate rollout requests with: +The feature_flags rule was added beyond the article's example. During a feature +rollout, dropping 95% of flagged requests would make it impossible to analyse +the new behaviour. Handlers annotate rollout requests with: ```typescript addToEvent(c, { feature_flags: { new_checkout_flow: true } }); @@ -513,8 +605,20 @@ and those events are always retained. ### What we explicitly left out -**`subscription_tier` / user plan on the `user` field** — the article's canonical example includes `user.subscription: "premium"`. We left it as a `[k: string]: unknown` extension point rather than baking it in: not every app built on this template will have subscription tiers. Add it in `authMiddleware.ts` by extending the `addToEvent` call with your user's plan field once it exists on the user record. - -**`error.code` and `error.retriable`** — typed on `WideEvent.error` as optional fields. The middleware catches unhandled errors and populates `type` and `message` automatically. Handlers should add `code` and `retriable` for business-logic errors where that context is meaningful (e.g. payment failures with Stripe decline codes). - -**Metrics** — no Prometheus, no StatsD, no counters. The same queries you'd run on a metrics dashboard (error rate by endpoint, p99 latency) can be run as APL aggregations on wide events in Axiom. One data store, one query language, no cardinality limit. +**`subscription_tier` / user plan on the `user` field** — the article's +canonical example includes `user.subscription: "premium"`. We left it as a +`[k: string]: unknown` extension point rather than baking it in: not every app +built on this template will have subscription tiers. Add it in +`authMiddleware.ts` by extending the `addToEvent` call with your user's plan +field once it exists on the user record. + +**`error.code` and `error.retriable`** — typed on `WideEvent.error` as optional +fields. The middleware catches unhandled errors and populates `type` and +`message` automatically. Handlers should add `code` and `retriable` for +business-logic errors where that context is meaningful (e.g. payment failures +with Stripe decline codes). + +**Metrics** — no Prometheus, no StatsD, no counters. The same queries you'd run +on a metrics dashboard (error rate by endpoint, p99 latency) can be run as APL +aggregations on wide events in Axiom. One data store, one query language, no +cardinality limit. diff --git a/apps/backend/docs/PATTERNS.md b/apps/backend/docs/PATTERNS.md index 998ae1c..d287246 100644 --- a/apps/backend/docs/PATTERNS.md +++ b/apps/backend/docs/PATTERNS.md @@ -21,9 +21,13 @@ export const posts = pgTable("posts", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), title: text("title").notNull(), content: text("content").notNull(), - authorId: text("author_id").notNull().references(() => users.id, { onDelete: "cascade" }), - createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(), + authorId: text("author_id").notNull().references(() => users.id, { + onDelete: "cascade", + }), + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow() + .notNull(), }); export const insertPostSchema = createInsertSchema(posts); @@ -47,7 +51,11 @@ export * from "./posts"; // Expected, business-rule failures — not bugs. // Each variant carries exactly the data a handler needs. export type PostNotFound = { type: "POST_NOT_FOUND"; lookup: string }; -export type NotPostAuthor = { type: "NOT_POST_AUTHOR"; userId: string; postId: string }; +export type NotPostAuthor = { + type: "NOT_POST_AUTHOR"; + userId: string; + postId: string; +}; export type PostRepoError = PostNotFound | NotPostAuthor; ``` @@ -60,18 +68,19 @@ export type PostRepoError = PostNotFound | NotPostAuthor; import { eq } from "drizzle-orm"; import { db } from "@/db"; import { posts } from "@repo/db/schema"; -import { ok, err } from "@repo/shared"; +import { err, ok } from "@repo/shared"; import type { Result } from "@repo/shared"; import { InfrastructureError } from "@/lib/error"; import { tryInfra } from "@/lib/infra"; -import type { Post, InsertPost } from "@repo/db/schema"; +import type { InsertPost, Post } from "@repo/db/schema"; import type { PostNotFound } from "./posts.errors"; export async function findPostById( id: string, ): Promise> { - const result = await tryInfra(`fetch post ${id}`, () => - db.query.posts.findFirst({ where: eq(posts.id, id) }), + const result = await tryInfra( + `fetch post ${id}`, + () => db.query.posts.findFirst({ where: eq(posts.id, id) }), ); if (!result.ok) return result; if (!result.value) return err({ type: "POST_NOT_FOUND", lookup: id }); @@ -81,11 +90,14 @@ export async function findPostById( export async function createPost( data: InsertPost, ): Promise> { - const result = await tryInfra("create post", () => - db.insert(posts).values(data).returning().then((rows) => rows[0]), + const result = await tryInfra( + "create post", + () => db.insert(posts).values(data).returning().then((rows) => rows[0]), ); if (!result.ok) return result; - if (!result.value) return err(new InfrastructureError("Insert returned no rows")); + if (!result.value) { + return err(new InfrastructureError("Insert returned no rows")); + } return ok(result.value); } @@ -93,8 +105,12 @@ export async function updatePost( id: string, data: Partial, ): Promise> { - const result = await tryInfra(`update post ${id}`, () => - db.update(posts).set(data).where(eq(posts.id, id)).returning().then((rows) => rows[0]), + const result = await tryInfra( + `update post ${id}`, + () => + db.update(posts).set(data).where(eq(posts.id, id)).returning().then(( + rows, + ) => rows[0]), ); if (!result.ok) return result; if (!result.value) return err({ type: "POST_NOT_FOUND", lookup: id }); @@ -104,8 +120,12 @@ export async function updatePost( export async function deletePost( id: string, ): Promise> { - const result = await tryInfra(`delete post ${id}`, () => - db.delete(posts).where(eq(posts.id, id)).returning().then((rows) => rows[0]), + const result = await tryInfra( + `delete post ${id}`, + () => + db.delete(posts).where(eq(posts.id, id)).returning().then((rows) => + rows[0] + ), ); if (!result.ok) return result; if (!result.value) return err({ type: "POST_NOT_FOUND", lookup: id }); @@ -120,15 +140,15 @@ export async function deletePost( ```typescript import { createRoute, z } from "@hono/zod-openapi"; import { selectPostSchema } from "@repo/db/schema"; -import { apiSuccessSchema, apiErrorSchema } from "@repo/shared"; +import { apiErrorSchema, apiSuccessSchema } from "@repo/shared"; import { - jsonRes, + CREATED, + INTERNAL_SERVER_ERROR, jsonBody, + jsonRes, + NOT_FOUND, OK, - CREATED, UNAUTHORIZED, - NOT_FOUND, - INTERNAL_SERVER_ERROR, } from "@/lib/types"; const tags = ["Posts"]; @@ -142,7 +162,9 @@ export const createPost = createRoute({ path: "/posts", tags, request: { - body: jsonBody(z.object({ title: z.string().min(3), content: z.string().min(1) })), + body: jsonBody( + z.object({ title: z.string().min(3), content: z.string().min(1) }), + ), }, responses: { [CREATED]: jsonRes(apiSuccessSchema(selectPostSchema), "Created"), @@ -168,26 +190,31 @@ export type CreatePostRoute = typeof createPost; export type GetPostRoute = typeof getPost; ``` -> **Every status code your handler returns must be declared in `responses`.** `AppRouteHandler` is type-safe against the route definition — returning an undeclared status (including 500) is a compile error. Always declare 500 if the handler calls a repository. +> **Every status code your handler returns must be declared in `responses`.** +> `AppRouteHandler` is type-safe against the route definition — returning an +> undeclared status (including 500) is a compile error. Always declare 500 if +> the handler calls a repository. ### 5. Write Use-Cases (when needed) -Add `posts.usecases.ts` only when business logic exists. These are **pure functions** — no DB imports, no async, no HTTP. +Add `posts.usecases.ts` only when business logic exists. These are **pure +functions** — no DB imports, no async, no HTTP. `apps/backend/src/modules/posts/posts.usecases.ts`: ```typescript -import type { User, Post } from "@repo/db/schema"; +import type { Post, User } from "@repo/db/schema"; import type { NotPostAuthor } from "./posts.errors"; -import { ok, err, type Result } from "@repo/shared"; +import { err, ok, type Result } from "@repo/shared"; // Pure rule: can this user modify this post? export function authorizePostUpdate( user: { id: string }, post: Post, ): Result { - if (post.authorId !== user.id) + if (post.authorId !== user.id) { return err({ type: "NOT_POST_AUTHOR", userId: user.id, postId: post.id }); + } return ok(post); } ``` @@ -202,19 +229,33 @@ For simple CRUD with no rules, skip this file entirely. ```typescript import type { AppRouteHandler } from "@/lib/types"; -import { success, failure, isInfraError, OK, CREATED, NOT_FOUND, INTERNAL_SERVER_ERROR } from "@/lib/types"; +import { + CREATED, + failure, + INTERNAL_SERVER_ERROR, + isInfraError, + NOT_FOUND, + OK, + success, +} from "@/lib/types"; import { match } from "@repo/shared"; import type { CreatePostRoute, GetPostRoute } from "./routes"; import { createPost, findPostById } from "./posts.repository"; -export const createPostHandler: AppRouteHandler = async (c) => { +export const createPostHandler: AppRouteHandler = async ( + c, +) => { const userId = c.get("user").id; const body = c.req.valid("json"); const result = await createPost({ authorId: userId, ...body }); - if (!result.ok) - return c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR); + if (!result.ok) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } return c.json(success(result.value), CREATED); }; @@ -227,11 +268,18 @@ export const getPostHandler: AppRouteHandler = async (c) => { return match(result, { ok: (post) => c.json(success(post), OK), err: (e) => { - if (isInfraError(e)) - return c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR); + if (isInfraError(e)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } switch (e.type) { case "POST_NOT_FOUND": - return c.json(failure({ code: "NOT_FOUND", message: "Post not found" }), NOT_FOUND); + return c.json( + failure({ code: "NOT_FOUND", message: "Post not found" }), + NOT_FOUND, + ); } }, }); @@ -282,7 +330,7 @@ app.use("/api/*", authMiddleware); ```typescript export const handler: AppRouteHandler = async (c) => { - const user = c.get("user"); // { id, email, name, role } + const user = c.get("user"); // { id, email, name, role } const session = c.get("session"); // { id, userId, expiresAt } }; ``` @@ -303,7 +351,8 @@ app.use("/api/admin/*", requireRole("admin")); ### Ownership Checks -Put ownership rules in a use-case — pure function, no DB. The handler loads the data (imperative shell) and delegates the rule (functional core): +Put ownership rules in a use-case — pure function, no DB. The handler loads the +data (imperative shell) and delegates the rule (functional core): ```typescript // posts.usecases.ts @@ -311,36 +360,57 @@ export function authorizePostUpdate( user: { id: string }, post: Post, ): Result { - if (post.authorId !== user.id) + if (post.authorId !== user.id) { return err({ type: "NOT_POST_AUTHOR", userId: user.id, postId: post.id }); + } return ok(post); } // handlers.ts -export const updatePostHandler: AppRouteHandler = async (c) => { +export const updatePostHandler: AppRouteHandler = async ( + c, +) => { const user = c.get("user"); const { id } = c.req.valid("param"); const body = c.req.valid("json"); const found = await findPostById(id); - if (!found.ok) + if (!found.ok) { return isInfraError(found.error) - ? c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR) - : c.json(failure({ code: "NOT_FOUND", message: "Post not found" }), NOT_FOUND); + ? c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ) + : c.json( + failure({ code: "NOT_FOUND", message: "Post not found" }), + NOT_FOUND, + ); + } const authorized = authorizePostUpdate(user, found.value); - if (!authorized.ok) - return c.json(failure({ code: "FORBIDDEN", message: "Not your post" }), FORBIDDEN); + if (!authorized.ok) { + return c.json( + failure({ code: "FORBIDDEN", message: "Not your post" }), + FORBIDDEN, + ); + } const result = await updatePost(id, body); return match(result, { ok: (post) => c.json(success(post), OK), err: (e) => { - if (isInfraError(e)) - return c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR); + if (isInfraError(e)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } switch (e.type) { case "POST_NOT_FOUND": - return c.json(failure({ code: "NOT_FOUND", message: "Post not found" }), NOT_FOUND); + return c.json( + failure({ code: "NOT_FOUND", message: "Post not found" }), + NOT_FOUND, + ); } }, }); @@ -352,31 +422,43 @@ export const updatePostHandler: AppRouteHandler = async (c) => ## Result Helpers ```typescript -import { ok, err, map, andThen, andThenAsync, match } from "@repo/shared"; +import { andThen, andThenAsync, err, map, match, ok } from "@repo/shared"; ``` ### `match` — handle both branches in a handler -`match` handles the ok/err split. When the error union has multiple variants, use `switch` inside the `err` branch — TypeScript will tell you if you miss one: +`match` handles the ok/err split. When the error union has multiple variants, +use `switch` inside the `err` branch — TypeScript will tell you if you miss one: ```typescript const result = await findPostById(id); return match(result, { ok: (post) => c.json(success(post), OK), err: (e) => { - if (isInfraError(e)) - return c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR); + if (isInfraError(e)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } switch (e.type) { case "POST_NOT_FOUND": - return c.json(failure({ code: "NOT_FOUND", message: "Post not found" }), NOT_FOUND); + return c.json( + failure({ code: "NOT_FOUND", message: "Post not found" }), + NOT_FOUND, + ); case "NOT_POST_AUTHOR": - return c.json(failure({ code: "FORBIDDEN", message: "Not your post" }), FORBIDDEN); + return c.json( + failure({ code: "FORBIDDEN", message: "Not your post" }), + FORBIDDEN, + ); } }, }); ``` -The ternary shorthand is only appropriate when there is exactly one domain error variant. +The ternary shorthand is only appropriate when there is exactly one domain error +variant. ### `andThenAsync` — chain two async repository calls @@ -421,9 +503,10 @@ export async function listPosts(options: { offset: options.offset, orderBy: (p, { desc }) => desc(p.createdAt), }), - db.select({ count: sql`count(*)` }).from(posts).then((r) => Number(r[0].count)), - ]), - ); + db.select({ count: sql`count(*)` }).from(posts).then((r) => + Number(r[0].count) + ), + ])); if (!result.ok) return result; const [data, total] = result.value; return ok({ data, total }); @@ -438,8 +521,12 @@ export const listPostsHandler: AppRouteHandler = async (c) => { const offset = (page - 1) * limit; const result = await listPosts({ limit, offset }); - if (!result.ok) - return c.json(failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), INTERNAL_SERVER_ERROR); + if (!result.ok) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } const { data, total } = result.value; return c.json({ @@ -458,16 +545,25 @@ Three tiers. No mocks unless there is genuinely no alternative. ### 1. Unit Testing Use-Cases -Use-cases are pure functions — no DB, no HTTP, no mocks needed. Just call them with plain values. +Use-cases are pure functions — no DB, no HTTP, no mocks needed. Just call them +with plain values. File: `modules/posts/__tests__/posts.usecases.test.ts` ```typescript -import { describe, it, expect } from "vitest"; +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import { authorizePostUpdate } from "../posts.usecases"; describe("authorizePostUpdate", () => { - const post = { id: "p1", authorId: "u1", title: "hi", content: "...", createdAt: new Date(), updatedAt: new Date() }; + const post = { + id: "p1", + authorId: "u1", + title: "hi", + content: "...", + createdAt: new Date(), + updatedAt: new Date(), + }; it("returns ok when user owns the post", () => { const result = authorizePostUpdate({ id: "u1" }, post); @@ -476,7 +572,10 @@ describe("authorizePostUpdate", () => { it("returns NOT_POST_AUTHOR when user does not own the post", () => { const result = authorizePostUpdate({ id: "u2" }, post); - expect(result).toEqual({ ok: false, error: { type: "NOT_POST_AUTHOR", userId: "u2", postId: "p1" } }); + expect(result).toEqual({ + ok: false, + error: { type: "NOT_POST_AUTHOR", userId: "u2", postId: "p1" }, + }); }); }); ``` @@ -485,12 +584,14 @@ Every business rule lives in a use-case. Every use-case is testable this way. ### 2. Integration Testing Repositories -Repositories talk directly to the DB — test them against a real test database, not mocks. Run a local Postgres instance (or Docker) configured via `.env.test`. +Repositories talk directly to the DB — test them against a real test database, +not mocks. Run a local Postgres instance (or Docker) configured via `.env.test`. File: `modules/posts/__tests__/posts.repository.test.ts` ```typescript -import { describe, it, expect, afterEach } from "vitest"; +import { afterEach, describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import { createPost, findPostById } from "../posts.repository"; import { db } from "@/db"; import { schema } from "@/db"; @@ -504,11 +605,18 @@ afterEach(async () => { describe("findPostById", () => { it("returns POST_NOT_FOUND when no row exists", async () => { const result = await findPostById("nonexistent"); - expect(result).toEqual({ ok: false, error: { type: "POST_NOT_FOUND", lookup: "nonexistent" } }); + expect(result).toEqual({ + ok: false, + error: { type: "POST_NOT_FOUND", lookup: "nonexistent" }, + }); }); it("returns the post when it exists", async () => { - const created = await createPost({ authorId: "u1", title: "hello", content: "world" }); + const created = await createPost({ + authorId: "u1", + title: "hello", + content: "world", + }); expect(created.ok).toBe(true); if (!created.ok) return; @@ -518,16 +626,19 @@ describe("findPostById", () => { }); ``` -This tests the actual SQL queries, actual constraint errors, and actual `tryInfra` boundary behaviour. +This tests the actual SQL queries, actual constraint errors, and actual +`tryInfra` boundary behaviour. ### 3. Integration Testing Handlers -Test full HTTP flows against a real DB using `app.request()`. Auth is handled by signing up through the app — no mocks, no fake sessions. +Test full HTTP flows against a real DB using `app.request()`. Auth is handled by +signing up through the app — no mocks, no fake sessions. File: `modules/posts/__tests__/handlers.test.ts` ```typescript -import { describe, it, expect, afterEach } from "vitest"; +import { afterEach, describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import app from "@/app"; import { db } from "@/db"; import { schema } from "@/db"; @@ -544,7 +655,7 @@ async function signUp(email = "test@example.com", password = "password-123") { afterEach(async () => { await db.delete(schema.verifications); // no FK cascade - await db.delete(schema.users); // cascades sessions + accounts + await db.delete(schema.users); // cascades sessions + accounts }); describe("GET /api/posts/:id", () => { @@ -581,6 +692,7 @@ describe("GET /api/posts/:id", () => { ### When Mocks Are Acceptable Only when the real thing cannot run in a test environment: + - External email/SMS providers (mock the transport, not the business logic) - Third-party payment APIs (Stripe, etc.) - External webhooks or OAuth flows @@ -591,8 +703,9 @@ Do **not** mock the DB, Redis, or any infrastructure you can run locally. ## Observability — Enriching Wide Events -Every request emits **one** structured JSON event (a "wide event" / canonical log line). -`wideEventMiddleware` populates the base fields automatically; handlers add domain context. +Every request emits **one** structured JSON event (a "wide event" / canonical +log line). `wideEventMiddleware` populates the base fields automatically; +handlers add domain context. ### Adding fields from a handler @@ -641,16 +754,16 @@ The wide event emitted at the end of the request includes every field added via ### Sampling rules (applied before emit) -| Condition | Sampled? | -|---|---| -| `status_code >= 500` | Always | -| `outcome === "error"` | Always | -| `duration_ms > 2000` | Always (slow requests) | -| User role `admin` | Always | -| Everything else | 5% random | +| Condition | Sampled? | +| --------------------- | ---------------------- | +| `status_code >= 500` | Always | +| `outcome === "error"` | Always | +| `duration_ms > 2000` | Always (slow requests) | +| User role `admin` | Always | +| Everything else | 5% random | -Events that are dropped are never written to Axiom, so you stay within the free tier -for normal traffic while retaining 100% of interesting signals. +Events that are dropped are never written to Axiom, so you stay within the free +tier for normal traffic while retaining 100% of interesting signals. ### Axiom setup @@ -663,4 +776,5 @@ for normal traffic while retaining 100% of interesting signals. SERVICE_VERSION=$(git rev-parse --short HEAD) REGION=eu-central-1 ``` -4. No extra infrastructure — `@axiomhq/pino` streams directly from the process over HTTPS. +4. No extra infrastructure — `@axiomhq/pino` streams directly from the process + over HTTPS. diff --git a/apps/backend/docs/REFERENCES.md b/apps/backend/docs/REFERENCES.md index 6bec1b0..c8e48b8 100644 --- a/apps/backend/docs/REFERENCES.md +++ b/apps/backend/docs/REFERENCES.md @@ -1,83 +1,102 @@ # References & Further Reading -The talks, articles, and specifications that informed the decisions in this codebase. Each section maps to a decision in [DECISIONS.md](./DECISIONS.md). +The talks, articles, and specifications that informed the decisions in this +codebase. Each section maps to a decision in [DECISIONS.md](./DECISIONS.md). --- ## Functional Core / Imperative Shell -*Decisions: use-case layer, handlers as orchestrators, pure functions for business logic.* +_Decisions: use-case layer, handlers as orchestrators, pure functions for +business logic._ -**Gary Bernhardt — "Boundaries"** (Destroy All Software, 2012) -The original articulation of this pattern. A 30-minute screencast explaining why mixing pure logic with I/O produces code that is hard to test and hard to reason about. Every use-case in this codebase exists because of this talk. +**Gary Bernhardt — "Boundaries"** (Destroy All Software, 2012) The original +articulation of this pattern. A 30-minute screencast explaining why mixing pure +logic with I/O produces code that is hard to test and hard to reason about. +Every use-case in this codebase exists because of this talk. https://www.destroyallsoftware.com/talks/boundaries -**Mark Seemann — "Impureim Sandwich"** (2020) -A concise restatement of the same idea: order your code as impure → pure → impure. Handlers are the bread; use-cases are the filling. -https://blog.ploeh.dk/2020/03/02/impureim-sandwich/ +**Mark Seemann — "Impureim Sandwich"** (2020) A concise restatement of the same +idea: order your code as impure → pure → impure. Handlers are the bread; +use-cases are the filling. https://blog.ploeh.dk/2020/03/02/impureim-sandwich/ --- ## Result Types / Railway Oriented Programming -*Decisions: never throw, `Result`, `ok`/`err`/`match`/`andThen`/`map` combinators.* +_Decisions: never throw, `Result`, `ok`/`err`/`match`/`andThen`/`map` +combinators._ -**Scott Wlaschin — "Railway Oriented Programming"** (NDC Oslo, 2014) -The talk that popularised chaining functions that carry a success/failure track. Direct inspiration for the combinator vocabulary in `@repo/shared`. The article version is a good reference to keep open while reading this codebase. -https://fsharpforfun.com/posts/recipe-part2.html -Talk: https://www.youtube.com/watch?v=fYo3LN9Vf_M +**Scott Wlaschin — "Railway Oriented Programming"** (NDC Oslo, 2014) The talk +that popularised chaining functions that carry a success/failure track. Direct +inspiration for the combinator vocabulary in `@repo/shared`. The article version +is a good reference to keep open while reading this codebase. +https://fsharpforfun.com/posts/recipe-part2.html Talk: +https://www.youtube.com/watch?v=fYo3LN9Vf_M -**Rust `std::result::Result`** -Language-level proof that Result types work at scale. Much of the combinator naming (`map`, `and_then`, `unwrap_or`) comes from Rust's standard library. -https://doc.rust-lang.org/std/result/ +**Rust `std::result::Result`** Language-level proof that Result types work at +scale. Much of the combinator naming (`map`, `and_then`, `unwrap_or`) comes from +Rust's standard library. https://doc.rust-lang.org/std/result/ --- ## Parse, Don't Validate -*Decisions: Zod at the HTTP boundary, `c.req.valid("json")` returns a fully-typed value — no further runtime checks inside the application.* +_Decisions: Zod at the HTTP boundary, `c.req.valid("json")` returns a +fully-typed value — no further runtime checks inside the application._ -**Alexis King — "Parse, Don't Validate"** (2019) -Argues that validation produces a boolean and throws away information; parsing produces a richer type and cannot be bypassed by accident. The core reason we validate *once* at the route boundary and trust the types downstream. +**Alexis King — "Parse, Don't Validate"** (2019) Argues that validation produces +a boolean and throws away information; parsing produces a richer type and cannot +be bypassed by accident. The core reason we validate _once_ at the route +boundary and trust the types downstream. https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ --- ## Making Impossible States Impossible -*Decisions: domain errors are discriminated unions with typed payloads, not generic `Error` subclasses or string codes.* +_Decisions: domain errors are discriminated unions with typed payloads, not +generic `Error` subclasses or string codes._ **Richard Feldman — "Making Impossible States Impossible"** (Elm Europe, 2016) -Shows how to design types so that invalid program states literally cannot be constructed. The pattern behind `type UserError = UserNotFound | EmailTaken` — each variant typed, each distinct, each handling exactly the data its handler needs. -https://www.youtube.com/watch?v=IcgmSRJHu_8 +Shows how to design types so that invalid program states literally cannot be +constructed. The pattern behind `type UserError = UserNotFound | EmailTaken` — +each variant typed, each distinct, each handling exactly the data its handler +needs. https://www.youtube.com/watch?v=IcgmSRJHu_8 --- ## Integration Testing Over Mock-Heavy Unit Tests -*Decisions: repositories tested against a real database, no mocking infrastructure, mocks only for third-party services that cannot run locally.* +_Decisions: repositories tested against a real database, no mocking +infrastructure, mocks only for third-party services that cannot run locally._ -**J.B. Rainsberger — "Integrated Tests Are a Scam"** (continuously updated since 2010) -Not an argument against integration tests — an argument against the belief that mocking your infrastructure gives you real confidence. Required reading before reaching for `vi.mock` on a database call. +**J.B. Rainsberger — "Integrated Tests Are a Scam"** (continuously updated +since 2010) Not an argument against integration tests — an argument against the +belief that mocking your infrastructure gives you real confidence. Required +reading before reaching for `vi.mock` on a database call. https://blog.thecodewhisperer.com/permalink/integrated-tests-are-a-scam -**Martin Fowler — "Test Double"** -The canonical taxonomy: stubs, mocks, fakes, spies. Useful for understanding *what* to mock (external HTTP APIs, payment providers, email transports) versus *what not to* (Postgres, Redis, infrastructure you can run in Docker). +**Martin Fowler — "Test Double"** The canonical taxonomy: stubs, mocks, fakes, +spies. Useful for understanding _what_ to mock (external HTTP APIs, payment +providers, email transports) versus _what not to_ (Postgres, Redis, +infrastructure you can run in Docker). https://martinfowler.com/bliki/TestDouble.html --- ## OpenAPI-First Design -*Decisions: `createRoute()` as single source of truth for validation, types, and documentation.* +_Decisions: `createRoute()` as single source of truth for validation, types, and +documentation._ -**OpenAPI Specification 3.1** -The specification every `createRoute()` call maps to. Worth reading the Paths and Components sections if you're extending the API. +**OpenAPI Specification 3.1** The specification every `createRoute()` call maps +to. Worth reading the Paths and Components sections if you're extending the API. https://spec.openapis.org/oas/v3.1.0 -**Zod documentation** -Zod schemas serve double duty throughout this codebase: runtime validation at the boundary and static TypeScript type inference everywhere downstream. -https://zod.dev +**Zod documentation** Zod schemas serve double duty throughout this codebase: +runtime validation at the boundary and static TypeScript type inference +everywhere downstream. https://zod.dev --- @@ -89,47 +108,64 @@ https://hono.dev **Drizzle ORM documentation** — schema declaration, query builder, migrations https://orm.drizzle.team -**better-auth documentation** — session management, auth flows, email verification -https://www.better-auth.com +**better-auth documentation** — session management, auth flows, email +verification https://www.better-auth.com --- ## Observability -*Decisions: wide events over scattered logs + metrics, tail-based sampling, Axiom, `addToEvent` as the instrumentation primitive. See the full rationale in [DECISIONS.md → Observability](./DECISIONS.md).* - -**Boris Tane — "Logging Sucks"** (loggingsucks.com, 2024) -The most direct reference for our implementation. Walks through the core problem (17 log lines per request that tell you nothing), defines the vocabulary (cardinality, dimensionality, wide events, canonical log lines), shows the implementation pattern step by step, and covers tail-based sampling. The `wideEventMiddleware` and `addToEvent` pattern in this codebase follows this implementation guide directly. -https://loggingsucks.com - -**Charity Majors — Blog and talks (Honeycomb)** -The originator of the "high-cardinality observability" argument. Her central claim: traditional logging and APM are broken because they force you to pre-aggregate before storage, discarding the individual request data you need to debug production. You need to store raw events in a column-oriented store and query them at debug time — not grep them. -- "Observability — The 5-Year Retrospective": https://charity.wtf/2020/03/03/observability-is-a-many-splendored-thing/ +_Decisions: wide events over scattered logs + metrics, tail-based sampling, +Axiom, `addToEvent` as the instrumentation primitive. See the full rationale in +[DECISIONS.md → Observability](./DECISIONS.md)._ + +**Boris Tane — "Logging Sucks"** (loggingsucks.com, 2024) The most direct +reference for our implementation. Walks through the core problem (17 log lines +per request that tell you nothing), defines the vocabulary (cardinality, +dimensionality, wide events, canonical log lines), shows the implementation +pattern step by step, and covers tail-based sampling. The `wideEventMiddleware` +and `addToEvent` pattern in this codebase follows this implementation guide +directly. https://loggingsucks.com + +**Charity Majors — Blog and talks (Honeycomb)** The originator of the +"high-cardinality observability" argument. Her central claim: traditional +logging and APM are broken because they force you to pre-aggregate before +storage, discarding the individual request data you need to debug production. +You need to store raw events in a column-oriented store and query them at debug +time — not grep them. + +- "Observability — The 5-Year Retrospective": + https://charity.wtf/2020/03/03/observability-is-a-many-splendored-thing/ - "Is This Just Metrics?": https://charity.wtf/2022/08/12/is-this-just-metrics/ -- "High Cardinality is Not the Same As High Dimensionality": https://charity.wtf/2021/08/09/notes-on-the-art-of-measuring-things/ +- "High Cardinality is Not the Same As High Dimensionality": + https://charity.wtf/2021/08/09/notes-on-the-art-of-measuring-things/ -**Stripe Engineering — "Canonical Log Lines"** -The origin of the term. Stripe's approach: each service emits one structured log line per request containing all the context needed to understand what happened. This is the "canonical log line" that `wideEventMiddleware` implements. +**Stripe Engineering — "Canonical Log Lines"** The origin of the term. Stripe's +approach: each service emits one structured log line per request containing all +the context needed to understand what happened. This is the "canonical log line" +that `wideEventMiddleware` implements. https://stripe.com/blog/canonical-log-lines -**Honeycomb documentation — "Core Analysis Loop"** -The mental model for debugging with events: start with a wide query (error rate), narrow by cardinality (which user_id?), expand to see the full event context. This is the loop Axiom's APL enables on our wide events. +**Honeycomb documentation — "Core Analysis Loop"** The mental model for +debugging with events: start with a wide query (error rate), narrow by +cardinality (which user_id?), expand to see the full event context. This is the +loop Axiom's APL enables on our wide events. https://docs.honeycomb.io/investigate-incidents-faster/ --- ## Summary Table -| Resource | Author | Format | Why it matters here | -|---|---|---|---| -| "Boundaries" | Gary Bernhardt | Talk (30 min) | Functional core / imperative shell | -| "Impureim Sandwich" | Mark Seemann | Article | Practical ordering of pure and impure code | -| "Railway Oriented Programming" | Scott Wlaschin | Talk + Article | Result types, combinator design | -| "Parse, Don't Validate" | Alexis King | Article | Zod at the boundary, trust types downstream | -| "Making Impossible States Impossible" | Richard Feldman | Talk (25 min) | Discriminated union error design | -| "Integrated Tests Are a Scam" | J.B. Rainsberger | Article | Real DB over mocks | -| "Test Double" | Martin Fowler | Article | When mocks are actually appropriate | -| *A Philosophy of Software Design* | John Ousterhout | Book | Deep modules, reducing complexity | -| "Logging Sucks" | Boris Tane | Article | Wide events implementation pattern | -| "Observability Requires Rethinking Logging" | Charity Majors | Articles/Talks | High cardinality/dimensionality philosophy | -| "Is This Just Metrics?" | Charity Majors | Article | Why metrics are not a substitute for events | +| Resource | Author | Format | Why it matters here | +| ------------------------------------------- | ---------------- | -------------- | ------------------------------------------- | +| "Boundaries" | Gary Bernhardt | Talk (30 min) | Functional core / imperative shell | +| "Impureim Sandwich" | Mark Seemann | Article | Practical ordering of pure and impure code | +| "Railway Oriented Programming" | Scott Wlaschin | Talk + Article | Result types, combinator design | +| "Parse, Don't Validate" | Alexis King | Article | Zod at the boundary, trust types downstream | +| "Making Impossible States Impossible" | Richard Feldman | Talk (25 min) | Discriminated union error design | +| "Integrated Tests Are a Scam" | J.B. Rainsberger | Article | Real DB over mocks | +| "Test Double" | Martin Fowler | Article | When mocks are actually appropriate | +| _A Philosophy of Software Design_ | John Ousterhout | Book | Deep modules, reducing complexity | +| "Logging Sucks" | Boris Tane | Article | Wide events implementation pattern | +| "Observability Requires Rethinking Logging" | Charity Majors | Articles/Talks | High cardinality/dimensionality philosophy | +| "Is This Just Metrics?" | Charity Majors | Article | Why metrics are not a substitute for events | diff --git a/apps/backend/drizzle.config.ts b/apps/backend/drizzle.config.ts index 9f3e39c..f2b5095 100644 --- a/apps/backend/drizzle.config.ts +++ b/apps/backend/drizzle.config.ts @@ -1,14 +1,13 @@ -import "dotenv/config"; import { defineConfig } from "drizzle-kit"; -import env from "./src/env.js"; + +const DATABASE_URL = Deno.env.get("DATABASE_URL"); +if (!DATABASE_URL) throw new Error("DATABASE_URL is required"); export default defineConfig({ - schema: "../../packages/db/src/schema/*.ts", - out: "../../packages/db/migrations", - dialect: "postgresql", - dbCredentials: { - url: env.DATABASE_URL, - }, - verbose: true, - strict: true, + schema: "../../packages/db/src/schema/*.ts", + out: "../../packages/db/migrations", + dialect: "postgresql", + dbCredentials: { url: DATABASE_URL }, + verbose: true, + strict: true, }); diff --git a/apps/backend/package.json b/apps/backend/package.json index 41cddac..e3ed8aa 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,65 +1,5 @@ { - "name": "backend", - "version": "1.0.0", - "license": "MIT", - "type": "module", - "scripts": { - "dev": "tsx watch src/index.ts", - "start": "node ./dist/src/index.js", - "typecheck": "tsc --noEmit", - "lint": "biome check .", - "lint:fix": "biome check --fix .", - "test": "cross-env NODE_ENV=test vitest run", - "build": "swc src --out-dir dist --config-file .swcrc && tsc-alias && cp package.json dist/", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit push", - "db:studio": "drizzle-kit studio", - "jobs": "tsx src/jobs/worker.ts" - }, - "dependencies": { - "@asteasolutions/zod-to-openapi": "8.0.0", - "@aws-sdk/client-s3": "^3.750.0", - "@aws-sdk/s3-request-presigner": "^3.750.0", - "@axiomhq/pino": "^1.4.0", - "@hono/node-server": "^1.19.7", - "@hono/node-ws": "^1.2.0", - "@hono/swagger-ui": "^0.5.3", - "@hono/zod-openapi": "^1.2.0", - "@hono/zod-validator": "^0.7.6", - "@repo/db": "workspace:*", - "@repo/email-templates": "workspace:*", - "@repo/shared": "workspace:*", - "@scalar/hono-api-reference": "^0.9.30", - "better-auth": "^1.4.9", - "bullmq": "^5.34.0", - "dotenv": "^16.6.1", - "dotenv-expand": "^12.0.3", - "drizzle-orm": "^0.44.7", - "drizzle-zod": "0.8.3", - "hono": "^4.11.3", - "hono-pino": "^0.7.2", - "ioredis": "^5.4.1", - "neverthrow": "^8.2.0", - "pg": "^8.16.3", - "pino": "^9.14.0", - "pino-pretty": "^13.1.3", - "postgres": "^3.4.7", - "resend": "^6.6.0", - "stoker": "2.0.1", - "zod": "^4.2.1" - }, - "devDependencies": { - "@biomejs/biome": "2.3.7", - "@swc/cli": "^0.7.9", - "@swc/core": "^1.15.8", - "@types/node": "^22.19.3", - "@types/pg": "^8.16.0", - "@vitest/ui": "^4.0.16", - "cross-env": "^7.0.3", - "drizzle-kit": "^0.31.8", - "tsc-alias": "^1.8.16", - "tsx": "^4.21.0", - "typescript": "^5.9.3", - "vitest": "^4.0.16" - } + "name": "backend", + "type": "module", + "private": true } diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index 723b83d..9657299 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -6,14 +6,14 @@ import configureOpenAPI from "@/lib/configure-open-api"; import createApp from "@/lib/create-app"; import { authMiddleware } from "@/middlewares/auth"; import { wideEventMiddleware } from "@/middlewares/wide-event"; -import { publicRoutes, routes } from "@/routes/index"; +import { publicRoutes, routes } from "@/routes/index.ts"; -const app = createApp(); +const app = await createApp(); const allowedOrigins = new Set([ - "http://localhost:5173", - "http://localhost:5174", - env.FRONTEND_URL, + "http://localhost:5173", + "http://localhost:5174", + env.FRONTEND_URL, ]); // Wide event middleware — must be first so it wraps the entire request lifecycle @@ -21,14 +21,14 @@ app.use("*", wideEventMiddleware); // CORS configuration app.use( - "*", - cors({ - origin: [...allowedOrigins], - allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], - allowHeaders: ["Content-Type", "Authorization"], - exposeHeaders: ["set-auth-token"], - credentials: true, - }), + "*", + cors({ + origin: [...allowedOrigins], + allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], + allowHeaders: ["Content-Type", "Authorization"], + exposeHeaders: ["set-auth-token"], + credentials: true, + }), ); // OpenAPI documentation @@ -39,7 +39,7 @@ app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw)); // Public routes (no auth required) for (const route of publicRoutes) { - app.route("/api", route); + app.route("/api", route); } // Apply auth middleware to all /api/* routes @@ -47,12 +47,12 @@ app.use("/api/*", authMiddleware); // Protected routes for (const route of routes) { - app.route("/api", route); + app.route("/api", route); } // Show routes in development if (env.NODE_ENV === "development") { - showRoutes(app); + showRoutes(app); } export type AppType = (typeof routes)[number]; diff --git a/apps/backend/src/db/migrate.ts b/apps/backend/src/db/migrate.ts index 43e0415..62f6089 100644 --- a/apps/backend/src/db/migrate.ts +++ b/apps/backend/src/db/migrate.ts @@ -1,31 +1,30 @@ -import path from "node:path"; import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; -const DATABASE_URL = process.env.DATABASE_URL; +const DATABASE_URL = Deno.env.get("DATABASE_URL"); if (!DATABASE_URL) { - console.error("Error: DATABASE_URL environment variable is required."); - process.exit(1); + console.error("Error: DATABASE_URL environment variable is required."); + Deno.exit(1); } -// In the Docker image the runner WORKDIR is /app and migrations are copied to /app/migrations. -// Locally, process.cwd() is the workspace root where packages/db/migrations lives — but the -// recommended approach for local dev is `pnpm db:migrate` (drizzle-kit push). -const migrationsFolder = path.join(process.cwd(), "migrations"); +const migrationsFolder = new URL( + "../../../../packages/db/migrations", + import.meta.url, +).pathname; const client = postgres(DATABASE_URL, { max: 1 }); const db = drizzle(client); async function main() { - console.log(`Running migrations from: ${migrationsFolder}`); - await migrate(db, { migrationsFolder }); - console.log("Migrations complete."); + console.log(`Running migrations from: ${migrationsFolder}`); + await migrate(db, { migrationsFolder }); + console.log("Migrations complete."); } main() - .catch((err) => { - console.error("Migration failed:", err); - process.exit(1); - }) - .finally(() => client.end()); + .catch((err) => { + console.error("Migration failed:", err); + Deno.exit(1); + }) + .finally(() => client.end()); diff --git a/apps/backend/src/env.ts b/apps/backend/src/env.ts index 016bb3a..8031926 100644 --- a/apps/backend/src/env.ts +++ b/apps/backend/src/env.ts @@ -3,69 +3,69 @@ import { expand } from "dotenv-expand"; import { z } from "zod"; // Load environment-specific .env file first (e.g. .env.test), then fall back to .env. -const nodeEnv = process.env.NODE_ENV ?? "development"; +const nodeEnv = Deno.env.get("NODE_ENV") ?? "development"; expand(config({ path: `.env.${nodeEnv}`, override: false })); expand(config({ override: false })); const envSchema = z.object({ - NODE_ENV: z - .enum(["development", "staging", "production", "test"]) - .default("development"), - PORT: z.coerce.number().default(9999), - LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), + NODE_ENV: z + .enum(["development", "staging", "production", "test"]) + .default("development"), + PORT: z.coerce.number().default(9999), + LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), - // Database - DATABASE_URL: z.string().min(1, "DATABASE_URL is required"), + // Database + DATABASE_URL: z.string().min(1, "DATABASE_URL is required"), - // Auth - BETTER_AUTH_SECRET: z - .string() - .min(32, "BETTER_AUTH_SECRET must be at least 32 characters"), - BETTER_AUTH_URL: z.string().url().default("http://localhost:9999"), + // Auth + BETTER_AUTH_SECRET: z + .string() + .min(32, "BETTER_AUTH_SECRET must be at least 32 characters"), + BETTER_AUTH_URL: z.url().default("http://localhost:9999"), - // URLs - SERVER_URL: z.string().url().default("http://localhost:9999"), - FRONTEND_URL: z.string().url().default("http://localhost:5173"), + // URLs + SERVER_URL: z.url().default("http://localhost:9999"), + FRONTEND_URL: z.url().default("http://localhost:5173"), - // Redis (optional - for jobs, rate limiting, caching) - REDIS_URL: z.string().optional(), + // Redis (optional - for jobs, rate limiting, caching) + REDIS_URL: z.string().optional(), - // S3/R2 Storage (optional) - S3_ENDPOINT: z.string().optional(), - S3_REGION: z.string().optional(), - S3_BUCKET: z.string().optional(), - S3_ACCESS_KEY_ID: z.string().optional(), - S3_SECRET_ACCESS_KEY: z.string().optional(), + // S3/R2 Storage (optional) + S3_ENDPOINT: z.string().optional(), + S3_REGION: z.string().optional(), + S3_BUCKET: z.string().optional(), + S3_ACCESS_KEY_ID: z.string().optional(), + S3_SECRET_ACCESS_KEY: z.string().optional(), - // Social OAuth (optional — only activate providers whose vars are set) - GOOGLE_CLIENT_ID: z.string().optional(), - GOOGLE_CLIENT_SECRET: z.string().optional(), - GITHUB_CLIENT_ID: z.string().optional(), - GITHUB_CLIENT_SECRET: z.string().optional(), + // Social OAuth (optional — only activate providers whose vars are set) + GOOGLE_CLIENT_ID: z.string().optional(), + GOOGLE_CLIENT_SECRET: z.string().optional(), + GITHUB_CLIENT_ID: z.string().optional(), + GITHUB_CLIENT_SECRET: z.string().optional(), - // Email - RESEND_API_KEY: z.string().optional(), + // Email + RESEND_API_KEY: z.string().optional(), - // Observability (Axiom) - AXIOM_TOKEN: z.string().optional(), - AXIOM_DATASET: z.string().optional(), + // Observability (Axiom) + AXIOM_TOKEN: z.string().optional(), + AXIOM_DATASET: z.string().optional(), - // Deployment metadata (injected by CI as git SHA / docker image tag / region) - SERVICE_VERSION: z.string().default("local"), - DEPLOYMENT_ID: z.string().default("local"), - REGION: z.string().default("local"), + // Deployment metadata (injected by CI as git SHA / docker image tag / region) + SERVICE_VERSION: z.string().default("local"), + DEPLOYMENT_ID: z.string().default("local"), + REGION: z.string().default("local"), }); export type Env = z.infer; function parseEnv(): Env { - const parsed = envSchema.safeParse(process.env); - if (!parsed.success) { - console.error("❌ Invalid environment variables:"); - console.error(parsed.error.flatten().fieldErrors); - process.exit(1); - } - return parsed.data; + const parsed = envSchema.safeParse(Deno.env.toObject()); + if (!parsed.success) { + console.error("Invalid environment variables:"); + console.error(parsed.error.flatten().fieldErrors); + Deno.exit(1); + } + return parsed.data; } const env = parseEnv(); diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index c3e7577..9f09020 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -1,23 +1,23 @@ -import { serve } from "@hono/node-server"; import app from "@/app"; import env from "@/env"; -const server = serve({ - fetch: app.fetch, - port: env.PORT, -}); - console.log(`Server running at http://localhost:${env.PORT}`); -console.log(`📚 API docs at http://localhost:${env.PORT}/docs`); +console.log(`API docs at http://localhost:${env.PORT}/docs`); + +const abortController = new AbortController(); + +Deno.serve( + { port: env.PORT, signal: abortController.signal }, + (req) => app.fetch(req), +); // Graceful shutdown const shutdown = () => { - console.log("\n Shutting down gracefully..."); - server.close(() => { - console.log("✅ Server closed"); - process.exit(0); - }); + console.log("\n Shutting down gracefully..."); + abortController.abort(); + console.log("✅ Server closed"); + Deno.exit(0); }; -process.on("SIGTERM", shutdown); -process.on("SIGINT", shutdown); +Deno.addSignalListener("SIGTERM", shutdown); +Deno.addSignalListener("SIGINT", shutdown); diff --git a/apps/backend/src/jobs/index.ts b/apps/backend/src/jobs/index.ts index 113cde4..e7d731f 100644 --- a/apps/backend/src/jobs/index.ts +++ b/apps/backend/src/jobs/index.ts @@ -5,15 +5,15 @@ import { getRedis } from "@/lib/redis"; export type JobName = "email" | "cleanup" | "sync"; export interface JobData { - email: { to: string; template: string; data: Record }; - cleanup: { olderThanDays: number }; - sync: { userId: string }; + email: { to: string; template: string; data: Record }; + cleanup: { olderThanDays: number }; + sync: { userId: string }; } // Create queues function createQueue(name: T) { - // biome-ignore lint/suspicious/noExplicitAny: BullMQ accepts ioredis instances but types diverge - return new Queue(name, { connection: getRedis() as any }); + // biome-ignore lint/suspicious/noExplicitAny: BullMQ accepts ioredis instances but types diverge + return new Queue(name, { connection: getRedis() as any }); } // Export queues (lazy initialization) @@ -22,41 +22,41 @@ let cleanupQueue: Queue | null = null; let syncQueue: Queue | null = null; export function getEmailQueue(): Queue { - if (!emailQueue) emailQueue = createQueue("email"); - return emailQueue; + if (!emailQueue) emailQueue = createQueue("email"); + return emailQueue; } export function getCleanupQueue(): Queue { - if (!cleanupQueue) cleanupQueue = createQueue("cleanup"); - return cleanupQueue; + if (!cleanupQueue) cleanupQueue = createQueue("cleanup"); + return cleanupQueue; } export function getSyncQueue(): Queue { - if (!syncQueue) syncQueue = createQueue("sync"); - return syncQueue; + if (!syncQueue) syncQueue = createQueue("sync"); + return syncQueue; } // Helper to add jobs export async function addJob( - name: T, - data: JobData[T], - options?: { delay?: number; priority?: number }, + name: T, + data: JobData[T], + options?: { delay?: number; priority?: number }, ) { - const queueMap = { - email: getEmailQueue(), - cleanup: getCleanupQueue(), - sync: getSyncQueue(), - }; - - const queue = queueMap[name]; - - // Queue collapses all generics so .add() accepts (string, data) without ExtractNameType inference errors - // biome-ignore lint/suspicious/noExplicitAny: BullMQ ExtractNameType bug (github.com/taskforcesh/bullmq/issues/3369) - const q = queue as unknown as Queue; - return q.add(name, data, { - delay: options?.delay, - priority: options?.priority, - removeOnComplete: 100, - removeOnFail: 1000, - }); + const queueMap = { + email: getEmailQueue(), + cleanup: getCleanupQueue(), + sync: getSyncQueue(), + }; + + const queue = queueMap[name]; + + // Queue collapses all generics so .add() accepts (string, data) without ExtractNameType inference errors + // biome-ignore lint/suspicious/noExplicitAny: BullMQ ExtractNameType bug (github.com/taskforcesh/bullmq/issues/3369) + const q = queue as unknown as Queue; + return q.add(name, data, { + delay: options?.delay, + priority: options?.priority, + removeOnComplete: 100, + removeOnFail: 1000, + }); } diff --git a/apps/backend/src/jobs/worker.ts b/apps/backend/src/jobs/worker.ts index 4ece22f..5468036 100644 --- a/apps/backend/src/jobs/worker.ts +++ b/apps/backend/src/jobs/worker.ts @@ -1,57 +1,60 @@ import { type Job, Worker } from "bullmq"; import pino from "pino"; -import { getRedis } from "@/lib/redis"; -import type { JobData, JobName } from "./index"; +import { getRedis } from "@/lib/redis.ts"; +import type { JobData, JobName } from "./index.ts"; const logger = pino({ name: "worker" }); const processors: { [K in JobName]: (job: Job) => Promise } = - { - async email(job) { - logger.info( - { to: job.data.to, template: job.data.template }, - "Processing email job", - ); - // TODO: Implement email sending - }, - async cleanup(job) { - logger.info( - { olderThanDays: job.data.olderThanDays }, - "Processing cleanup job", - ); - // TODO: Implement cleanup logic - }, - async sync(job) { - logger.info({ userId: job.data.userId }, "Processing sync job"); - // TODO: Implement sync logic - }, - }; + { + async email(job) { + logger.info( + { to: job.data.to, template: job.data.template }, + "Processing email job", + ); + // TODO: Implement email sending + }, + async cleanup(job) { + logger.info( + { olderThanDays: job.data.olderThanDays }, + "Processing cleanup job", + ); + // TODO: Implement cleanup logic + }, + async sync(job) { + logger.info({ userId: job.data.userId }, "Processing sync job"); + // TODO: Implement sync logic + }, + }; function startWorker(name: T) { - const worker = new Worker( - name, - (job) => processors[name](job as Job), - // biome-ignore lint/suspicious/noExplicitAny: BullMQ accepts ioredis instances but types diverge - { connection: getRedis() as any, concurrency: 5 }, - ); - worker.on("completed", (job) => - logger.info({ jobId: job.id, name }, "Job completed"), - ); - worker.on("failed", (job, err) => - logger.error({ jobId: job?.id, name, error: err.message }, "Job failed"), - ); - return worker; + const worker = new Worker( + name, + (job) => processors[name](job as Job), + // biome-ignore lint/suspicious/noExplicitAny: BullMQ accepts ioredis instances but types diverge + { connection: getRedis() as any, concurrency: 5 }, + ); + worker.on( + "completed", + (job) => logger.info({ jobId: job.id, name }, "Job completed"), + ); + worker.on( + "failed", + (job, err) => + logger.error({ jobId: job?.id, name, error: err.message }, "Job failed"), + ); + return worker; } const workers = (["email", "cleanup", "sync"] as JobName[]).map(startWorker); async function shutdown() { - logger.info("Shutting down workers..."); - await Promise.all(workers.map((w) => w.close())); - process.exit(0); + logger.info("Shutting down workers..."); + await Promise.all(workers.map((w) => w.close())); + Deno.exit(0); } -process.on("SIGTERM", shutdown); -process.on("SIGINT", shutdown); +Deno.addSignalListener("SIGTERM", shutdown); +Deno.addSignalListener("SIGINT", shutdown); logger.info("Workers started"); diff --git a/apps/backend/src/lib/__tests__/result.test.ts b/apps/backend/src/lib/__tests__/result.test.ts index 7cd63d6..37536c1 100644 --- a/apps/backend/src/lib/__tests__/result.test.ts +++ b/apps/backend/src/lib/__tests__/result.test.ts @@ -2,178 +2,179 @@ // Pure functions — no DB, no network, no mocks. import { - andThen, - andThenAsync, - err, - isErr, - isOk, - map, - match, - ok, - unwrap, + andThen, + andThenAsync, + err, + isErr, + isOk, + map, + match, + ok, + unwrap, } from "@repo/shared"; -import { describe, expect, it } from "vitest"; +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; // ─── Constructors ──────────────────────────────────────────────────────────── describe("ok", () => { - it("creates an Ok result", () => { - expect(ok(42)).toEqual({ ok: true, value: 42 }); - }); - - it("wraps any value including null and objects", () => { - expect(ok(null)).toEqual({ ok: true, value: null }); - expect(ok({ id: "1" })).toEqual({ ok: true, value: { id: "1" } }); - }); + it("creates an Ok result", () => { + expect(ok(42)).toEqual({ ok: true, value: 42 }); + }); + + it("wraps any value including null and objects", () => { + expect(ok(null)).toEqual({ ok: true, value: null }); + expect(ok({ id: "1" })).toEqual({ ok: true, value: { id: "1" } }); + }); }); describe("err", () => { - it("creates an Err result", () => { - expect(err("oops")).toEqual({ ok: false, error: "oops" }); - }); - - it("wraps typed error objects", () => { - const e = { type: "NOT_FOUND" as const, lookup: "abc" }; - expect(err(e)).toEqual({ ok: false, error: e }); - }); + it("creates an Err result", () => { + expect(err("oops")).toEqual({ ok: false, error: "oops" }); + }); + + it("wraps typed error objects", () => { + const e = { type: "NOT_FOUND" as const, lookup: "abc" }; + expect(err(e)).toEqual({ ok: false, error: e }); + }); }); // ─── Guards ────────────────────────────────────────────────────────────────── describe("isOk / isErr", () => { - it("isOk returns true for Ok, false for Err", () => { - expect(isOk(ok(1))).toBe(true); - expect(isOk(err("x"))).toBe(false); - }); - - it("isErr returns true for Err, false for Ok", () => { - expect(isErr(err("x"))).toBe(true); - expect(isErr(ok(1))).toBe(false); - }); + it("isOk returns true for Ok, false for Err", () => { + expect(isOk(ok(1))).toBe(true); + expect(isOk(err("x"))).toBe(false); + }); + + it("isErr returns true for Err, false for Ok", () => { + expect(isErr(err("x"))).toBe(true); + expect(isErr(ok(1))).toBe(false); + }); }); // ─── Unwrap ─────────────────────────────────────────────────────────────────── describe("unwrap", () => { - it("returns the value for an Ok result", () => { - expect(unwrap(ok("hello"))).toBe("hello"); - }); + it("returns the value for an Ok result", () => { + expect(unwrap(ok("hello"))).toBe("hello"); + }); - it("throws when called on an Err result", () => { - expect(() => unwrap(err("bad"))).toThrow("Called unwrap on an Err result"); - }); + it("throws when called on an Err result", () => { + expect(() => unwrap(err("bad"))).toThrow("Called unwrap on an Err result"); + }); }); // ─── map ───────────────────────────────────────────────────────────────────── describe("map", () => { - it("transforms the Ok value", () => { - const result = map(ok(2), (n) => n * 3); - expect(result).toEqual(ok(6)); - }); - - it("passes Err through unchanged", () => { - const original = err({ type: "NOT_FOUND" as const, lookup: "x" }); - const result = map(original, (n: number) => n * 3); - expect(result).toEqual(original); - }); - - it("does not call fn on Err", () => { - let called = false; - map(err("e"), () => { - called = true; - return 0; - }); - expect(called).toBe(false); - }); + it("transforms the Ok value", () => { + const result = map(ok(2), (n) => n * 3); + expect(result).toEqual(ok(6)); + }); + + it("passes Err through unchanged", () => { + const original = err({ type: "NOT_FOUND" as const, lookup: "x" }); + const result = map(original, (n: number) => n * 3); + expect(result).toEqual(original); + }); + + it("does not call fn on Err", () => { + let called = false; + map(err("e"), () => { + called = true; + return 0; + }); + expect(called).toBe(false); + }); }); // ─── andThen ───────────────────────────────────────────────────────────────── describe("andThen", () => { - it("chains the function when Ok", () => { - const result = andThen(ok(5), (n) => ok(n + 1)); - expect(result).toEqual(ok(6)); - }); - - it("short-circuits when the input is Err", () => { - const original = err("input failed"); - const result = andThen(original, (n: number) => ok(n + 1)); - expect(result).toEqual(original); - }); - - it("propagates Err returned by the chained function", () => { - const result = andThen(ok(5), () => err("chained failed")); - expect(result).toEqual(err("chained failed")); - }); - - it("does not call fn on Err input", () => { - let called = false; - andThen(err("e"), () => { - called = true; - return ok(0); - }); - expect(called).toBe(false); - }); + it("chains the function when Ok", () => { + const result = andThen(ok(5), (n) => ok(n + 1)); + expect(result).toEqual(ok(6)); + }); + + it("short-circuits when the input is Err", () => { + const original = err("input failed"); + const result = andThen(original, (n: number) => ok(n + 1)); + expect(result).toEqual(original); + }); + + it("propagates Err returned by the chained function", () => { + const result = andThen(ok(5), () => err("chained failed")); + expect(result).toEqual(err("chained failed")); + }); + + it("does not call fn on Err input", () => { + let called = false; + andThen(err("e"), () => { + called = true; + return ok(0); + }); + expect(called).toBe(false); + }); }); // ─── andThenAsync ───────────────────────────────────────────────────────────── describe("andThenAsync", () => { - it("chains the async function when Ok", async () => { - const result = await andThenAsync(ok(5), async (n) => ok(n * 2)); - expect(result).toEqual(ok(10)); - }); - - it("short-circuits when the input is Err", async () => { - const original = err("input failed"); - const result = await andThenAsync(original, async (n: number) => ok(n)); - expect(result).toEqual(original); - }); - - it("propagates Err returned by the async function", async () => { - const result = await andThenAsync(ok("x"), async () => err("async failed")); - expect(result).toEqual(err("async failed")); - }); - - it("does not call fn on Err input", async () => { - let called = false; - await andThenAsync(err("e"), async () => { - called = true; - return ok(0); - }); - expect(called).toBe(false); - }); + it("chains the async function when Ok", async () => { + const result = await andThenAsync(ok(5), async (n) => ok(n * 2)); + expect(result).toEqual(ok(10)); + }); + + it("short-circuits when the input is Err", async () => { + const original = err("input failed"); + const result = await andThenAsync(original, async (n: number) => ok(n)); + expect(result).toEqual(original); + }); + + it("propagates Err returned by the async function", async () => { + const result = await andThenAsync(ok("x"), async () => err("async failed")); + expect(result).toEqual(err("async failed")); + }); + + it("does not call fn on Err input", async () => { + let called = false; + await andThenAsync(err("e"), async () => { + called = true; + return ok(0); + }); + expect(called).toBe(false); + }); }); // ─── match ─────────────────────────────────────────────────────────────────── describe("match", () => { - it("calls the ok handler for Ok results", () => { - const result = match(ok(42), { - ok: (n) => `value is ${n}`, - err: () => "error", - }); - expect(result).toBe("value is 42"); - }); - - it("calls the err handler for Err results", () => { - const result = match(err("bad"), { - ok: () => "ok", - err: (e) => `error: ${e}`, - }); - expect(result).toBe("error: bad"); - }); - - it("ok and err branches can return different types", () => { - // This tests the R1 | R2 generic — both branches compile with distinct return types. - const result = match( - ok(1) as ReturnType> | ReturnType>, - { - ok: (n) => n * 2, // number - err: (e) => e.length, // also number, but from a different source - }, - ); - expect(typeof result).toBe("number"); - }); + it("calls the ok handler for Ok results", () => { + const result = match(ok(42), { + ok: (n) => `value is ${n}`, + err: () => "error", + }); + expect(result).toBe("value is 42"); + }); + + it("calls the err handler for Err results", () => { + const result = match(err("bad"), { + ok: () => "ok", + err: (e) => `error: ${e}`, + }); + expect(result).toBe("error: bad"); + }); + + it("ok and err branches can return different types", () => { + // This tests the R1 | R2 generic — both branches compile with distinct return types. + const result = match( + ok(1) as ReturnType> | ReturnType>, + { + ok: (n) => n * 2, // number + err: (e) => e.length, // also number, but from a different source + }, + ); + expect(typeof result).toBe("number"); + }); }); diff --git a/apps/backend/src/lib/auth.ts b/apps/backend/src/lib/auth.ts index 83dfd3f..da4b994 100644 --- a/apps/backend/src/lib/auth.ts +++ b/apps/backend/src/lib/auth.ts @@ -1,67 +1,155 @@ +import { hash, verify } from "@node-rs/argon2"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { openAPI } from "better-auth/plugins"; -import { db, schema } from "@/db"; -import env from "@/env"; +import { twoFactor } from "better-auth/plugins/two-factor"; +import { db, schema } from "@/db/index.ts"; +import { sendEmail } from "@/lib/email.ts"; +import { redis } from "@/lib/redis.ts"; +import { passwordResetEmail, welcomeEmail } from "@repo/email-templates"; +import type { User as DbUser } from "@repo/db/schema"; +import env from "@/env.ts"; + +const argon2Options = { + memoryCost: 65536, + timeCost: 3, + parallelism: 4, + outputLen: 32, + algorithm: 2, +}; -// Build social providers object — only include a provider when both its -// client ID and secret are present. Adding empty strings would cause silent -// OAuth failures, so we omit the provider entirely when vars are missing. const socialProviders: Parameters[0]["socialProviders"] = { - ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET - ? { - google: { - clientId: env.GOOGLE_CLIENT_ID, - clientSecret: env.GOOGLE_CLIENT_SECRET, - }, - } - : {}), - ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET - ? { - github: { - clientId: env.GITHUB_CLIENT_ID, - clientSecret: env.GITHUB_CLIENT_SECRET, - }, - } - : {}), + ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET + ? { + google: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + } + : {}), + ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET + ? { + github: { + clientId: env.GITHUB_CLIENT_ID, + clientSecret: env.GITHUB_CLIENT_SECRET, + }, + } + : {}), }; +const secondaryStorage = redis + ? { + get: (key: string) => redis!.get(key), + set: async (key: string, value: string, ttl?: number) => { + if (ttl) await redis!.setex(key, ttl, value); + else await redis!.set(key, value); + }, + delete: async (key: string) => { + await redis!.del(key); + }, + } + : undefined; + export const auth = betterAuth({ - plugins: [openAPI()], - socialProviders, - database: drizzleAdapter(db, { - provider: "pg", - // Map better-auth model names to the project's Drizzle table objects. - // better-auth uses singular names (user, session, account, verification) - // while the schema uses plural names (users, sessions, accounts, verifications). - schema: { - user: schema.users, - session: schema.sessions, - account: schema.accounts, - verification: schema.verifications, - }, - }), - secret: env.BETTER_AUTH_SECRET, - baseURL: env.BETTER_AUTH_URL, - trustedOrigins: [env.FRONTEND_URL], - emailAndPassword: { - enabled: true, - autoSignIn: true, - }, - session: { - expiresIn: 60 * 60 * 24 * 7, // 7 days - updateAge: 60 * 60 * 24, // 1 day - }, - user: { - additionalFields: { - role: { - type: "string", - required: false, - defaultValue: "user", - }, - }, - }, + database: drizzleAdapter(db, { + provider: "pg", + schema: { + user: schema.users, + session: schema.sessions, + account: schema.accounts, + verification: schema.verifications, + }, + }), + plugins: [ + openAPI(), + twoFactor({ + issuer: env.BETTER_AUTH_URL + ? new URL(env.BETTER_AUTH_URL).hostname + : "Orcta", + totpOptions: { + digits: 6, + period: 30, + }, + backupCodeOptions: { + amount: 10, + length: 10, + storeBackupCodes: "encrypted", + }, + trustDeviceMaxAge: 30 * 24 * 60 * 60, + twoFactorCookieMaxAge: 600, + }), + ], + socialProviders, + secret: env.BETTER_AUTH_SECRET, + baseURL: env.BETTER_AUTH_URL, + trustedOrigins: [env.FRONTEND_URL], + emailAndPassword: { + enabled: true, + autoSignIn: true, + minPasswordLength: 12, + maxPasswordLength: 256, + revokeSessionsOnPasswordReset: true, + sendResetPassword: async ({ user, url }) => { + const template = passwordResetEmail({ name: user.name, actionUrl: url }); + await sendEmail({ + to: user.email, + subject: template.subject, + html: template.html, + text: template.text, + }); + }, + password: { + hash: (password) => hash(password, argon2Options), + verify: ({ password, hash: storedHash }) => + verify(storedHash, password, argon2Options), + }, + }, + emailVerification: { + sendVerificationEmail: async ({ user, url }) => { + const template = welcomeEmail({ name: user.name, actionUrl: url }); + await sendEmail({ + to: user.email, + subject: template.subject, + html: template.html, + text: template.text, + }); + }, + sendOnSignUp: true, + }, + session: { + expiresIn: 60 * 60 * 24 * 7, + updateAge: 60 * 60 * 24, + cookieCache: { + enabled: true, + maxAge: 60 * 15, + }, + }, + secondaryStorage, + rateLimit: { + enabled: true, + window: 10, + max: 100, + storage: "secondary-storage", + }, + user: { + additionalFields: { + role: { + type: "string", + required: false, + defaultValue: "buyer", + }, + }, + }, + advanced: { + backgroundTasks: { + handler: (promise) => { + promise.catch(console.error); + }, + }, + }, }); -export type Session = typeof auth.$Infer.Session; -export type User = Session["user"]; +type BetterAuthSession = typeof auth.$Infer.Session; +type BetterUser = BetterAuthSession["user"]; +export type Session = BetterAuthSession; +export type User = BetterUser & Pick; diff --git a/apps/backend/src/lib/cache.ts b/apps/backend/src/lib/cache.ts index fcf6ad2..c8fa1da 100644 --- a/apps/backend/src/lib/cache.ts +++ b/apps/backend/src/lib/cache.ts @@ -23,28 +23,28 @@ import { redis } from "@/lib/redis"; * ); */ export async function withCache( - key: string, - ttlSeconds: number, - fn: () => Promise, + key: string, + ttlSeconds: number, + fn: () => Promise, ): Promise { - if (!redis) return fn(); + if (!redis) return fn(); - try { - const cached = await redis.get(key); - if (cached !== null) return JSON.parse(cached) as T; - } catch { - // Redis unavailable — fall through to source of truth - } + try { + const cached = await redis.get(key); + if (cached !== null) return JSON.parse(cached) as T; + } catch { + // Redis unavailable — fall through to source of truth + } - const value = await fn(); + const value = await fn(); - try { - await redis.set(key, JSON.stringify(value), "EX", ttlSeconds); - } catch { - // Best-effort write — never fail the request because the cache is down - } + try { + await redis.set(key, JSON.stringify(value), "EX", ttlSeconds); + } catch { + // Best-effort write — never fail the request because the cache is down + } - return value; + return value; } /** @@ -56,8 +56,8 @@ export async function withCache( * await invalidateCache(cacheKey("post", postId), cacheKey("posts", "list")); */ export async function invalidateCache(...keys: string[]): Promise { - if (!redis || keys.length === 0) return; - await redis.del(...keys); + if (!redis || keys.length === 0) return; + await redis.del(...keys); } /** @@ -69,5 +69,5 @@ export async function invalidateCache(...keys: string[]): Promise { * cacheKey("posts", page, limit) // "posts:1:20" */ export function cacheKey(...parts: (string | number)[]): string { - return parts.join(":"); + return parts.join(":"); } diff --git a/apps/backend/src/lib/configure-open-api.ts b/apps/backend/src/lib/configure-open-api.ts index 3fae757..15a410b 100644 --- a/apps/backend/src/lib/configure-open-api.ts +++ b/apps/backend/src/lib/configure-open-api.ts @@ -1,52 +1,52 @@ import { Scalar } from "@scalar/hono-api-reference"; -import type { AppType } from "./create-app"; +import type { AppType } from "./create-app.ts"; -export default function configureOpenAPI(app: AppType) { - app.doc("/openapi.json", { - openapi: "3.1.0", - info: { - title: "Orcta Stack API", - version: "1.0.0", - description: "API documentation for Orcta Stack", - }, - servers: [ - { - url: "http://localhost:9999", - description: "Local development", - }, - ], - }); +export default function configureOpenAPI(app: Awaited) { + app.doc("/openapi.json", { + openapi: "3.1.0", + info: { + title: "Orcta Stack API", + version: "1.0.0", + description: "API documentation for Orcta Stack", + }, + servers: [ + { + url: "http://localhost:9999", + description: "Local development", + }, + ], + }); - app.get( - "/docs", - Scalar({ - theme: "kepler", - layout: "modern", - defaultHttpClient: { - targetKey: "js", - clientKey: "fetch", - }, - url: "/openapi.json", - sources: [ - { - title: "Backend API", - url: "/openapi.json", - }, - { url: "/api/auth/open-api/generate-schema", title: "Auth" }, - ], - title: "Orcta Stack API Reference", - metaData: { - title: "Orcta Stack", - description: "Interactive API documentation for Orcta Stack", - ogDescription: "Explore and test the Orcta Stack API endpoints", - ogTitle: "Orcta Stack API Documentation", - twitterCard: "summary_large_image", - }, - searchHotKey: "k", - showSidebar: true, - hideModels: false, - hideDownloadButton: false, - hideDarkModeToggle: false, - }), - ); + app.get( + "/docs", + Scalar({ + theme: "kepler", + layout: "modern", + defaultHttpClient: { + targetKey: "js", + clientKey: "fetch", + }, + url: "/openapi.json", + sources: [ + { + title: "Backend API", + url: "/openapi.json", + }, + { url: "/api/auth/open-api/generate-schema", title: "Auth" }, + ], + title: "Orcta Stack API Reference", + metaData: { + title: "Orcta Stack", + description: "Interactive API documentation for Orcta Stack", + ogDescription: "Explore and test the Orcta Stack API endpoints", + ogTitle: "Orcta Stack API Documentation", + twitterCard: "summary_large_image", + }, + searchHotKey: "k", + showSidebar: true, + hideModels: false, + hideDownloadButton: false, + hideDarkModeToggle: false, + }), + ); } diff --git a/apps/backend/src/lib/create-app.ts b/apps/backend/src/lib/create-app.ts index c79b6e7..abce5a5 100644 --- a/apps/backend/src/lib/create-app.ts +++ b/apps/backend/src/lib/create-app.ts @@ -3,70 +3,72 @@ import { pinoLogger } from "hono-pino"; import pino, { multistream } from "pino"; import { notFound, onError } from "stoker/middlewares"; import { defaultHook } from "stoker/openapi"; -import env from "@/env"; -import type { Session } from "./auth"; -import type { WideEvent } from "./types"; +import env from "@/env.ts"; +import type { Session, User } from "./auth.ts"; +import type { WideEvent } from "./types.ts"; export type AppEnv = { - Variables: { - user: Session["user"]; - session: Session["session"]; - wideEvent: WideEvent; - }; + Variables: { + user: User; + session: Session["session"]; + wideEvent: WideEvent; + }; }; export function createRouter() { - return new OpenAPIHono({ - defaultHook, - }); + return new OpenAPIHono({ + defaultHook, + }); } -function buildPinoInstance() { - if (env.NODE_ENV === "development") { - return pino({ - level: env.LOG_LEVEL, - transport: { target: "pino-pretty", options: { colorize: true } }, - }); - } +async function buildPinoInstance() { + if (env.NODE_ENV === "development") { + return pino({ + level: env.LOG_LEVEL, + transport: { target: "pino-pretty", options: { colorize: true } }, + }); + } - const streams: pino.StreamEntry[] = [ - { level: env.LOG_LEVEL, stream: process.stdout }, - ]; + const stdoutStream = { + write: (msg: string) => Deno.stdout.write(new TextEncoder().encode(msg)), + }; - if (env.AXIOM_TOKEN && env.AXIOM_DATASET) { - // Dynamic import keeps the Axiom transport out of dev/test bundles - // eslint-disable-next-line @typescript-eslint/no-require-imports - const { createWriteStream } = require("@axiomhq/pino"); - streams.push({ - level: "info", - stream: createWriteStream({ - dataset: env.AXIOM_DATASET, - token: env.AXIOM_TOKEN, - }), - }); - } + const streams: pino.StreamEntry[] = [ + { level: env.LOG_LEVEL, stream: stdoutStream }, + ]; - return pino({ level: env.LOG_LEVEL }, multistream(streams)); + if (env.AXIOM_TOKEN && env.AXIOM_DATASET) { + const { default: createAxiomStream } = await import("@axiomhq/pino"); + streams.push({ + level: "info", + stream: await createAxiomStream({ + dataset: env.AXIOM_DATASET, + token: env.AXIOM_TOKEN, + }), + }); + } + + return pino({ level: env.LOG_LEVEL }, multistream(streams)); } -export default function createApp() { - const app = createRouter(); +export default async function createApp() { + const app = createRouter(); - // Logging — hono-pino binds a per-request logger to context; wide-event - // middleware uses this logger to emit the single canonical event. - app.use( - pinoLogger({ - pino: buildPinoInstance(), - // Suppress hono-pino's own per-request log — we emit the wide event instead - http: false, - }), - ); + // Logging — hono-pino binds a per-request logger to context; wide-event + // middleware uses this logger to emit the single canonical event. + app.use( + pinoLogger({ + pino: await buildPinoInstance(), + // Suppress hono-pino's own per-request log — we emit the wide event instead + http: false, + }), + ); - // Error handling - app.onError(onError); - app.notFound(notFound); + // Error handling + app.onError(onError); + app.notFound(notFound); - return app; + return app; } -export type AppType = ReturnType; +export type AppType = Awaited>; diff --git a/apps/backend/src/lib/email.ts b/apps/backend/src/lib/email.ts new file mode 100644 index 0000000..bb0fb3f --- /dev/null +++ b/apps/backend/src/lib/email.ts @@ -0,0 +1,30 @@ +import { Resend } from "resend"; +import env from "@/env.ts"; + +const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null; + +export async function sendEmail(options: { + to: string; + subject: string; + html: string; + text: string; +}) { + if (!resend) { + console.log("[email] no RESEND_API_KEY configured, logging instead"); + console.log("[email] to:", options.to); + console.log("[email] subject:", options.subject); + return; + } + + const { error } = await resend.emails.send({ + from: `Orcta `, + replyTo: "noreply@orctatech.com", + ...options, + }); + + if (error) { + console.error("[email] failed to send:", error); + } +} diff --git a/apps/backend/src/lib/error.ts b/apps/backend/src/lib/error.ts index ac36abc..7b5750e 100644 --- a/apps/backend/src/lib/error.ts +++ b/apps/backend/src/lib/error.ts @@ -1,42 +1,42 @@ // AppError — for known, expected failures that map to HTTP responses. // Use in handlers when you need to communicate a specific error to the client. export class AppError extends Error { - constructor( - public readonly code: string, - message: string, - public readonly statusCode: number = 500, - public readonly details?: Record, - ) { - super(message); - this.name = "AppError"; - } + constructor( + public readonly code: string, + message: string, + public readonly statusCode: number = 500, + public readonly details?: Record, + ) { + super(message); + this.name = "AppError"; + } - static badRequest(message: string, details?: Record) { - return new AppError("BAD_REQUEST", message, 400, details); - } - static unauthorized(message = "Unauthorized") { - return new AppError("UNAUTHORIZED", message, 401); - } - static forbidden(message = "Forbidden") { - return new AppError("FORBIDDEN", message, 403); - } - static notFound(message = "Not found") { - return new AppError("NOT_FOUND", message, 404); - } - static conflict(message: string) { - return new AppError("CONFLICT", message, 409); - } - static internal(message = "Internal server error") { - return new AppError("INTERNAL_ERROR", message, 500); - } + static badRequest(message: string, details?: Record) { + return new AppError("BAD_REQUEST", message, 400, details); + } + static unauthorized(message = "Unauthorized") { + return new AppError("UNAUTHORIZED", message, 401); + } + static forbidden(message = "Forbidden") { + return new AppError("FORBIDDEN", message, 403); + } + static notFound(message = "Not found") { + return new AppError("NOT_FOUND", message, 404); + } + static conflict(message: string) { + return new AppError("CONFLICT", message, 409); + } + static internal(message = "Internal server error") { + return new AppError("INTERNAL_ERROR", message, 500); + } - toJSON() { - return { - code: this.code, - message: this.message, - ...(this.details && { details: this.details }), - }; - } + toJSON() { + return { + code: this.code, + message: this.message, + ...(this.details && { details: this.details }), + }; + } } // InfrastructureError — wraps throws from DB, Redis, storage, external APIs. @@ -44,11 +44,11 @@ export class AppError extends Error { // Handlers that receive one as a result return 500; they don't re-throw. // The global onError only sees these if they escape (a bug). export class InfrastructureError extends Error { - constructor( - message: string, - public readonly cause?: unknown, - ) { - super(message); - this.name = "InfrastructureError"; - } + constructor( + message: string, + public override readonly cause?: unknown, + ) { + super(message); + this.name = "InfrastructureError"; + } } diff --git a/apps/backend/src/lib/http-status-phrases.ts b/apps/backend/src/lib/http-status-phrases.ts index 1d3a655..8266701 100644 --- a/apps/backend/src/lib/http-status-phrases.ts +++ b/apps/backend/src/lib/http-status-phrases.ts @@ -151,7 +151,7 @@ export const MULTIPLE_CHOICES = "Multiple Choices"; * The 511 status code indicates that the client needs to authenticate to gain network access. */ export const NETWORK_AUTHENTICATION_REQUIRED = - "Network Authentication Required"; + "Network Authentication Required"; /** * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5 * @@ -258,7 +258,7 @@ export const PROXY_AUTHENTICATION_REQUIRED = "Proxy Authentication Required"; * The server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields. */ export const REQUEST_HEADER_FIELDS_TOO_LARGE = - "Request Header Fields Too Large"; + "Request Header Fields Too Large"; /** * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7 * @@ -283,7 +283,7 @@ export const REQUEST_URI_TOO_LONG = "Request-URI Too Long"; * The range specified by the Range header field in the request can't be fulfilled; it's possible that the range is outside the size of the target URI's data. */ export const REQUESTED_RANGE_NOT_SATISFIABLE = - "Requested Range Not Satisfiable"; + "Requested Range Not Satisfiable"; /** * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.6 * diff --git a/apps/backend/src/lib/infra.ts b/apps/backend/src/lib/infra.ts index 4cb08da..896ae05 100644 --- a/apps/backend/src/lib/infra.ts +++ b/apps/backend/src/lib/infra.ts @@ -1,16 +1,16 @@ import type { Result } from "@repo/shared"; import { err, ok } from "@repo/shared"; -import { InfrastructureError } from "./error"; +import { InfrastructureError } from "./error.ts"; // The single catch boundary for all repository operations. // Wrap every DB/Redis/storage call in this — never catch anywhere else in a repository. export async function tryInfra( - message: string, - fn: () => Promise, + message: string, + fn: () => Promise, ): Promise> { - try { - return ok(await fn()); - } catch (cause) { - return err(new InfrastructureError(message, cause)); - } + try { + return ok(await fn()); + } catch (cause) { + return err(new InfrastructureError(message, cause)); + } } diff --git a/apps/backend/src/lib/rate-limit.ts b/apps/backend/src/lib/rate-limit.ts index 0e2271e..dba8c0c 100644 --- a/apps/backend/src/lib/rate-limit.ts +++ b/apps/backend/src/lib/rate-limit.ts @@ -1,67 +1,67 @@ import type { Context, Next } from "hono"; interface RateLimitOptions { - windowMs?: number; // Time window in ms (default: 60000 = 1 min) - max?: number; // Max requests per window (default: 100) - keyGenerator?: (c: Context) => string; - handler?: (c: Context) => Response; + windowMs?: number; // Time window in ms (default: 60000 = 1 min) + max?: number; // Max requests per window (default: 100) + keyGenerator?: (c: Context) => string; + handler?: (c: Context) => Response; } interface RateLimitEntry { - count: number; - resetAt: number; + count: number; + resetAt: number; } const store = new Map(); // Cleanup old entries periodically setInterval(() => { - const now = Date.now(); - for (const [key, entry] of store) { - if (entry.resetAt < now) store.delete(key); - } + const now = Date.now(); + for (const [key, entry] of store) { + if (entry.resetAt < now) store.delete(key); + } }, 60000); export function rateLimit(options: RateLimitOptions = {}) { - const { - windowMs = 60000, - max = 100, - keyGenerator = (c) => - c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || "unknown", - handler = (c) => - c.json( - { - success: false, - error: { code: "RATE_LIMITED", message: "Too many requests" }, - }, - 429, - ), - } = options; + const { + windowMs = 60000, + max = 100, + keyGenerator = (c) => + c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || "unknown", + handler = (c) => + c.json( + { + success: false, + error: { code: "RATE_LIMITED", message: "Too many requests" }, + }, + 429, + ), + } = options; - return async (c: Context, next: Next) => { - const key = keyGenerator(c); - const now = Date.now(); - const entry = store.get(key); + return async (c: Context, next: Next) => { + const key = keyGenerator(c); + const now = Date.now(); + const entry = store.get(key); - if (!entry || entry.resetAt < now) { - store.set(key, { count: 1, resetAt: now + windowMs }); - c.header("X-RateLimit-Limit", String(max)); - c.header("X-RateLimit-Remaining", String(max - 1)); - return next(); - } + if (!entry || entry.resetAt < now) { + store.set(key, { count: 1, resetAt: now + windowMs }); + c.header("X-RateLimit-Limit", String(max)); + c.header("X-RateLimit-Remaining", String(max - 1)); + return next(); + } - if (entry.count >= max) { - c.header("X-RateLimit-Limit", String(max)); - c.header("X-RateLimit-Remaining", "0"); - c.header("Retry-After", String(Math.ceil((entry.resetAt - now) / 1000))); - return handler(c); - } + if (entry.count >= max) { + c.header("X-RateLimit-Limit", String(max)); + c.header("X-RateLimit-Remaining", "0"); + c.header("Retry-After", String(Math.ceil((entry.resetAt - now) / 1000))); + return handler(c); + } - entry.count++; - c.header("X-RateLimit-Limit", String(max)); - c.header("X-RateLimit-Remaining", String(max - entry.count)); - return next(); - }; + entry.count++; + c.header("X-RateLimit-Limit", String(max)); + c.header("X-RateLimit-Remaining", String(max - entry.count)); + return next(); + }; } // Presets diff --git a/apps/backend/src/lib/redis.ts b/apps/backend/src/lib/redis.ts index 2f66e37..89a55b4 100644 --- a/apps/backend/src/lib/redis.ts +++ b/apps/backend/src/lib/redis.ts @@ -1,13 +1,13 @@ -import Redis from "ioredis"; +import { Redis } from "ioredis"; import env from "@/env"; export const redis = env.REDIS_URL - ? new Redis(env.REDIS_URL, { maxRetriesPerRequest: null }) - : null; + ? new Redis(env.REDIS_URL, { maxRetriesPerRequest: null }) + : null; export function getRedis(): Redis { - if (!redis) { - throw new Error("REDIS_URL not configured"); - } - return redis; + if (!redis) { + throw new Error("REDIS_URL not configured"); + } + return redis; } diff --git a/apps/backend/src/lib/storage.ts b/apps/backend/src/lib/storage.ts index 48eada9..a46379b 100644 --- a/apps/backend/src/lib/storage.ts +++ b/apps/backend/src/lib/storage.ts @@ -1,83 +1,83 @@ import { - DeleteObjectCommand, - GetObjectCommand, - PutObjectCommand, - S3Client, + DeleteObjectCommand, + GetObjectCommand, + PutObjectCommand, + S3Client, } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import env from "@/env"; // Works with AWS S3, Cloudflare R2, MinIO, etc. const s3 = new S3Client({ - region: env.S3_REGION || "auto", - endpoint: env.S3_ENDPOINT, - credentials: env.S3_ACCESS_KEY_ID - ? { - accessKeyId: env.S3_ACCESS_KEY_ID, - secretAccessKey: env.S3_SECRET_ACCESS_KEY ?? "", - } - : undefined, + region: env.S3_REGION || "auto", + endpoint: env.S3_ENDPOINT, + credentials: env.S3_ACCESS_KEY_ID + ? { + accessKeyId: env.S3_ACCESS_KEY_ID, + secretAccessKey: env.S3_SECRET_ACCESS_KEY ?? "", + } + : undefined, }); const BUCKET = env.S3_BUCKET || "uploads"; interface UploadOptions { - key: string; - contentType?: string; - expiresIn?: number; // seconds, default 3600 + key: string; + contentType?: string; + expiresIn?: number; // seconds, default 3600 } interface DownloadOptions { - key: string; - expiresIn?: number; + key: string; + expiresIn?: number; } /** * Generate presigned URL for uploading */ export async function getUploadUrl({ - key, - contentType, - expiresIn = 3600, + key, + contentType, + expiresIn = 3600, }: UploadOptions): Promise { - const command = new PutObjectCommand({ - Bucket: BUCKET, - Key: key, - ContentType: contentType, - }); - return getSignedUrl(s3, command, { expiresIn }); + const command = new PutObjectCommand({ + Bucket: BUCKET, + Key: key, + ContentType: contentType, + }); + return getSignedUrl(s3, command, { expiresIn }); } /** * Generate presigned URL for downloading */ export async function getDownloadUrl({ - key, - expiresIn = 3600, + key, + expiresIn = 3600, }: DownloadOptions): Promise { - const command = new GetObjectCommand({ - Bucket: BUCKET, - Key: key, - }); - return getSignedUrl(s3, command, { expiresIn }); + const command = new GetObjectCommand({ + Bucket: BUCKET, + Key: key, + }); + return getSignedUrl(s3, command, { expiresIn }); } /** * Delete a file */ export async function deleteFile(key: string): Promise { - const command = new DeleteObjectCommand({ - Bucket: BUCKET, - Key: key, - }); - await s3.send(command); + const command = new DeleteObjectCommand({ + Bucket: BUCKET, + Key: key, + }); + await s3.send(command); } /** * Generate a unique file key */ export function generateKey(filename: string, prefix = "uploads"): string { - const ext = filename.split(".").pop() || ""; - const id = crypto.randomUUID(); - return `${prefix}/${id}${ext ? `.${ext}` : ""}`; + const ext = filename.split(".").pop() || ""; + const id = crypto.randomUUID(); + return `${prefix}/${id}${ext ? `.${ext}` : ""}`; } diff --git a/apps/backend/src/lib/types.ts b/apps/backend/src/lib/types.ts index f44058f..60d63a5 100644 --- a/apps/backend/src/lib/types.ts +++ b/apps/backend/src/lib/types.ts @@ -2,19 +2,19 @@ import type { RouteConfig, RouteHandler } from "@hono/zod-openapi"; import type { ApiError, ApiSuccess } from "@repo/shared"; import type { Context } from "hono"; import type { ZodType } from "zod"; -import type { AppEnv } from "./create-app"; -import { InfrastructureError } from "./error"; +import type { AppEnv } from "./create-app.ts"; +import { InfrastructureError } from "./error.ts"; /** * Shared API response types re-exported for handler convenience. */ export type { - ApiError, - ApiResponse, - ApiSuccess, - Err, - Ok, - Result, + ApiError, + ApiResponse, + ApiSuccess, + Err, + Ok, + Result, } from "@repo/shared"; /** @@ -29,19 +29,19 @@ export { err, isErr, isOk, ok } from "@repo/shared"; * from a single module. */ export { - BAD_REQUEST, - CONFLICT, - CREATED, - FORBIDDEN, - INTERNAL_SERVER_ERROR, - NO_CONTENT, - NOT_FOUND, - OK, - SERVICE_UNAVAILABLE, - TOO_MANY_REQUESTS, - UNAUTHORIZED, - UNPROCESSABLE_ENTITY, -} from "./http-status-codes"; + BAD_REQUEST, + CONFLICT, + CREATED, + FORBIDDEN, + INTERNAL_SERVER_ERROR, + NO_CONTENT, + NOT_FOUND, + OK, + SERVICE_UNAVAILABLE, + TOO_MANY_REQUESTS, + UNAUTHORIZED, + UNPROCESSABLE_ENTITY, +} from "./http-status-codes.ts"; /** * Strongly typed route handler bound to the application environment. @@ -60,10 +60,10 @@ export type AppRouteHandler = RouteHandler; * 200: jsonRes(userSchema, "User retrieved") */ export function jsonRes(schema: S, description: string) { - return { - content: { "application/json": { schema } }, - description, - } as const; + return { + content: { "application/json": { schema } }, + description, + } as const; } /** @@ -75,9 +75,9 @@ export function jsonRes(schema: S, description: string) { * body: jsonBody(createUserSchema) */ export function jsonBody(schema: S) { - return { - content: { "application/json": { schema } }, - } as const; + return { + content: { "application/json": { schema } }, + } as const; } /** @@ -86,7 +86,7 @@ export function jsonBody(schema: S) { * @param data - Response payload */ export function success(data: T): ApiSuccess { - return { success: true, data }; + return { success: true, data }; } /** @@ -95,11 +95,11 @@ export function success(data: T): ApiSuccess { * @param error - Machine-readable error payload */ export function failure(error: { - code: string; - message: string; - details?: Record; + code: string; + message: string; + details?: Record; }): ApiError { - return { success: false, error }; + return { success: false, error }; } /** @@ -109,7 +109,7 @@ export function failure(error: { * from domain/business rule errors. */ export const isInfraError = (e: unknown): e is InfrastructureError => - e instanceof InfrastructureError; + e instanceof InfrastructureError; /** * WideEvent @@ -121,82 +121,82 @@ export const isInfraError = (e: unknown): e is InfrastructureError => * wide-event middleware. */ export type WideEvent = { - /** Unique request identifier */ - request_id?: string; - - /** - * Trace identifier for cross-service correlation. - * - * Forwarded from `x-trace-id` when present, otherwise defaults - * to `request_id`. - */ - trace_id?: string; - - /** ISO timestamp */ - timestamp?: string; - - /** HTTP method */ - method?: string; - - /** Request path */ - path?: string; - - /** Final HTTP status code */ - status_code?: number; - - /** Request duration in milliseconds */ - duration_ms?: number; - - /** Request outcome classification */ - outcome?: "success" | "error"; - - /** Logical service name */ - service?: string; - - /** Code/service version */ - service_version?: string; - - /** - * Deployment identifier. - * - * Typically a Docker image tag or Git SHA. Distinct from - * `service_version`. - */ - deployment_id?: string; - - /** Execution region */ - region?: string; - - /** Client IP address */ - ip?: string; - - /** Client user agent */ - user_agent?: string; - - /** Session identifier */ - session_id?: string; - - /** Authenticated user context */ - user?: { - id: string; - role: string; - [k: string]: unknown; - }; - - /** Error metadata */ - error?: { - type?: string; - message?: string; - code?: string; - retriable?: boolean; - [k: string]: unknown; - }; - - /** Feature flag snapshot */ - feature_flags?: Record; - - /** Arbitrary handler-defined fields */ - [key: string]: unknown; + /** Unique request identifier */ + request_id?: string; + + /** + * Trace identifier for cross-service correlation. + * + * Forwarded from `x-trace-id` when present, otherwise defaults + * to `request_id`. + */ + trace_id?: string; + + /** ISO timestamp */ + timestamp?: string; + + /** HTTP method */ + method?: string; + + /** Request path */ + path?: string; + + /** Final HTTP status code */ + status_code?: number; + + /** Request duration in milliseconds */ + duration_ms?: number; + + /** Request outcome classification */ + outcome?: "success" | "error"; + + /** Logical service name */ + service?: string; + + /** Code/service version */ + service_version?: string; + + /** + * Deployment identifier. + * + * Typically a Docker image tag or Git SHA. Distinct from + * `service_version`. + */ + deployment_id?: string; + + /** Execution region */ + region?: string; + + /** Client IP address */ + ip?: string; + + /** Client user agent */ + user_agent?: string; + + /** Session identifier */ + session_id?: string; + + /** Authenticated user context */ + user?: { + id: string; + role: string; + [k: string]: unknown; + }; + + /** Error metadata */ + error?: { + type?: string; + message?: string; + code?: string; + retriable?: boolean; + [k: string]: unknown; + }; + + /** Feature flag snapshot */ + feature_flags?: Record; + + /** Arbitrary handler-defined fields */ + [key: string]: unknown; }; /** @@ -209,9 +209,9 @@ export type WideEvent = { * @param fields - Partial event fields to merge */ export function addToEvent( - c: Context, - fields: Partial, + c: Context, + fields: Partial, ): void { - const event = c.get("wideEvent"); - if (event) Object.assign(event, fields); + const event = c.get("wideEvent"); + if (event) Object.assign(event, fields); } diff --git a/apps/backend/src/lib/ws.ts b/apps/backend/src/lib/ws.ts index 18c06d8..51d9ed9 100644 --- a/apps/backend/src/lib/ws.ts +++ b/apps/backend/src/lib/ws.ts @@ -1,78 +1,78 @@ import type { WSContext } from "hono/ws"; type Connection = { - ws: WSContext; - userId?: string; - rooms: Set; + ws: WSContext; + userId?: string; + rooms: Set; }; class WebSocketManager { - private connections = new Map(); + private connections = new Map(); - add(id: string, ws: WSContext, userId?: string): void { - this.connections.set(id, { ws, userId, rooms: new Set() }); - } + add(id: string, ws: WSContext, userId?: string): void { + this.connections.set(id, { ws, userId, rooms: new Set() }); + } - remove(id: string): void { - this.connections.delete(id); - } + remove(id: string): void { + this.connections.delete(id); + } - get(id: string): Connection | undefined { - return this.connections.get(id); - } + get(id: string): Connection | undefined { + return this.connections.get(id); + } - // Join a room - join(id: string, room: string): void { - const conn = this.connections.get(id); - if (conn) conn.rooms.add(room); - } + // Join a room + join(id: string, room: string): void { + const conn = this.connections.get(id); + if (conn) conn.rooms.add(room); + } - // Leave a room - leave(id: string, room: string): void { - const conn = this.connections.get(id); - if (conn) conn.rooms.delete(room); - } + // Leave a room + leave(id: string, room: string): void { + const conn = this.connections.get(id); + if (conn) conn.rooms.delete(room); + } - // Send to specific connection - send(id: string, data: unknown): void { - const conn = this.connections.get(id); - if (conn) conn.ws.send(JSON.stringify(data)); - } + // Send to specific connection + send(id: string, data: unknown): void { + const conn = this.connections.get(id); + if (conn) conn.ws.send(JSON.stringify(data)); + } - // Send to all connections in a room - broadcast(room: string, data: unknown, excludeId?: string): void { - const message = JSON.stringify(data); - for (const [id, conn] of this.connections) { - if (conn.rooms.has(room) && id !== excludeId) { - conn.ws.send(message); - } - } - } + // Send to all connections in a room + broadcast(room: string, data: unknown, excludeId?: string): void { + const message = JSON.stringify(data); + for (const [id, conn] of this.connections) { + if (conn.rooms.has(room) && id !== excludeId) { + conn.ws.send(message); + } + } + } - // Send to a specific user (all their connections) - sendToUser(userId: string, data: unknown): void { - const message = JSON.stringify(data); - for (const conn of this.connections.values()) { - if (conn.userId === userId) { - conn.ws.send(message); - } - } - } + // Send to a specific user (all their connections) + sendToUser(userId: string, data: unknown): void { + const message = JSON.stringify(data); + for (const conn of this.connections.values()) { + if (conn.userId === userId) { + conn.ws.send(message); + } + } + } - // Broadcast to all connections - broadcastAll(data: unknown, excludeId?: string): void { - const message = JSON.stringify(data); - for (const [id, conn] of this.connections) { - if (id !== excludeId) { - conn.ws.send(message); - } - } - } + // Broadcast to all connections + broadcastAll(data: unknown, excludeId?: string): void { + const message = JSON.stringify(data); + for (const [id, conn] of this.connections) { + if (id !== excludeId) { + conn.ws.send(message); + } + } + } - // Get connection count - get size(): number { - return this.connections.size; - } + // Get connection count + get size(): number { + return this.connections.size; + } } export const wsManager = new WebSocketManager(); diff --git a/apps/backend/src/middlewares/auth.ts b/apps/backend/src/middlewares/auth.ts index 1ad7529..c98b0e9 100644 --- a/apps/backend/src/middlewares/auth.ts +++ b/apps/backend/src/middlewares/auth.ts @@ -1,62 +1,62 @@ import type { Context, Next } from "hono"; -import { auth } from "@/lib/auth"; +import { auth, type User } from "@/lib/auth"; import type { AppEnv } from "@/lib/create-app"; import { addToEvent, UNAUTHORIZED } from "@/lib/types"; export async function authMiddleware(c: Context, next: Next) { - const session = await auth.api.getSession({ - headers: c.req.raw.headers, - }); - - if (!session) { - return c.json( - { - success: false, - error: { code: "UNAUTHORIZED", message: "Authentication required" }, - }, - UNAUTHORIZED, - ); - } - - c.set("user", session.user); - c.set("session", session.session); - - // Enrich the wide event with auth context so every authenticated request - // carries user identity and session ID without handlers doing it manually. - // session_id is distinct from user_id: one user can have many concurrent - // sessions across devices, making it a separate high-cardinality dimension. - addToEvent(c, { - session_id: session.session.id, - user: { id: session.user.id, role: session.user.role ?? "user" }, - }); - - await next(); + const session = await auth.api.getSession({ + headers: c.req.raw.headers, + }); + + if (!session) { + return c.json( + { + success: false, + error: { code: "UNAUTHORIZED", message: "Authentication required" }, + }, + UNAUTHORIZED, + ); + } + + c.set("user", session.user as User); + c.set("session", session.session); + + // Enrich the wide event with auth context so every authenticated request + // carries user identity and session ID without handlers doing it manually. + // session_id is distinct from user_id: one user can have many concurrent + // sessions across devices, making it a separate high-cardinality dimension. + addToEvent(c, { + session_id: session.session.id, + user: { id: session.user.id, role: session.user.role ?? "buyer" }, + }); + + await next(); } export function requireRole(...roles: string[]) { - return async (c: Context, next: Next) => { - const user = c.get("user"); - - if (!user) { - return c.json( - { - success: false, - error: { code: "UNAUTHORIZED", message: "Authentication required" }, - }, - UNAUTHORIZED, - ); - } - - if (!roles.includes(user.role ?? "user")) { - return c.json( - { - success: false, - error: { code: "FORBIDDEN", message: "Insufficient permissions" }, - }, - 403, - ); - } - - await next(); - }; + return async (c: Context, next: Next) => { + const user = c.get("user"); + + if (!user) { + return c.json( + { + success: false, + error: { code: "UNAUTHORIZED", message: "Authentication required" }, + }, + UNAUTHORIZED, + ); + } + + if (!roles.includes(user.role ?? "buyer")) { + return c.json( + { + success: false, + error: { code: "FORBIDDEN", message: "Insufficient permissions" }, + }, + 403, + ); + } + + await next(); + }; } diff --git a/apps/backend/src/middlewares/wide-event.ts b/apps/backend/src/middlewares/wide-event.ts index 41043d0..2658dda 100644 --- a/apps/backend/src/middlewares/wide-event.ts +++ b/apps/backend/src/middlewares/wide-event.ts @@ -12,18 +12,19 @@ import type { WideEvent } from "@/lib/types"; // - Always keep: 5xx responses, any error field, requests > 2s, admin users // - Randomly sample 5% of everything else function shouldSample(event: WideEvent): boolean { - // Never drop errors or slow requests — these are the events that matter most. - if ((event.status_code ?? 0) >= 500) return true; - if (event.outcome === "error") return true; - if ((event.duration_ms ?? 0) > 2000) return true; - // Never drop admin users — low volume, high signal. - if (event.user?.role === "admin") return true; - // Never drop requests annotated with feature_flags — critical during rollouts. - // Handlers set this via: addToEvent(c, { feature_flags: { flag_name: true } }) - if (event.feature_flags && Object.keys(event.feature_flags).length > 0) - return true; - // Random sample 5% of happy, fast, normal requests. - return Math.random() < 0.05; + // Never drop errors or slow requests — these are the events that matter most. + if ((event.status_code ?? 0) >= 500) return true; + if (event.outcome === "error") return true; + if ((event.duration_ms ?? 0) > 2000) return true; + // Never drop admin users — low volume, high signal. + if (event.user?.role === "admin") return true; + // Never drop requests annotated with feature_flags — critical during rollouts. + // Handlers set this via: addToEvent(c, { feature_flags: { flag_name: true } }) + if (event.feature_flags && Object.keys(event.feature_flags).length > 0) { + return true; + } + // Random sample 5% of happy, fast, normal requests. + return Math.random() < 0.05; } // ─── Wide Event Middleware ──────────────────────────────────────────────────── @@ -39,45 +40,45 @@ function shouldSample(event: WideEvent): boolean { // hono-pino's own per-request log is suppressed in create-app.ts so this is // the only log emitted per request. export async function wideEventMiddleware(c: Context, next: Next) { - const startTime = Date.now(); + const startTime = Date.now(); - const event: WideEvent = { - timestamp: new Date().toISOString(), - request_id: crypto.randomUUID(), - // Propagate trace_id from upstream (gateway/client) if present so events - // across services can be correlated by the same ID without a full OTel setup. - // Falls back to a new UUID so every event always has a trace_id. - trace_id: c.req.header("x-trace-id") ?? crypto.randomUUID(), - method: c.req.method, - path: c.req.path, - ip: - c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip") ?? "unknown", - user_agent: c.req.header("user-agent"), - service: "backend", - service_version: env.SERVICE_VERSION, - deployment_id: env.DEPLOYMENT_ID, - region: env.REGION, - }; + const event: WideEvent = { + timestamp: new Date().toISOString(), + request_id: crypto.randomUUID(), + // Propagate trace_id from upstream (gateway/client) if present so events + // across services can be correlated by the same ID without a full OTel setup. + // Falls back to a new UUID so every event always has a trace_id. + trace_id: c.req.header("x-trace-id") ?? crypto.randomUUID(), + method: c.req.method, + path: c.req.path, + ip: c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip") ?? + "unknown", + user_agent: c.req.header("user-agent"), + service: "backend", + service_version: env.SERVICE_VERSION, + deployment_id: env.DEPLOYMENT_ID, + region: env.REGION, + }; - c.set("wideEvent", event); + c.set("wideEvent", event); - try { - await next(); - event.status_code = c.res.status; - event.outcome = c.res.status < 500 ? "success" : "error"; - } catch (err) { - event.status_code = 500; - event.outcome = "error"; - event.error = { - type: err instanceof Error ? err.constructor.name : "UnknownError", - message: err instanceof Error ? err.message : String(err), - }; - throw err; - } finally { - event.duration_ms = Date.now() - startTime; + try { + await next(); + event.status_code = c.res.status; + event.outcome = c.res.status < 500 ? "success" : "error"; + } catch (err) { + event.status_code = 500; + event.outcome = "error"; + event.error = { + type: err instanceof Error ? err.constructor.name : "UnknownError", + message: err instanceof Error ? err.message : String(err), + }; + throw err; + } finally { + event.duration_ms = Date.now() - startTime; - if (shouldSample(event)) { - getLogger(c).info(event); - } - } + if (shouldSample(event)) { + getLogger(c).info(event); + } + } } diff --git a/apps/backend/src/modules/health/handlers.ts b/apps/backend/src/modules/health/handlers.ts index 9eb74eb..a1c066f 100644 --- a/apps/backend/src/modules/health/handlers.ts +++ b/apps/backend/src/modules/health/handlers.ts @@ -2,32 +2,32 @@ import { sql } from "drizzle-orm"; import { db } from "@/db"; import type { AppRouteHandler } from "@/lib/types"; import { OK, SERVICE_UNAVAILABLE, success } from "@/lib/types"; -import type { HealthCheckRoute, PingRoute } from "./routes"; +import type { HealthCheckRoute, PingRoute } from "./routes.ts"; const startTime = Date.now(); export const healthCheckHandler: AppRouteHandler = async ( - c, + c, ) => { - let databaseUp = false; - try { - await db.execute(sql`SELECT 1`); - databaseUp = true; - } catch { - databaseUp = false; - } + let databaseUp = false; + try { + await db.execute(sql`SELECT 1`); + databaseUp = true; + } catch { + databaseUp = false; + } - const data = { - status: (databaseUp ? "healthy" : "unhealthy") as "healthy" | "unhealthy", - timestamp: new Date().toISOString(), - version: "1.0.0", - uptime: Math.floor((Date.now() - startTime) / 1000), - services: { database: (databaseUp ? "up" : "down") as "up" | "down" }, - }; + const data = { + status: (databaseUp ? "healthy" : "unhealthy") as "healthy" | "unhealthy", + timestamp: new Date().toISOString(), + version: "1.0.0", + uptime: Math.floor((Date.now() - startTime) / 1000), + services: { database: (databaseUp ? "up" : "down") as "up" | "down" }, + }; - return c.json(success(data), databaseUp ? OK : SERVICE_UNAVAILABLE); + return c.json(success(data), databaseUp ? OK : SERVICE_UNAVAILABLE); }; export const pingHandler: AppRouteHandler = async (c) => { - return c.json(success({ message: "pong" as const }), OK); + return c.json(success({ message: "pong" as const }), OK); }; diff --git a/apps/backend/src/modules/health/index.ts b/apps/backend/src/modules/health/index.ts index f8b93ba..5818253 100644 --- a/apps/backend/src/modules/health/index.ts +++ b/apps/backend/src/modules/health/index.ts @@ -1,9 +1,9 @@ import { createRouter } from "@/lib/create-app"; -import * as handlers from "./handlers"; -import * as routes from "./routes"; +import * as handlers from "./handlers.ts"; +import * as routes from "./routes.ts"; const router = createRouter() - .openapi(routes.healthCheck, handlers.healthCheckHandler) - .openapi(routes.ping, handlers.pingHandler); + .openapi(routes.healthCheck, handlers.healthCheckHandler) + .openapi(routes.ping, handlers.pingHandler); export default router; diff --git a/apps/backend/src/modules/health/routes.ts b/apps/backend/src/modules/health/routes.ts index 132cebf..3d7b10a 100644 --- a/apps/backend/src/modules/health/routes.ts +++ b/apps/backend/src/modules/health/routes.ts @@ -1,51 +1,51 @@ import { createRoute, z } from "@hono/zod-openapi"; import { apiErrorSchema, apiSuccessSchema } from "@repo/shared"; import { - INTERNAL_SERVER_ERROR, - jsonRes, - OK, - SERVICE_UNAVAILABLE, + INTERNAL_SERVER_ERROR, + jsonRes, + OK, + SERVICE_UNAVAILABLE, } from "@/lib/types"; const tags = ["Health"]; const healthDataSchema = z.object({ - status: z.enum(["healthy", "degraded", "unhealthy"]), - timestamp: z.string(), - version: z.string(), - uptime: z.number(), - services: z.object({ - database: z.enum(["up", "down"]), - }), + status: z.enum(["healthy", "degraded", "unhealthy"]), + timestamp: z.string(), + version: z.string(), + uptime: z.number(), + services: z.object({ + database: z.enum(["up", "down"]), + }), }); const healthResponseSchema = apiSuccessSchema(healthDataSchema); export const healthCheck = createRoute({ - method: "get", - path: "/health", - tags, - summary: "Health check", - description: "Check the health status of the API and its dependencies", - responses: { - [OK]: jsonRes(healthResponseSchema, "Service is healthy"), - [INTERNAL_SERVER_ERROR]: jsonRes(apiErrorSchema, "Service is unhealthy"), - [SERVICE_UNAVAILABLE]: jsonRes(healthResponseSchema, "Service is degraded"), - }, + method: "get", + path: "/health", + tags, + summary: "Health check", + description: "Check the health status of the API and its dependencies", + responses: { + [OK]: jsonRes(healthResponseSchema, "Service is healthy"), + [INTERNAL_SERVER_ERROR]: jsonRes(apiErrorSchema, "Service is unhealthy"), + [SERVICE_UNAVAILABLE]: jsonRes(healthResponseSchema, "Service is degraded"), + }, }); export const ping = createRoute({ - method: "get", - path: "/ping", - tags, - summary: "Ping", - description: "Simple ping endpoint for basic connectivity check", - responses: { - [OK]: jsonRes( - apiSuccessSchema(z.object({ message: z.literal("pong") })), - "Pong response", - ), - }, + method: "get", + path: "/ping", + tags, + summary: "Ping", + description: "Simple ping endpoint for basic connectivity check", + responses: { + [OK]: jsonRes( + apiSuccessSchema(z.object({ message: z.literal("pong") })), + "Pong response", + ), + }, }); export type HealthCheckRoute = typeof healthCheck; diff --git a/apps/backend/src/modules/health/usecases/check-health.usecase.ts b/apps/backend/src/modules/health/usecases/check-health.usecase.ts index d29ad3a..870d280 100644 --- a/apps/backend/src/modules/health/usecases/check-health.usecase.ts +++ b/apps/backend/src/modules/health/usecases/check-health.usecase.ts @@ -2,59 +2,59 @@ // Dependencies required by this use-case export interface CheckHealthDeps { - checkDatabase: () => Promise; + checkDatabase: () => Promise; } // Input for the use-case interface CheckHealthInput { - startTime: number; - version: string; + startTime: number; + version: string; } // Discriminated union result type type CheckHealthResult = - | { - type: "HEALTHY"; - data: HealthData; - } - | { - type: "UNHEALTHY"; - data: HealthData; - }; + | { + type: "HEALTHY"; + data: HealthData; + } + | { + type: "UNHEALTHY"; + data: HealthData; + }; interface HealthData { - status: "healthy" | "degraded" | "unhealthy"; - timestamp: string; - version: string; - uptime: number; - services: { - database: "up" | "down"; - }; + status: "healthy" | "degraded" | "unhealthy"; + timestamp: string; + version: string; + uptime: number; + services: { + database: "up" | "down"; + }; } export async function checkHealthUseCase( - deps: CheckHealthDeps, - input: CheckHealthInput, + deps: CheckHealthDeps, + input: CheckHealthInput, ): Promise { - const { checkDatabase } = deps; - const { startTime, version } = input; - - // Check database connectivity - const databaseUp = await checkDatabase(); - - const data: HealthData = { - status: databaseUp ? "healthy" : "unhealthy", - timestamp: new Date().toISOString(), - version, - uptime: Math.floor((Date.now() - startTime) / 1000), - services: { - database: databaseUp ? "up" : "down", - }, - }; - - if (databaseUp) { - return { type: "HEALTHY", data }; - } - - return { type: "UNHEALTHY", data }; + const { checkDatabase } = deps; + const { startTime, version } = input; + + // Check database connectivity + const databaseUp = await checkDatabase(); + + const data: HealthData = { + status: databaseUp ? "healthy" : "unhealthy", + timestamp: new Date().toISOString(), + version, + uptime: Math.floor((Date.now() - startTime) / 1000), + services: { + database: databaseUp ? "up" : "down", + }, + }; + + if (databaseUp) { + return { type: "HEALTHY", data }; + } + + return { type: "UNHEALTHY", data }; } diff --git a/apps/backend/src/modules/users/__tests__/handlers.test.ts b/apps/backend/src/modules/users/__tests__/handlers.test.ts index 8ef8455..6918c62 100644 --- a/apps/backend/src/modules/users/__tests__/handlers.test.ts +++ b/apps/backend/src/modules/users/__tests__/handlers.test.ts @@ -6,18 +6,27 @@ // Auth is handled by actually signing up through the app — no mocks. // Each test suite gets its own user; afterEach cleans all auth-related tables. -import { eq } from "drizzle-orm"; -import { afterEach, describe, expect, it } from "vitest"; +import { eq, inArray } from "drizzle-orm"; +import { afterAll, afterEach, describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import app from "@/app"; import { db, schema } from "@/db"; // ─── Cleanup ───────────────────────────────────────────────────────────────── +const createdEmails: string[] = []; +function uniqueEmail(prefix = "test"): string { + return `${prefix}-${crypto.randomUUID()}@example.com`; +} + afterEach(async () => { - // verifications has no cascade; delete first. - await db.delete(schema.verifications); - // users cascades sessions + accounts. - await db.delete(schema.users); + const emails = createdEmails.splice(0); + if (emails.length === 0) return; + await db.delete(schema.users).where(inArray(schema.users.email, emails)); +}); + +afterAll(async () => { + await (db as any).$client.end(); }); // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -25,203 +34,208 @@ afterEach(async () => { // Registers a user through the app and returns the session cookie. // autoSignIn: true in auth config means sign-up also creates the session. async function signUp(overrides?: { - email?: string; - password?: string; - name?: string; + email?: string; + password?: string; + name?: string; }) { - const email = overrides?.email ?? "test@example.com"; - const password = overrides?.password ?? "password-secret-123"; - const name = overrides?.name ?? "Test User"; - - const res = await app.request("/api/auth/sign-up/email", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email, password, name }), - }); - - const cookie = res.headers.get("set-cookie") ?? ""; - return { cookie, email, password, name }; + const email = overrides?.email ?? uniqueEmail(); + const password = overrides?.password ?? "password-secret-123"; + const name = overrides?.name ?? "Test User"; + + const res = await app.request("/api/auth/sign-up/email", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password, name }), + }); + + createdEmails.push(email); + + const cookie = res.headers.get("set-cookie") ?? ""; + return { cookie, email, password, name }; } // Signs in and returns the session cookie. async function signIn(email: string, password: string) { - const res = await app.request("/api/auth/sign-in/email", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email, password }), - }); - return res.headers.get("set-cookie") ?? ""; + const res = await app.request("/api/auth/sign-in/email", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + return res.headers.get("set-cookie") ?? ""; } // Elevates a user to admin directly in the DB. async function makeAdmin(email: string) { - await db - .update(schema.users) - .set({ role: "admin" }) - .where(eq(schema.users.email, email)); + await db + .update(schema.users) + .set({ role: "admin" }) + .where(eq(schema.users.email, email)); } // ─── GET /api/users/me ──────────────────────────────────────────────────────── describe("GET /api/users/me", () => { - it("returns 401 when not authenticated", async () => { - const res = await app.request("/api/users/me"); - expect(res.status).toBe(401); - }); - - it("returns the current user's profile when authenticated", async () => { - const { cookie, email, name } = await signUp(); - const res = await app.request("/api/users/me", { - headers: { Cookie: cookie }, - }); - - expect(res.status).toBe(200); - const body = (await res.json()) as any; - expect(body.success).toBe(true); - expect(body.data.email).toBe(email); - expect(body.data.name).toBe(name); - }); + it("returns 401 when not authenticated", async () => { + const res = await app.request("/api/users/me"); + expect(res.status).toBe(401); + }); + + it("returns the current user's profile when authenticated", async () => { + const { cookie, email, name } = await signUp(); + const res = await app.request("/api/users/me", { + headers: { Cookie: cookie }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as any; + expect(body.success).toBe(true); + expect(body.data.email).toBe(email); + expect(body.data.name).toBe(name); + }); }); // ─── PATCH /api/users/me ───────────────────────────────────────────────────── describe("PATCH /api/users/me", () => { - it("returns 401 when not authenticated", async () => { - const res = await app.request("/api/users/me", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: "New Name" }), - }); - expect(res.status).toBe(401); - }); - - it("updates the user's name", async () => { - const { cookie } = await signUp(); - const res = await app.request("/api/users/me", { - method: "PATCH", - headers: { Cookie: cookie, "Content-Type": "application/json" }, - body: JSON.stringify({ name: "Updated Name" }), - }); - - expect(res.status).toBe(200); - const body = (await res.json()) as any; - expect(body.data.name).toBe("Updated Name"); - }); - - it("returns 409 when the new email is already taken", async () => { - await signUp({ email: "taken@example.com", name: "Other" }); - const { cookie } = await signUp({ email: "me@example.com", name: "Me" }); - - const res = await app.request("/api/users/me", { - method: "PATCH", - headers: { Cookie: cookie, "Content-Type": "application/json" }, - body: JSON.stringify({ email: "taken@example.com" }), - }); - - expect(res.status).toBe(409); - const body = (await res.json()) as any; - expect(body.success).toBe(false); - expect(body.error.code).toBe("EMAIL_TAKEN"); - }); - - it("returns 400 when the email is the same as the current one", async () => { - const { cookie, email } = await signUp(); - const res = await app.request("/api/users/me", { - method: "PATCH", - headers: { Cookie: cookie, "Content-Type": "application/json" }, - body: JSON.stringify({ email }), - }); - - expect(res.status).toBe(400); - const body = (await res.json()) as any; - expect(body.error.code).toBe("EMAIL_UNCHANGED"); - }); - - it("updates the email, sets emailVerified to false, and returns the updated user", async () => { - const { cookie } = await signUp({ email: "old@example.com" }); - const res = await app.request("/api/users/me", { - method: "PATCH", - headers: { Cookie: cookie, "Content-Type": "application/json" }, - body: JSON.stringify({ email: "new@example.com" }), - }); - - expect(res.status).toBe(200); - const body = (await res.json()) as any; - expect(body.data.email).toBe("new@example.com"); - expect(body.data.emailVerified).toBe(false); - }); - - it("returns 200 with the current user when no fields are provided", async () => { - const { cookie, email } = await signUp(); - const res = await app.request("/api/users/me", { - method: "PATCH", - headers: { Cookie: cookie, "Content-Type": "application/json" }, - body: JSON.stringify({}), - }); - - expect(res.status).toBe(200); - const body = (await res.json()) as any; - expect(body.data.email).toBe(email); - }); + it("returns 401 when not authenticated", async () => { + const res = await app.request("/api/users/me", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: "New Name" }), + }); + expect(res.status).toBe(401); + }); + + it("updates the user's name", async () => { + const { cookie } = await signUp(); + const res = await app.request("/api/users/me", { + method: "PATCH", + headers: { Cookie: cookie, "Content-Type": "application/json" }, + body: JSON.stringify({ name: "Updated Name" }), + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as any; + expect(body.data.name).toBe("Updated Name"); + }); + + it("returns 409 when the new email is already taken", async () => { + const takenEmail = uniqueEmail("taken"); + await signUp({ email: takenEmail, name: "Other" }); + const { cookie } = await signUp({ email: uniqueEmail("me"), name: "Me" }); + + const res = await app.request("/api/users/me", { + method: "PATCH", + headers: { Cookie: cookie, "Content-Type": "application/json" }, + body: JSON.stringify({ email: takenEmail }), + }); + + expect(res.status).toBe(409); + const body = (await res.json()) as any; + expect(body.success).toBe(false); + expect(body.error.code).toBe("EMAIL_TAKEN"); + }); + + it("returns 400 when the email is the same as the current one", async () => { + const { cookie, email } = await signUp(); + const res = await app.request("/api/users/me", { + method: "PATCH", + headers: { Cookie: cookie, "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }); + + expect(res.status).toBe(400); + const body = (await res.json()) as any; + expect(body.error.code).toBe("EMAIL_UNCHANGED"); + }); + + it("updates the email, sets emailVerified to false, and returns the updated user", async () => { + const oldEmail = uniqueEmail("old"); + const newEmail = uniqueEmail("new"); + const { cookie } = await signUp({ email: oldEmail }); + const res = await app.request("/api/users/me", { + method: "PATCH", + headers: { Cookie: cookie, "Content-Type": "application/json" }, + body: JSON.stringify({ email: newEmail }), + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as any; + expect(body.data.email).toBe(newEmail); + expect(body.data.emailVerified).toBe(false); + }); + + it("returns 200 with the current user when no fields are provided", async () => { + const { cookie, email } = await signUp(); + const res = await app.request("/api/users/me", { + method: "PATCH", + headers: { Cookie: cookie, "Content-Type": "application/json" }, + body: JSON.stringify({}), + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as any; + expect(body.data.email).toBe(email); + }); }); // ─── GET /api/users/:id ─────────────────────────────────────────────────────── describe("GET /api/users/:id", () => { - it("returns 401 when not authenticated", async () => { - const res = await app.request("/api/users/some-id"); - expect(res.status).toBe(401); - }); - - it("returns 403 when authenticated as a regular user", async () => { - const { cookie } = await signUp(); - const res = await app.request("/api/users/some-id", { - headers: { Cookie: cookie }, - }); - - expect(res.status).toBe(403); - const body = (await res.json()) as any; - expect(body.error.code).toBe("FORBIDDEN"); - }); - - it("returns 404 when the user does not exist (admin)", async () => { - const { email } = await signUp(); - await makeAdmin(email); - // Re-sign in to get a fresh session with the updated role - const freshCookie = await signIn(email, "password-secret-123"); - - const res = await app.request("/api/users/nonexistent", { - headers: { Cookie: freshCookie }, - }); - - expect(res.status).toBe(404); - }); - - it("returns the target user when authenticated as admin", async () => { - // Create the target user - const { email: targetEmail } = await signUp({ - email: "target@example.com", - name: "Target", - }); - // Fetch the target's id from DB - const target = await db.query.users.findFirst({ - where: eq(schema.users.email, targetEmail), - }); - - // Create and elevate the admin - const { email: adminEmail } = await signUp({ - email: "admin@example.com", - name: "Admin", - }); - await makeAdmin(adminEmail); - const adminCookie = await signIn(adminEmail, "password-secret-123"); - - const res = await app.request(`/api/users/${target?.id}`, { - headers: { Cookie: adminCookie }, - }); - - expect(res.status).toBe(200); - const body = (await res.json()) as any; - expect(body.data.email).toBe(targetEmail); - }); + it("returns 401 when not authenticated", async () => { + const res = await app.request("/api/users/some-id"); + expect(res.status).toBe(401); + }); + + it("returns 403 when authenticated as a regular user", async () => { + const { cookie } = await signUp(); + const res = await app.request("/api/users/some-id", { + headers: { Cookie: cookie }, + }); + + expect(res.status).toBe(403); + const body = (await res.json()) as any; + expect(body.error.code).toBe("FORBIDDEN"); + }); + + it("returns 404 when the user does not exist (admin)", async () => { + const { email } = await signUp(); + await makeAdmin(email); + // Re-sign in to get a fresh session with the updated role + const freshCookie = await signIn(email, "password-secret-123"); + + const res = await app.request("/api/users/nonexistent", { + headers: { Cookie: freshCookie }, + }); + + expect(res.status).toBe(404); + }); + + it("returns the target user when authenticated as admin", async () => { + // Create the target user + const { email: targetEmail } = await signUp({ + email: uniqueEmail("target"), + name: "Target", + }); + // Fetch the target's id from DB + const target = await db.query.users.findFirst({ + where: eq(schema.users.email, targetEmail), + }); + + // Create and elevate the admin + const { email: adminEmail } = await signUp({ + email: uniqueEmail("admin"), + name: "Admin", + }); + await makeAdmin(adminEmail); + const adminCookie = await signIn(adminEmail, "password-secret-123"); + + const res = await app.request(`/api/users/${target?.id}`, { + headers: { Cookie: adminCookie }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as any; + expect(body.data.email).toBe(targetEmail); + }); }); diff --git a/apps/backend/src/modules/users/__tests__/users.repository.test.ts b/apps/backend/src/modules/users/__tests__/users.repository.test.ts index 7469db6..4e23c17 100644 --- a/apps/backend/src/modules/users/__tests__/users.repository.test.ts +++ b/apps/backend/src/modules/users/__tests__/users.repository.test.ts @@ -6,126 +6,137 @@ // Each test is isolated: afterEach cleans out the users table. // Sessions and accounts cascade on user delete, so no manual cleanup needed. +import { inArray } from "drizzle-orm"; import { users } from "@repo/db/schema"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterAll, afterEach, describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import { db } from "@/db"; import { - createUser, - findUserByEmail, - findUserById, - updateUser, -} from "../users.repository"; + createUser, + findUserByEmail, + findUserById, + updateUser, +} from "../users.repository.ts"; + +const FIXTURE_IDS = ["test-user-alice", "test-user-bob"]; afterEach(async () => { - await db.delete(users); + await db.delete(users).where(inArray(users.id, FIXTURE_IDS)); +}); + +afterAll(async () => { + await (db as any).$client.end(); }); // ─── Fixtures ───────────────────────────────────────────────────────────────── const alice = { - id: "test-user-alice", - email: "alice@example.com", - name: "Alice", - emailVerified: false as const, + id: "test-user-alice", + email: "alice@example.com", + name: "Alice", + emailVerified: false as const, }; const bob = { - id: "test-user-bob", - email: "bob@example.com", - name: "Bob", - emailVerified: false as const, + id: "test-user-bob", + email: "bob@example.com", + name: "Bob", + emailVerified: false as const, }; // ─── findUserById ───────────────────────────────────────────────────────────── describe("findUserById", () => { - it("returns USER_NOT_FOUND when no row exists", async () => { - const result = await findUserById("nonexistent"); - expect(result).toEqual({ - ok: false, - error: { type: "USER_NOT_FOUND", lookup: "nonexistent" }, - }); - }); - - it("returns the user when the row exists", async () => { - await db.insert(users).values(alice); - const result = await findUserById(alice.id); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.value.id).toBe(alice.id); - expect(result.value.email).toBe(alice.email); - } - }); + it("returns USER_NOT_FOUND when no row exists", async () => { + const result = await findUserById("nonexistent"); + expect(result).toEqual({ + ok: false, + error: { type: "USER_NOT_FOUND", lookup: "nonexistent" }, + }); + }); + + it("returns the user when the row exists", async () => { + await db.insert(users).values(alice); + const result = await findUserById(alice.id); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.id).toBe(alice.id); + expect(result.value.email).toBe(alice.email); + } + }); }); // ─── findUserByEmail ────────────────────────────────────────────────────────── describe("findUserByEmail", () => { - it("returns USER_NOT_FOUND when no row exists", async () => { - const result = await findUserByEmail("nobody@example.com"); - expect(result).toEqual({ - ok: false, - error: { type: "USER_NOT_FOUND", lookup: "nobody@example.com" }, - }); - }); - - it("returns the user when the row exists", async () => { - await db.insert(users).values(alice); - const result = await findUserByEmail(alice.email); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.value.email).toBe(alice.email); - } - }); + it("returns USER_NOT_FOUND when no row exists", async () => { + const result = await findUserByEmail("nobody@example.com"); + expect(result).toEqual({ + ok: false, + error: { type: "USER_NOT_FOUND", lookup: "nobody@example.com" }, + }); + }); + + it("returns the user when the row exists", async () => { + await db.insert(users).values(alice); + const result = await findUserByEmail(alice.email); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.email).toBe(alice.email); + } + }); }); // ─── createUser ─────────────────────────────────────────────────────────────── describe("createUser", () => { - it("inserts and returns the new user", async () => { - const result = await createUser(alice); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.value.email).toBe(alice.email); - expect(result.value.name).toBe(alice.name); - } - }); - - it("returns EMAIL_TAKEN when the email is already registered", async () => { - await createUser(alice); - const result = await createUser({ ...bob, email: alice.email }); - expect(result).toEqual({ - ok: false, - error: { type: "EMAIL_TAKEN", email: alice.email }, - }); - }); - - it("does not insert a duplicate when EMAIL_TAKEN is returned", async () => { - await createUser(alice); - await createUser({ ...bob, email: alice.email }); - const rows = await db.select().from(users); - expect(rows).toHaveLength(1); - }); + it("inserts and returns the new user", async () => { + const result = await createUser(alice); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.email).toBe(alice.email); + expect(result.value.name).toBe(alice.name); + } + }); + + it("returns EMAIL_TAKEN when the email is already registered", async () => { + await createUser(alice); + const result = await createUser({ ...bob, email: alice.email }); + expect(result).toEqual({ + ok: false, + error: { type: "EMAIL_TAKEN", email: alice.email }, + }); + }); + + it("does not insert a duplicate when EMAIL_TAKEN is returned", async () => { + await createUser(alice); + await createUser({ ...bob, email: alice.email }); + const rows = await db + .select() + .from(users) + .where(inArray(users.id, FIXTURE_IDS)); + expect(rows).toHaveLength(1); + }); }); // ─── updateUser ─────────────────────────────────────────────────────────────── describe("updateUser", () => { - it("updates and returns the user", async () => { - await db.insert(users).values(alice); - const result = await updateUser(alice.id, { name: "Alice Updated" }); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.value.name).toBe("Alice Updated"); - expect(result.value.id).toBe(alice.id); - } - }); - - it("returns USER_NOT_FOUND when no row matches the id", async () => { - const result = await updateUser("nonexistent", { name: "Ghost" }); - expect(result).toEqual({ - ok: false, - error: { type: "USER_NOT_FOUND", lookup: "nonexistent" }, - }); - }); + it("updates and returns the user", async () => { + await db.insert(users).values(alice); + const result = await updateUser(alice.id, { name: "Alice Updated" }); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.name).toBe("Alice Updated"); + expect(result.value.id).toBe(alice.id); + } + }); + + it("returns USER_NOT_FOUND when no row matches the id", async () => { + const result = await updateUser("nonexistent", { name: "Ghost" }); + expect(result).toEqual({ + ok: false, + error: { type: "USER_NOT_FOUND", lookup: "nonexistent" }, + }); + }); }); diff --git a/apps/backend/src/modules/users/__tests__/users.usecases.test.ts b/apps/backend/src/modules/users/__tests__/users.usecases.test.ts index 77a25b1..c3ac7e1 100644 --- a/apps/backend/src/modules/users/__tests__/users.usecases.test.ts +++ b/apps/backend/src/modules/users/__tests__/users.usecases.test.ts @@ -2,55 +2,59 @@ // Pure functions — no DB, no network, no mocks. Call with plain values. import type { User } from "@repo/db/schema"; -import { describe, expect, it } from "vitest"; -import { prepareEmailChange } from "../users.usecases"; +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; +import { prepareEmailChange } from "../users.usecases.ts"; // ─── Fixture ───────────────────────────────────────────────────────────────── const baseUser: User = { - id: "user-1", - email: "alice@example.com", - name: "Alice", - image: null, - role: "user", - emailVerified: true, - createdAt: new Date("2024-01-01"), - updatedAt: new Date("2024-01-01"), + id: "user-1", + email: "alice@example.com", + name: "Alice", + image: null, + role: "buyer", + emailVerified: true, + twoFactorEnabled: false, + twoFactorSecret: null, + backupCodes: null, + createdAt: new Date("2024-01-01"), + updatedAt: new Date("2024-01-01"), }; // ─── prepareEmailChange ─────────────────────────────────────────────────────── describe("prepareEmailChange", () => { - it("returns the new email and resets emailVerified when the email changes", () => { - const result = prepareEmailChange(baseUser, "bob@example.com"); - expect(result).toEqual({ - ok: true, - value: { email: "bob@example.com", emailVerified: false }, - }); - }); - - it("returns EMAIL_UNCHANGED when the new email matches the current one", () => { - const result = prepareEmailChange(baseUser, "alice@example.com"); - expect(result).toEqual({ - ok: false, - error: { type: "EMAIL_UNCHANGED", email: "alice@example.com" }, - }); - }); - - it("always sets emailVerified to false on a successful change", () => { - // Even if the user already had emailVerified: false, it must stay false. - const unverified = { ...baseUser, emailVerified: false }; - const result = prepareEmailChange(unverified, "new@example.com"); - expect(result.ok).toBe(true); - if (result.ok) expect(result.value.emailVerified).toBe(false); - }); - - it("does not carry forward any other user fields", () => { - // The return value is only { email, emailVerified } — no id, name, role leakage. - const result = prepareEmailChange(baseUser, "new@example.com"); - expect(result.ok).toBe(true); - if (result.ok) { - expect(Object.keys(result.value)).toEqual(["email", "emailVerified"]); - } - }); + it("returns the new email and resets emailVerified when the email changes", () => { + const result = prepareEmailChange(baseUser, "bob@example.com"); + expect(result).toEqual({ + ok: true, + value: { email: "bob@example.com", emailVerified: false }, + }); + }); + + it("returns EMAIL_UNCHANGED when the new email matches the current one", () => { + const result = prepareEmailChange(baseUser, "alice@example.com"); + expect(result).toEqual({ + ok: false, + error: { type: "EMAIL_UNCHANGED", email: "alice@example.com" }, + }); + }); + + it("always sets emailVerified to false on a successful change", () => { + // Even if the user already had emailVerified: false, it must stay false. + const unverified = { ...baseUser, emailVerified: false }; + const result = prepareEmailChange(unverified, "new@example.com"); + expect(result.ok).toBe(true); + if (result.ok) expect(result.value.emailVerified).toBe(false); + }); + + it("does not carry forward any other user fields", () => { + // The return value is only { email, emailVerified } — no id, name, role leakage. + const result = prepareEmailChange(baseUser, "new@example.com"); + expect(result.ok).toBe(true); + if (result.ok) { + expect(Object.keys(result.value)).toEqual(["email", "emailVerified"]); + } + }); }); diff --git a/apps/backend/src/modules/users/handlers.ts b/apps/backend/src/modules/users/handlers.ts index 9b38814..f716ed7 100644 --- a/apps/backend/src/modules/users/handlers.ts +++ b/apps/backend/src/modules/users/handlers.ts @@ -15,45 +15,50 @@ import type { InsertUser } from "@repo/db/schema"; import { match } from "@repo/shared"; import type { AppRouteHandler } from "@/lib/types"; import { - BAD_REQUEST, - CONFLICT, - FORBIDDEN, - failure, - INTERNAL_SERVER_ERROR, - isInfraError, - NOT_FOUND, - OK, - success, + BAD_REQUEST, + CONFLICT, + failure, + FORBIDDEN, + INTERNAL_SERVER_ERROR, + isInfraError, + NOT_FOUND, + OK, + success, } from "@/lib/types"; -import type { GetMeRoute, GetUserByIdRoute, UpdateMeRoute } from "./routes"; -import { findUserByEmail, findUserById, updateUser } from "./users.repository"; -import { prepareEmailChange } from "./users.usecases"; +import type { GetMeRoute, GetUserByIdRoute, UpdateMeRoute } from "./routes.ts"; +import { + findUserByEmail, + findUserById, + updateUser, +} from "./users.repository.ts"; +import { prepareEmailChange } from "./users.usecases.ts"; // GET /users/me // // Simple: load the current user from the DB and return it. // Shows the standard match + switch pattern for a single-error repository call. export const getMeHandler: AppRouteHandler = async (c) => { - const userId = c.get("session").userId; - const result = await findUserById(userId); + const userId = c.get("session").userId; + const result = await findUserById(userId); - return match(result, { - ok: (user) => c.json(success(user), OK), - err: (e) => { - if (isInfraError(e)) - return c.json( - failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), - INTERNAL_SERVER_ERROR, - ); - switch (e.type) { - case "USER_NOT_FOUND": - return c.json( - failure({ code: "NOT_FOUND", message: "User not found" }), - NOT_FOUND, - ); - } - }, - }); + return match(result, { + ok: (user) => c.json(success(user), OK), + err: (e) => { + if (isInfraError(e)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } + switch (e.type) { + case "USER_NOT_FOUND": + return c.json( + failure({ code: "NOT_FOUND", message: "User not found" }), + NOT_FOUND, + ); + } + }, + }); }; // PATCH /users/me @@ -70,90 +75,92 @@ export const getMeHandler: AppRouteHandler = async (c) => { // - Ensures the email verification flag is reset. // - Pure function: receives data, returns Result — no DB calls. export const updateMeHandler: AppRouteHandler = async (c) => { - const userId = c.get("user").id; - const body = c.req.valid("json"); + const userId = c.get("user").id; + const body = c.req.valid("json"); - // Load current user — the use-case needs it to apply business rules. - const current = await findUserById(userId); - if (!current.ok) { - if (isInfraError(current.error)) - return c.json( - failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), - INTERNAL_SERVER_ERROR, - ); - return c.json( - failure({ code: "NOT_FOUND", message: "User not found" }), - NOT_FOUND, - ); - } + // Load current user — the use-case needs it to apply business rules. + const current = await findUserById(userId); + if (!current.ok) { + if (isInfraError(current.error)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } + return c.json( + failure({ code: "NOT_FOUND", message: "User not found" }), + NOT_FOUND, + ); + } - // If an email change is requested, run the use-case and check availability. - let emailFields: { email: string; emailVerified: false } | undefined; - if (body.email !== undefined) { - // Pure rule: reject no-ops and derive the fields to persist. - const prepared = prepareEmailChange(current.value, body.email); - if (!prepared.ok) { - return c.json( - failure({ - code: "EMAIL_UNCHANGED", - message: "Provided email is the same as current", - }), - BAD_REQUEST, - ); - } + // If an email change is requested, run the use-case and check availability. + let emailFields: { email: string; emailVerified: false } | undefined; + if (body.email !== undefined) { + // Pure rule: reject no-ops and derive the fields to persist. + const prepared = prepareEmailChange(current.value, body.email); + if (!prepared.ok) { + return c.json( + failure({ + code: "EMAIL_UNCHANGED", + message: "Provided email is the same as current", + }), + BAD_REQUEST, + ); + } - // Imperative check: is the new email available? - // findUserByEmail returns ok if email IS taken, err(USER_NOT_FOUND) if it's free. - const emailCheck = await findUserByEmail(body.email); - if (emailCheck.ok) { - return c.json( - failure({ - code: "EMAIL_TAKEN", - message: "Email address is already in use", - }), - CONFLICT, - ); - } - if (isInfraError(emailCheck.error)) { - return c.json( - failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), - INTERNAL_SERVER_ERROR, - ); - } - // emailCheck.error.type === "USER_NOT_FOUND" — email is available. - emailFields = prepared.value; - } + // Imperative check: is the new email available? + // findUserByEmail returns ok if email IS taken, err(USER_NOT_FOUND) if it's free. + const emailCheck = await findUserByEmail(body.email); + if (emailCheck.ok) { + return c.json( + failure({ + code: "EMAIL_TAKEN", + message: "Email address is already in use", + }), + CONFLICT, + ); + } + if (isInfraError(emailCheck.error)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } + // emailCheck.error.type === "USER_NOT_FOUND" — email is available. + emailFields = prepared.value; + } - // Build the update payload. Spreading undefined = {} so emailFields is safe. - const updatePayload: Partial = { - ...(body.name !== undefined && { name: body.name }), - ...(body.image !== undefined && { image: body.image }), - ...emailFields, - }; + // Build the update payload. Spreading undefined = {} so emailFields is safe. + const updatePayload: Partial = { + ...(body.name !== undefined && { name: body.name }), + ...(body.image !== undefined && { image: body.image }), + ...emailFields, + }; - // Nothing to update — return current user without a DB roundtrip. - if (Object.keys(updatePayload).length === 0) { - return c.json(success(current.value), OK); - } + // Nothing to update — return current user without a DB roundtrip. + if (Object.keys(updatePayload).length === 0) { + return c.json(success(current.value), OK); + } - const result = await updateUser(userId, updatePayload); - return match(result, { - ok: (user) => c.json(success(user), OK), - err: (e) => { - if (isInfraError(e)) - return c.json( - failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), - INTERNAL_SERVER_ERROR, - ); - switch (e.type) { - case "USER_NOT_FOUND": - return c.json( - failure({ code: "NOT_FOUND", message: "User not found" }), - NOT_FOUND, - ); - } - }, - }); + const result = await updateUser(userId, updatePayload); + return match(result, { + ok: (user) => c.json(success(user), OK), + err: (e) => { + if (isInfraError(e)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } + switch (e.type) { + case "USER_NOT_FOUND": + return c.json( + failure({ code: "NOT_FOUND", message: "User not found" }), + NOT_FOUND, + ); + } + }, + }); }; // GET /users/:id (admin only) @@ -162,34 +169,35 @@ export const updateMeHandler: AppRouteHandler = async (c) => { // rather than via a path-level middleware, avoiding ambiguous path matching // with /users/me. All other logic is the same single-error match pattern. export const getUserByIdHandler: AppRouteHandler = async ( - c, + c, ) => { - const currentUser = c.get("user"); - if (currentUser.role !== "admin") { - return c.json( - failure({ code: "FORBIDDEN", message: "Admin access required" }), - FORBIDDEN, - ); - } + const currentUser = c.get("user"); + if (currentUser.role !== "admin") { + return c.json( + failure({ code: "FORBIDDEN", message: "Admin access required" }), + FORBIDDEN, + ); + } - const { id } = c.req.valid("param"); - const result = await findUserById(id); + const { id } = c.req.valid("param"); + const result = await findUserById(id); - return match(result, { - ok: (user) => c.json(success(user), OK), - err: (e) => { - if (isInfraError(e)) - return c.json( - failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), - INTERNAL_SERVER_ERROR, - ); - switch (e.type) { - case "USER_NOT_FOUND": - return c.json( - failure({ code: "NOT_FOUND", message: "User not found" }), - NOT_FOUND, - ); - } - }, - }); + return match(result, { + ok: (user) => c.json(success(user), OK), + err: (e) => { + if (isInfraError(e)) { + return c.json( + failure({ code: "INTERNAL_ERROR", message: "Service unavailable" }), + INTERNAL_SERVER_ERROR, + ); + } + switch (e.type) { + case "USER_NOT_FOUND": + return c.json( + failure({ code: "NOT_FOUND", message: "User not found" }), + NOT_FOUND, + ); + } + }, + }); }; diff --git a/apps/backend/src/modules/users/index.ts b/apps/backend/src/modules/users/index.ts index e916aca..040b421 100644 --- a/apps/backend/src/modules/users/index.ts +++ b/apps/backend/src/modules/users/index.ts @@ -1,10 +1,10 @@ import { createRouter } from "@/lib/create-app"; -import * as handlers from "./handlers"; -import * as routes from "./routes"; +import * as handlers from "./handlers.ts"; +import * as routes from "./routes.ts"; const router = createRouter() - .openapi(routes.getMe, handlers.getMeHandler) - .openapi(routes.updateMe, handlers.updateMeHandler) - .openapi(routes.getUserById, handlers.getUserByIdHandler); + .openapi(routes.getMe, handlers.getMeHandler) + .openapi(routes.updateMe, handlers.updateMeHandler) + .openapi(routes.getUserById, handlers.getUserByIdHandler); export default router; diff --git a/apps/backend/src/modules/users/routes.ts b/apps/backend/src/modules/users/routes.ts index dcced3d..57a31d7 100644 --- a/apps/backend/src/modules/users/routes.ts +++ b/apps/backend/src/modules/users/routes.ts @@ -2,15 +2,15 @@ import { createRoute, z } from "@hono/zod-openapi"; import { selectUserSchema } from "@repo/db/schema"; import { apiErrorSchema, apiSuccessSchema, idParamSchema } from "@repo/shared"; import { - BAD_REQUEST, - CONFLICT, - FORBIDDEN, - INTERNAL_SERVER_ERROR, - jsonBody, - jsonRes, - NOT_FOUND, - OK, - UNAUTHORIZED, + BAD_REQUEST, + CONFLICT, + FORBIDDEN, + INTERNAL_SERVER_ERROR, + jsonBody, + jsonRes, + NOT_FOUND, + OK, + UNAUTHORIZED, } from "@/lib/types"; const tags = ["Users"]; @@ -19,9 +19,9 @@ const userResponseSchema = apiSuccessSchema(selectUserSchema); // PATCH /users/me body const updateMeBodySchema = z.object({ - name: z.string().min(1).max(255).optional(), - image: z.url().optional(), - email: z.email().optional(), + name: z.string().min(1).max(255).optional(), + image: z.url().optional(), + email: z.email().optional(), }); // Shared error responses used across all three routes @@ -32,53 +32,53 @@ const e500 = jsonRes(apiErrorSchema, "Internal server error"); // ─── Routes ───────────────────────────────────────────────────────────────── export const getMe = createRoute({ - method: "get", - path: "/users/me", - tags, - summary: "Get current user", - description: "Returns the authenticated user's profile.", - responses: { - [OK]: jsonRes(userResponseSchema, "Current user profile"), - [UNAUTHORIZED]: e401, - [NOT_FOUND]: e404, - [INTERNAL_SERVER_ERROR]: e500, - }, + method: "get", + path: "/users/me", + tags, + summary: "Get current user", + description: "Returns the authenticated user's profile.", + responses: { + [OK]: jsonRes(userResponseSchema, "Current user profile"), + [UNAUTHORIZED]: e401, + [NOT_FOUND]: e404, + [INTERNAL_SERVER_ERROR]: e500, + }, }); export const updateMe = createRoute({ - method: "patch", - path: "/users/me", - tags, - summary: "Update current user", - description: - "Update name, image, or email. Changing email resets email verification and requires the address to be available.", - request: { - body: jsonBody(updateMeBodySchema), - }, - responses: { - [OK]: jsonRes(userResponseSchema, "Updated profile"), - [BAD_REQUEST]: jsonRes(apiErrorSchema, "Email is unchanged"), - [UNAUTHORIZED]: e401, - [NOT_FOUND]: e404, - [CONFLICT]: jsonRes(apiErrorSchema, "Email already taken"), - [INTERNAL_SERVER_ERROR]: e500, - }, + method: "patch", + path: "/users/me", + tags, + summary: "Update current user", + description: + "Update name, image, or email. Changing email resets email verification and requires the address to be available.", + request: { + body: jsonBody(updateMeBodySchema), + }, + responses: { + [OK]: jsonRes(userResponseSchema, "Updated profile"), + [BAD_REQUEST]: jsonRes(apiErrorSchema, "Email is unchanged"), + [UNAUTHORIZED]: e401, + [NOT_FOUND]: e404, + [CONFLICT]: jsonRes(apiErrorSchema, "Email already taken"), + [INTERNAL_SERVER_ERROR]: e500, + }, }); export const getUserById = createRoute({ - method: "get", - path: "/users/:id", - tags, - summary: "Get user by ID", - description: "Admin only. Fetch any user's profile by their ID.", - request: { params: idParamSchema }, - responses: { - [OK]: jsonRes(userResponseSchema, "User found"), - [UNAUTHORIZED]: e401, - [FORBIDDEN]: jsonRes(apiErrorSchema, "Forbidden — admin access required"), - [NOT_FOUND]: e404, - [INTERNAL_SERVER_ERROR]: e500, - }, + method: "get", + path: "/users/:id", + tags, + summary: "Get user by ID", + description: "Admin only. Fetch any user's profile by their ID.", + request: { params: idParamSchema }, + responses: { + [OK]: jsonRes(userResponseSchema, "User found"), + [UNAUTHORIZED]: e401, + [FORBIDDEN]: jsonRes(apiErrorSchema, "Forbidden — admin access required"), + [NOT_FOUND]: e404, + [INTERNAL_SERVER_ERROR]: e500, + }, }); // ─── Types ─────────────────────────────────────────────────────────────────── diff --git a/apps/backend/src/modules/users/users.repository.ts b/apps/backend/src/modules/users/users.repository.ts index 2190397..e1e4600 100644 --- a/apps/backend/src/modules/users/users.repository.ts +++ b/apps/backend/src/modules/users/users.repository.ts @@ -12,66 +12,68 @@ import { eq } from "drizzle-orm"; import { db } from "@/db"; import { InfrastructureError } from "@/lib/error"; import { tryInfra } from "@/lib/infra"; -import type { EmailTaken, UserNotFound } from "./users.errors"; +import type { EmailTaken, UserNotFound } from "./users.errors.ts"; export async function findUserById( - id: string, + id: string, ): Promise> { - const result = await tryInfra(`fetch user ${id}`, () => - db.query.users.findFirst({ where: eq(users.id, id) }), - ); - if (!result.ok) return result; - if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: id }); - return ok(result.value); + const result = await tryInfra( + `fetch user ${id}`, + () => db.query.users.findFirst({ where: eq(users.id, id) }), + ); + if (!result.ok) return result; + if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: id }); + return ok(result.value); } export async function findUserByEmail( - email: string, + email: string, ): Promise> { - const result = await tryInfra(`fetch user by email`, () => - db.query.users.findFirst({ where: eq(users.email, email) }), - ); - if (!result.ok) return result; - if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: email }); - return ok(result.value); + const result = await tryInfra( + `fetch user by email`, + () => db.query.users.findFirst({ where: eq(users.email, email) }), + ); + if (!result.ok) return result; + if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: email }); + return ok(result.value); } export async function createUser( - data: InsertUser, + data: InsertUser, ): Promise> { - // Check for existing email first — turns a DB constraint error into a typed domain error. - const existing = await tryInfra(`check email ${data.email}`, () => - db.query.users.findFirst({ where: eq(users.email, data.email) }), - ); - if (!existing.ok) return existing; - if (existing.value) return err({ type: "EMAIL_TAKEN", email: data.email }); + // Check for existing email first — turns a DB constraint error into a typed domain error. + const existing = await tryInfra( + `check email ${data.email}`, + () => db.query.users.findFirst({ where: eq(users.email, data.email) }), + ); + if (!existing.ok) return existing; + if (existing.value) return err({ type: "EMAIL_TAKEN", email: data.email }); - const result = await tryInfra("create user", () => - db - .insert(users) - .values(data) - .returning() - .then((rows) => rows[0]), - ); - if (!result.ok) return result; - if (!result.value) - return err(new InfrastructureError("Insert returned no rows")); - return ok(result.value); + const result = await tryInfra("create user", () => + db + .insert(users) + .values(data) + .returning() + .then((rows) => rows[0])); + if (!result.ok) return result; + if (!result.value) { + return err(new InfrastructureError("Insert returned no rows")); + } + return ok(result.value); } export async function updateUser( - id: string, - data: Partial, + id: string, + data: Partial, ): Promise> { - const result = await tryInfra(`update user ${id}`, () => - db - .update(users) - .set(data) - .where(eq(users.id, id)) - .returning() - .then((rows) => rows[0]), - ); - if (!result.ok) return result; - if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: id }); - return ok(result.value); + const result = await tryInfra(`update user ${id}`, () => + db + .update(users) + .set(data) + .where(eq(users.id, id)) + .returning() + .then((rows) => rows[0])); + if (!result.ok) return result; + if (!result.value) return err({ type: "USER_NOT_FOUND", lookup: id }); + return ok(result.value); } diff --git a/apps/backend/src/modules/users/users.usecases.ts b/apps/backend/src/modules/users/users.usecases.ts index edf16cf..704d650 100644 --- a/apps/backend/src/modules/users/users.usecases.ts +++ b/apps/backend/src/modules/users/users.usecases.ts @@ -5,7 +5,7 @@ // Testable by calling with plain values — no mocks, no DB setup. import type { User } from "@repo/db/schema"; import { err, ok, type Result } from "@repo/shared"; -import type { EmailUnchanged } from "./users.errors"; +import type { EmailUnchanged } from "./users.errors.ts"; // Prepare an email change for a user. // @@ -16,11 +16,12 @@ import type { EmailUnchanged } from "./users.errors"; // The handler is responsible for checking whether the new email is available // (findUserByEmail) before applying this result to updateUser. export function prepareEmailChange( - user: User, - newEmail: string, + user: User, + newEmail: string, ): Result<{ email: string; emailVerified: false }, EmailUnchanged> { - if (user.email === newEmail) - return err({ type: "EMAIL_UNCHANGED", email: newEmail }); + if (user.email === newEmail) { + return err({ type: "EMAIL_UNCHANGED", email: newEmail }); + } - return ok({ email: newEmail, emailVerified: false as const }); + return ok({ email: newEmail, emailVerified: false as const }); } diff --git a/apps/backend/src/routes/index.ts b/apps/backend/src/routes/index.ts index 5f9110a..236a22b 100644 --- a/apps/backend/src/routes/index.ts +++ b/apps/backend/src/routes/index.ts @@ -6,6 +6,6 @@ export const publicRoutes = [health]; // Protected routes (auth required — authMiddleware applied in app.ts) export const routes = [ - users, - // posts, + users, + // posts, ]; diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json deleted file mode 100644 index 1dc5371..0000000 --- a/apps/backend/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "target": "ESNext", - "jsx": "react-jsx", - "jsxImportSource": "react", - "module": "ESNext", - "moduleResolution": "Bundler", - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - "typeRoots": ["./node_modules/@types"], - "types": ["node"], - "strict": true, - "outDir": "./dist", - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "tsc-alias": { - "resolveFullPaths": true, - "baseUrl": "./", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src", "globals.d.ts", "../../packages/db/seedCore.ts"], - "references": [ - { "path": "../../packages/db" }, - { "path": "../../packages/shared" } - ] -} diff --git a/apps/backend/vitest.config.ts b/apps/backend/vitest.config.ts deleted file mode 100644 index 950472c..0000000 --- a/apps/backend/vitest.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import path from "node:path"; -import { defineConfig } from "vitest/config"; - -const repo = (pkg: string) => path.resolve(__dirname, "../../packages", pkg); - -export default defineConfig({ - test: { - globals: true, - environment: "node", - include: ["src/**/*.test.ts"], - coverage: { - reporter: ["text", "json", "html"], - }, - }, - resolve: { - alias: { - // Internal path alias - "@": path.resolve(__dirname, "./src"), - // Workspace packages — point straight to source so no build step is needed - "@repo/shared": repo("shared/src/index.ts"), - "@repo/db/schema": repo("db/src/schema/index.ts"), - "@repo/db/types": repo("db/src/types.ts"), - "@repo/db": repo("db/src/index.ts"), - }, - }, -}); diff --git a/biome.json b/biome.json index 3246310..b9cb233 100644 --- a/biome.json +++ b/biome.json @@ -1,92 +1,92 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.7/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "ignoreUnknown": false - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "a11y": { - "recommended": true, - "useButtonType": "error", - "useAltText": "error", - "useAnchorContent": "error", - "useHeadingContent": "error", - "useKeyWithClickEvents": "error", - "useKeyWithMouseEvents": "error", - "useValidAriaProps": "error", - "useValidAriaRole": "error", - "useValidAriaValues": "error", - "noAccessKey": "error", - "noAutofocus": "warn", - "noPositiveTabindex": "error", - "noRedundantAlt": "error", - "noRedundantRoles": "error", - "useFocusableInteractive": "error", - "useSemanticElements": "error" - }, - "correctness": { - "recommended": true, - "useExhaustiveDependencies": "error", - "useHookAtTopLevel": "error" - }, - "suspicious": { - "recommended": true, - "noExplicitAny": "warn" - }, - "style": { - "recommended": true, - "noNonNullAssertion": "error" - } - } - }, - "css": { - "parser": { - "tailwindDirectives": true - }, - "linter": { - "enabled": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - }, - "globals": ["React"] - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - }, - "overrides": [ - { - "includes": ["**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"], - "linter": { - "rules": { - "suspicious": { "noExplicitAny": "off" }, - "style": { "noNonNullAssertion": "off" } - } - } - }, - { - "includes": ["**/routeTree.gen.ts", "**/migrations/**"], - "linter": { "enabled": false }, - "formatter": { "enabled": false }, - "assist": { "enabled": false } - } - ] + "$schema": "https://biomejs.dev/schemas/2.3.7/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "recommended": true, + "useButtonType": "error", + "useAltText": "error", + "useAnchorContent": "error", + "useHeadingContent": "error", + "useKeyWithClickEvents": "error", + "useKeyWithMouseEvents": "error", + "useValidAriaProps": "error", + "useValidAriaRole": "error", + "useValidAriaValues": "error", + "noAccessKey": "error", + "noAutofocus": "warn", + "noPositiveTabindex": "error", + "noRedundantAlt": "error", + "noRedundantRoles": "error", + "useFocusableInteractive": "error", + "useSemanticElements": "error" + }, + "correctness": { + "recommended": true, + "useExhaustiveDependencies": "error", + "useHookAtTopLevel": "error" + }, + "suspicious": { + "recommended": true, + "noExplicitAny": "warn" + }, + "style": { + "recommended": true, + "noNonNullAssertion": "error" + } + } + }, + "css": { + "parser": { + "tailwindDirectives": true + }, + "linter": { + "enabled": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + }, + "globals": ["React"] + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "overrides": [ + { + "includes": ["**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"], + "linter": { + "rules": { + "suspicious": { "noExplicitAny": "off" }, + "style": { "noNonNullAssertion": "off" } + } + } + }, + { + "includes": ["**/routeTree.gen.ts", "**/migrations/**"], + "linter": { "enabled": false }, + "formatter": { "enabled": false }, + "assist": { "enabled": false } + } + ] } diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..f22b58d --- /dev/null +++ b/deno.json @@ -0,0 +1,29 @@ +{ + "workspace": [ + "apps/backend", + "packages/shared", + "packages/db", + "packages/email-templates" + ], + "tasks": { + "dev": "deno task --cwd=apps/backend dev", + "dev:backend": "deno task --cwd=apps/backend dev", + "dev:frontend": "deno run -A npm:vite dev --config apps/frontend/vite.config.ts", + "start": "deno task --cwd=apps/backend start", + "worker": "deno task --cwd=apps/backend worker", + "test": "deno task --cwd=apps/backend test", + "lint": "deno lint", + "fmt": "deno fmt", + "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", + "db:migrate": "deno task --cwd=apps/backend db:migrate", + "db:studio": "deno task --cwd=apps/backend db:studio", + "db:generate": "deno task --cwd=apps/backend db:generate" + }, + "imports": { + "@std/expect": "jsr:@std/expect@^1.0.19", + "@std/testing/bdd": "jsr:@std/testing@^1.0.18/bdd" + }, + "exclude": ["apps/frontend"], + "nodeModulesDir": "auto", + "sloppyImports": true +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..59b5b02 --- /dev/null +++ b/deno.lock @@ -0,0 +1,2730 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/assert@^1.0.19": "1.0.19", + "jsr:@std/expect@^1.0.19": "1.0.19", + "jsr:@std/internal@^1.0.12": "1.0.13", + "jsr:@std/internal@^1.0.13": "1.0.13", + "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@std/testing@^1.0.18": "1.0.18", + "npm:@aws-sdk/client-s3@*": "3.1045.0", + "npm:@aws-sdk/s3-request-presigner@*": "3.1045.0", + "npm:@axiomhq/pino@*": "1.6.1", + "npm:@biomejs/biome@2.3.7": "2.3.7", + "npm:@hono/swagger-ui@*": "0.6.1_hono@4.12.18", + "npm:@hono/zod-openapi@*": "1.4.0_hono@4.12.18_zod@4.4.3", + "npm:@hono/zod-validator@*": "0.8.0_hono@4.12.18_zod@4.4.3", + "npm:@node-rs/argon2@*": "2.0.2", + "npm:@scalar/hono-api-reference@*": "0.10.14_hono@4.12.18", + "npm:better-auth@*": "1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9", + "npm:bullmq@*": "5.76.6", + "npm:dotenv-expand@*": "13.0.0", + "npm:dotenv@*": "17.4.2", + "npm:drizzle-kit@*": "0.31.10", + "npm:drizzle-orm@*": "0.45.2_kysely@0.28.17_postgres@3.4.9", + "npm:drizzle-zod@*": "0.8.3_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_zod@4.4.3_kysely@0.28.17_postgres@3.4.9", + "npm:hono-pino@*": "0.10.3_hono@4.12.18_pino@10.3.1", + "npm:hono@*": "4.12.18", + "npm:ioredis@*": "5.10.1", + "npm:pino-pretty@*": "13.1.3", + "npm:pino@*": "10.3.1", + "npm:postgres@*": "3.4.9", + "npm:resend@*": "6.12.3", + "npm:stoker@*": "2.0.1_@hono+zod-openapi@1.4.0__hono@4.12.18__zod@4.4.3_hono@4.12.18_zod@4.4.3", + "npm:vite@*": "8.0.11", + "npm:zod@*": "4.4.3" + }, + "jsr": { + "@std/assert@1.0.19": { + "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", + "dependencies": [ + "jsr:@std/internal@^1.0.12" + ] + }, + "@std/expect@1.0.19": { + "integrity": "25271785688c7ff902fa54875d6aa4001f552c5578f7f0fefb5b5aac1fc2ed8a", + "dependencies": [ + "jsr:@std/assert", + "jsr:@std/internal@^1.0.13", + "jsr:@std/path" + ] + }, + "@std/internal@1.0.13": { + "integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0" + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal@^1.0.12" + ] + }, + "@std/testing@1.0.18": { + "integrity": "d3152f57b11666bf6358d0e127c7e3488e91178b0c2d8fbf0793e1c53cd13cb1", + "dependencies": [ + "jsr:@std/assert", + "jsr:@std/internal@^1.0.13" + ] + } + }, + "npm": { + "@asteasolutions/zod-to-openapi@8.5.0_zod@4.4.3": { + "integrity": "sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q==", + "dependencies": [ + "openapi3-ts", + "zod" + ] + }, + "@aws-crypto/crc32@5.2.0": { + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": [ + "@aws-crypto/util", + "@aws-sdk/types", + "tslib" + ] + }, + "@aws-crypto/crc32c@5.2.0": { + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dependencies": [ + "@aws-crypto/util", + "@aws-sdk/types", + "tslib" + ] + }, + "@aws-crypto/sha1-browser@5.2.0": { + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": [ + "@aws-crypto/supports-web-crypto", + "@aws-crypto/util", + "@aws-sdk/types", + "@aws-sdk/util-locate-window", + "@smithy/util-utf8@2.3.0", + "tslib" + ] + }, + "@aws-crypto/sha256-browser@5.2.0": { + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": [ + "@aws-crypto/sha256-js", + "@aws-crypto/supports-web-crypto", + "@aws-crypto/util", + "@aws-sdk/types", + "@aws-sdk/util-locate-window", + "@smithy/util-utf8@2.3.0", + "tslib" + ] + }, + "@aws-crypto/sha256-js@5.2.0": { + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": [ + "@aws-crypto/util", + "@aws-sdk/types", + "tslib" + ] + }, + "@aws-crypto/supports-web-crypto@5.2.0": { + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": [ + "tslib" + ] + }, + "@aws-crypto/util@5.2.0": { + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/util-utf8@2.3.0", + "tslib" + ] + }, + "@aws-sdk/client-s3@3.1045.0": { + "integrity": "sha512-fsuO3Y6t+3Ro9Bsg41DKj4Sfy53CGSrhnMldNplWmG8Tx0UbYk+YDa4RD1hVlJpERw4JBmPkl0+J9qlxMh1pcA==", + "dependencies": [ + "@aws-crypto/sha1-browser", + "@aws-crypto/sha256-browser", + "@aws-crypto/sha256-js", + "@aws-sdk/core", + "@aws-sdk/credential-provider-node", + "@aws-sdk/middleware-bucket-endpoint", + "@aws-sdk/middleware-expect-continue", + "@aws-sdk/middleware-flexible-checksums", + "@aws-sdk/middleware-host-header", + "@aws-sdk/middleware-location-constraint", + "@aws-sdk/middleware-logger", + "@aws-sdk/middleware-recursion-detection", + "@aws-sdk/middleware-sdk-s3", + "@aws-sdk/middleware-ssec", + "@aws-sdk/middleware-user-agent", + "@aws-sdk/region-config-resolver", + "@aws-sdk/signature-v4-multi-region", + "@aws-sdk/types", + "@aws-sdk/util-endpoints", + "@aws-sdk/util-user-agent-browser", + "@aws-sdk/util-user-agent-node", + "@smithy/config-resolver", + "@smithy/core", + "@smithy/eventstream-serde-browser", + "@smithy/eventstream-serde-config-resolver", + "@smithy/eventstream-serde-node", + "@smithy/fetch-http-handler", + "@smithy/hash-blob-browser", + "@smithy/hash-node", + "@smithy/hash-stream-node", + "@smithy/invalid-dependency", + "@smithy/md5-js", + "@smithy/middleware-content-length", + "@smithy/middleware-endpoint", + "@smithy/middleware-retry", + "@smithy/middleware-serde", + "@smithy/middleware-stack", + "@smithy/node-config-provider", + "@smithy/node-http-handler", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-body-length-node", + "@smithy/util-defaults-mode-browser", + "@smithy/util-defaults-mode-node", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "@smithy/util-waiter", + "tslib" + ] + }, + "@aws-sdk/core@3.974.8": { + "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "dependencies": [ + "@aws-sdk/types", + "@aws-sdk/xml-builder", + "@smithy/core", + "@smithy/node-config-provider", + "@smithy/property-provider", + "@smithy/protocol-http", + "@smithy/signature-v4", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-base64", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/crc64-nvme@3.972.7": { + "integrity": "sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-env@3.972.34": { + "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-http@3.972.36": { + "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@smithy/fetch-http-handler", + "@smithy/node-http-handler", + "@smithy/property-provider", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-stream", + "tslib" + ] + }, + "@aws-sdk/credential-provider-ini@3.972.38": { + "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/credential-provider-env", + "@aws-sdk/credential-provider-http", + "@aws-sdk/credential-provider-login", + "@aws-sdk/credential-provider-process", + "@aws-sdk/credential-provider-sso", + "@aws-sdk/credential-provider-web-identity", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/credential-provider-imds", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-login@3.972.38": { + "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/protocol-http", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-node@3.972.39": { + "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", + "dependencies": [ + "@aws-sdk/credential-provider-env", + "@aws-sdk/credential-provider-http", + "@aws-sdk/credential-provider-ini", + "@aws-sdk/credential-provider-process", + "@aws-sdk/credential-provider-sso", + "@aws-sdk/credential-provider-web-identity", + "@aws-sdk/types", + "@smithy/credential-provider-imds", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-process@3.972.34": { + "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-sso@3.972.38": { + "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/token-providers", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-web-identity@3.972.38": { + "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-bucket-endpoint@3.972.10": { + "integrity": "sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==", + "dependencies": [ + "@aws-sdk/types", + "@aws-sdk/util-arn-parser", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-config-provider", + "tslib" + ] + }, + "@aws-sdk/middleware-expect-continue@3.972.10": { + "integrity": "sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-flexible-checksums@3.974.16": { + "integrity": "sha512-6ru8doI0/XzszqLIPXf0E/V7HhAw1Pu94010XCKYtBUfD0LxF0BuOzrUf8OQGR6j2o6wgKTHUniOmndQycHwCA==", + "dependencies": [ + "@aws-crypto/crc32", + "@aws-crypto/crc32c", + "@aws-crypto/util", + "@aws-sdk/core", + "@aws-sdk/crc64-nvme", + "@aws-sdk/types", + "@smithy/is-array-buffer@4.2.2", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-middleware", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/middleware-host-header@3.972.10": { + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-location-constraint@3.972.10": { + "integrity": "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-logger@3.972.10": { + "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-recursion-detection@3.972.11": { + "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", + "dependencies": [ + "@aws-sdk/types", + "@aws/lambda-invoke-store", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-sdk-s3@3.972.37": { + "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@aws-sdk/util-arn-parser", + "@smithy/core", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/signature-v4", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-config-provider", + "@smithy/util-middleware", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/middleware-ssec@3.972.10": { + "integrity": "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-user-agent@3.972.38": { + "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@aws-sdk/util-endpoints", + "@smithy/core", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-retry", + "tslib" + ] + }, + "@aws-sdk/nested-clients@3.997.6": { + "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "dependencies": [ + "@aws-crypto/sha256-browser", + "@aws-crypto/sha256-js", + "@aws-sdk/core", + "@aws-sdk/middleware-host-header", + "@aws-sdk/middleware-logger", + "@aws-sdk/middleware-recursion-detection", + "@aws-sdk/middleware-user-agent", + "@aws-sdk/region-config-resolver", + "@aws-sdk/signature-v4-multi-region", + "@aws-sdk/types", + "@aws-sdk/util-endpoints", + "@aws-sdk/util-user-agent-browser", + "@aws-sdk/util-user-agent-node", + "@smithy/config-resolver", + "@smithy/core", + "@smithy/fetch-http-handler", + "@smithy/hash-node", + "@smithy/invalid-dependency", + "@smithy/middleware-content-length", + "@smithy/middleware-endpoint", + "@smithy/middleware-retry", + "@smithy/middleware-serde", + "@smithy/middleware-stack", + "@smithy/node-config-provider", + "@smithy/node-http-handler", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-body-length-node", + "@smithy/util-defaults-mode-browser", + "@smithy/util-defaults-mode-node", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/region-config-resolver@3.972.13": { + "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/config-resolver", + "@smithy/node-config-provider", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/s3-request-presigner@3.1045.0": { + "integrity": "sha512-VDRF8GIuUPX+K4DUYrvcODj/h54LOmdJ7DhpLQ0wrYrdxzIiJEpi0n9jZ1bbjT2UxhwTbOorse5EGo+gnOK2aA==", + "dependencies": [ + "@aws-sdk/signature-v4-multi-region", + "@aws-sdk/types", + "@aws-sdk/util-format-url", + "@smithy/middleware-endpoint", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/signature-v4-multi-region@3.996.25": { + "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "dependencies": [ + "@aws-sdk/middleware-sdk-s3", + "@aws-sdk/types", + "@smithy/protocol-http", + "@smithy/signature-v4", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/token-providers@3.1041.0": { + "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/types@3.973.8": { + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/util-arn-parser@3.972.3": { + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "dependencies": [ + "tslib" + ] + }, + "@aws-sdk/util-endpoints@3.996.8": { + "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-endpoints", + "tslib" + ] + }, + "@aws-sdk/util-format-url@3.972.10": { + "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/querystring-builder", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/util-locate-window@3.965.5": { + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dependencies": [ + "tslib" + ] + }, + "@aws-sdk/util-user-agent-browser@3.972.10": { + "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "bowser", + "tslib" + ] + }, + "@aws-sdk/util-user-agent-node@3.973.24": { + "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", + "dependencies": [ + "@aws-sdk/middleware-user-agent", + "@aws-sdk/types", + "@smithy/node-config-provider", + "@smithy/types", + "@smithy/util-config-provider", + "tslib" + ] + }, + "@aws-sdk/xml-builder@3.972.22": { + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "dependencies": [ + "@nodable/entities", + "@smithy/types", + "fast-xml-parser", + "tslib" + ] + }, + "@aws/lambda-invoke-store@0.2.4": { + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==" + }, + "@axiomhq/js@1.6.1": { + "integrity": "sha512-iNWOnGvP+R2lIWYk9jkfvRI/rfhOR9hlWJOBJhMKwZ9mfpE0KdnFnG2XPLoMcrbyrOBKj6Jw0hpIH5GhPucLog==", + "dependencies": [ + "fetch-retry" + ] + }, + "@axiomhq/pino@1.6.1": { + "integrity": "sha512-T5mdwsrbPOkPu7dkBU/HnQvO8EoiVEO4HpqvGliDEgYv2bTVwE/ELiHep02UMVze/Gv3DEgQ/GQSIjbOqTAzYw==", + "dependencies": [ + "@axiomhq/js", + "pino-abstract-transport@1.2.0" + ] + }, + "@better-auth/core@1.6.10_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-13h/rfSGMLl7zwyOb1BSlxAQZs2nQqn/xFI/bxB7zQuS95hVgTmNbKqhHtJYkNDtuJCcjEf1sNtLBHkvaPT/vw==", + "dependencies": [ + "@better-auth/utils", + "@better-fetch/fetch", + "@opentelemetry/semantic-conventions", + "@standard-schema/spec", + "better-call", + "jose", + "kysely", + "nanostores", + "zod" + ] + }, + "@better-auth/drizzle-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0_postgres@3.4.9": { + "integrity": "sha512-Ax0Jlpvuu35P3U6FtUGfkLAUmBwYIF+JwwtHt+jBlOEQNIiBhbLHh8ArQxrKJFRTDYv/XoSsrvJ5OluPsUFuuQ==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils", + "drizzle-orm" + ], + "optionalPeers": [ + "drizzle-orm" + ] + }, + "@better-auth/kysely-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_kysely@0.28.17_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_nanostores@1.3.0": { + "integrity": "sha512-Mp27qHgnvNCkkVEMRwhtMVpiiVFBnww0V+bunRWNU8fkxsm6H+GIBihUQOnkSCnIjV7f2HNjrf5DeYI2IhDePQ==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils", + "kysely" + ], + "optionalPeers": [ + "kysely" + ] + }, + "@better-auth/memory-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-zXk7GXnOpafAjCJ3+boh8hTEmUozGCLQpCl4plH9sAix6UlMdpYmdDTEGd+I8zungiEK+jzw4oevy7IirzKrrw==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils" + ] + }, + "@better-auth/mongo-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-EkbK3j8qwE9STteWoUh7vkve7n7/jSlkI0e9onAwr3YuE+an8scG7BgTHoDXku2qPlVa+KFPmcWc1FX6K/N6xA==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils" + ] + }, + "@better-auth/prisma-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-p/eJXl/RtHLt/A75chX/P55gjMzo5tWSJyfAyICts1miGHFUsu6D2TmKSzF7KNtjucjjzRKmtFl6BwMOxXXf2Q==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils" + ] + }, + "@better-auth/telemetry@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-7lcx4btKGe4tP7Y1Nk6MWQuaiKI+qOk08B4vZFJeNKxRXyCNjVmdck78NyOTEj3iaVxN69MiXDoBZ4fEdvVbBw==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils", + "@better-fetch/fetch" + ] + }, + "@better-auth/utils@0.4.0": { + "integrity": "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==", + "dependencies": [ + "@noble/hashes" + ] + }, + "@better-fetch/fetch@1.1.21": { + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==" + }, + "@biomejs/biome@2.3.7": { + "integrity": "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==", + "optionalDependencies": [ + "@biomejs/cli-darwin-arm64", + "@biomejs/cli-darwin-x64", + "@biomejs/cli-linux-arm64", + "@biomejs/cli-linux-arm64-musl", + "@biomejs/cli-linux-x64", + "@biomejs/cli-linux-x64-musl", + "@biomejs/cli-win32-arm64", + "@biomejs/cli-win32-x64" + ], + "bin": true + }, + "@biomejs/cli-darwin-arm64@2.3.7": { + "integrity": "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@biomejs/cli-darwin-x64@2.3.7": { + "integrity": "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@biomejs/cli-linux-arm64-musl@2.3.7": { + "integrity": "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@biomejs/cli-linux-arm64@2.3.7": { + "integrity": "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@biomejs/cli-linux-x64-musl@2.3.7": { + "integrity": "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@biomejs/cli-linux-x64@2.3.7": { + "integrity": "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@biomejs/cli-win32-arm64@2.3.7": { + "integrity": "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@biomejs/cli-win32-x64@2.3.7": { + "integrity": "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@drizzle-team/brocli@0.10.2": { + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==" + }, + "@emnapi/core@1.10.0": { + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dependencies": [ + "@emnapi/wasi-threads", + "tslib" + ] + }, + "@emnapi/runtime@1.10.0": { + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/wasi-threads@1.2.1": { + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dependencies": [ + "tslib" + ] + }, + "@esbuild-kit/core-utils@3.3.2": { + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "dependencies": [ + "esbuild@0.18.20", + "source-map-support" + ], + "deprecated": true + }, + "@esbuild-kit/esm-loader@2.6.5": { + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "dependencies": [ + "@esbuild-kit/core-utils", + "get-tsconfig" + ], + "deprecated": true + }, + "@esbuild/aix-ppc64@0.25.12": { + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/aix-ppc64@0.27.7": { + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.18.20": { + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm64@0.25.12": { + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm64@0.27.7": { + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.18.20": { + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-arm@0.25.12": { + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-arm@0.27.7": { + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.18.20": { + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/android-x64@0.25.12": { + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/android-x64@0.27.7": { + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.18.20": { + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-arm64@0.25.12": { + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-arm64@0.27.7": { + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.18.20": { + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/darwin-x64@0.25.12": { + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/darwin-x64@0.27.7": { + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.18.20": { + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-arm64@0.25.12": { + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-arm64@0.27.7": { + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.18.20": { + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-x64@0.25.12": { + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-x64@0.27.7": { + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.18.20": { + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm64@0.25.12": { + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm64@0.27.7": { + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.18.20": { + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-arm@0.25.12": { + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-arm@0.27.7": { + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.18.20": { + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-ia32@0.25.12": { + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-ia32@0.27.7": { + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.18.20": { + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-loong64@0.25.12": { + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-loong64@0.27.7": { + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.18.20": { + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-mips64el@0.25.12": { + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-mips64el@0.27.7": { + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.18.20": { + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-ppc64@0.25.12": { + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-ppc64@0.27.7": { + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.18.20": { + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-riscv64@0.25.12": { + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-riscv64@0.27.7": { + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.18.20": { + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-s390x@0.25.12": { + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-s390x@0.27.7": { + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.18.20": { + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/linux-x64@0.25.12": { + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/linux-x64@0.27.7": { + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.25.12": { + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-arm64@0.27.7": { + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.18.20": { + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-x64@0.25.12": { + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-x64@0.27.7": { + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.25.12": { + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-arm64@0.27.7": { + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.18.20": { + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-x64@0.25.12": { + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-x64@0.27.7": { + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.25.12": { + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/openharmony-arm64@0.27.7": { + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.18.20": { + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/sunos-x64@0.25.12": { + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/sunos-x64@0.27.7": { + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.18.20": { + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-arm64@0.25.12": { + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-arm64@0.27.7": { + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.18.20": { + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-ia32@0.25.12": { + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-ia32@0.27.7": { + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.18.20": { + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@esbuild/win32-x64@0.25.12": { + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@esbuild/win32-x64@0.27.7": { + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@hono/swagger-ui@0.6.1_hono@4.12.18": { + "integrity": "sha512-sJTvldu1GPeEPfyeLG7gRj+W4vEuD+JDi+JjJ3TJs/DvMUtBLs0KJO5yokGegWWdy5qrbdnQGekbhgNRmPmYKQ==", + "dependencies": [ + "hono" + ] + }, + "@hono/zod-openapi@1.4.0_hono@4.12.18_zod@4.4.3": { + "integrity": "sha512-AFchqR1N/NxfI4hUOSGI2/g8zLROxA1OE7Oh5JJFlTaGxhrdRyH+93gd0tIBpb0z8s9r8hUoNnaOBfHbdb4NMw==", + "dependencies": [ + "@asteasolutions/zod-to-openapi", + "@hono/zod-validator", + "hono", + "openapi3-ts", + "zod" + ] + }, + "@hono/zod-validator@0.8.0_hono@4.12.18_zod@4.4.3": { + "integrity": "sha512-5uS4S1/LKtZQYvD4BtpPUFkOv8d1wNxHHrChm26buMiEYc1FrHWvDUaKVBwkiVtvSExHSpLGDvcnpI2Copyj9w==", + "dependencies": [ + "hono", + "zod" + ] + }, + "@ioredis/commands@1.5.1": { + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==" + }, + "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": { + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": { + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": { + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": { + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": { + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": { + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@napi-rs/wasm-runtime@0.2.12": { + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util" + ] + }, + "@napi-rs/wasm-runtime@1.1.4_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0": { + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util" + ] + }, + "@noble/ciphers@2.2.0": { + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==" + }, + "@noble/hashes@2.2.0": { + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==" + }, + "@nodable/entities@2.1.0": { + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==" + }, + "@node-rs/argon2-android-arm-eabi@2.0.2": { + "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@node-rs/argon2-android-arm64@2.0.2": { + "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-darwin-arm64@2.0.2": { + "integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-darwin-x64@2.0.2": { + "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@node-rs/argon2-freebsd-x64@2.0.2": { + "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@node-rs/argon2-linux-arm-gnueabihf@2.0.2": { + "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@node-rs/argon2-linux-arm64-gnu@2.0.2": { + "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-linux-arm64-musl@2.0.2": { + "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-linux-x64-gnu@2.0.2": { + "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@node-rs/argon2-linux-x64-musl@2.0.2": { + "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@node-rs/argon2-wasm32-wasi@2.0.2": { + "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", + "dependencies": [ + "@napi-rs/wasm-runtime@0.2.12" + ], + "cpu": ["wasm32"] + }, + "@node-rs/argon2-win32-arm64-msvc@2.0.2": { + "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-win32-ia32-msvc@2.0.2": { + "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@node-rs/argon2-win32-x64-msvc@2.0.2": { + "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@node-rs/argon2@2.0.2": { + "integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==", + "optionalDependencies": [ + "@node-rs/argon2-android-arm-eabi", + "@node-rs/argon2-android-arm64", + "@node-rs/argon2-darwin-arm64", + "@node-rs/argon2-darwin-x64", + "@node-rs/argon2-freebsd-x64", + "@node-rs/argon2-linux-arm-gnueabihf", + "@node-rs/argon2-linux-arm64-gnu", + "@node-rs/argon2-linux-arm64-musl", + "@node-rs/argon2-linux-x64-gnu", + "@node-rs/argon2-linux-x64-musl", + "@node-rs/argon2-wasm32-wasi", + "@node-rs/argon2-win32-arm64-msvc", + "@node-rs/argon2-win32-ia32-msvc", + "@node-rs/argon2-win32-x64-msvc" + ] + }, + "@opentelemetry/semantic-conventions@1.40.0": { + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==" + }, + "@oxc-project/types@0.128.0": { + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==" + }, + "@pinojs/redact@0.4.0": { + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "@rolldown/binding-android-arm64@1.0.0-rc.18": { + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rolldown/binding-darwin-arm64@1.0.0-rc.18": { + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rolldown/binding-darwin-x64@1.0.0-rc.18": { + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rolldown/binding-freebsd-x64@1.0.0-rc.18": { + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18": { + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18": { + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.18": { + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18": { + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18": { + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.18": { + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rolldown/binding-linux-x64-musl@1.0.0-rc.18": { + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rolldown/binding-openharmony-arm64@1.0.0-rc.18": { + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@rolldown/binding-wasm32-wasi@1.0.0-rc.18": { + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", + "@napi-rs/wasm-runtime@1.1.4_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0" + ], + "cpu": ["wasm32"] + }, + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18": { + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.18": { + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rolldown/pluginutils@1.0.0-rc.18": { + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==" + }, + "@scalar/client-side-rendering@0.1.7": { + "integrity": "sha512-IDzjKF93jrOljlvKBsLHXT1FPWgz56jFrMPC+iLihREp1qH8wF92mG8Zpakw8cURkEuw5WijRk0xNBP2moGyuw==", + "dependencies": [ + "@scalar/types" + ] + }, + "@scalar/helpers@0.6.0": { + "integrity": "sha512-pfSamAgBxqFeE8IpEG6uGkHlnPhY1CLeOTttV9+vKQbrBk5b7vvyTsUXv0Hz4kNU1TFrxcTTPE+Akn5S+jlTtQ==" + }, + "@scalar/hono-api-reference@0.10.14_hono@4.12.18": { + "integrity": "sha512-LCIT4ul3c4MyD7shhxsWcvvOABt0fEHNQID2n+2TPeItc/MR2qCjjp/QfqD+JoQ7zbc0nnzh1kwRR06MVBmnUA==", + "dependencies": [ + "@scalar/client-side-rendering", + "hono" + ] + }, + "@scalar/types@0.9.6": { + "integrity": "sha512-UaCQQcscFTJdxZREE8KhUdSJgaDlc44TZbmWcZffs4m1hzqOvEI7lEBS13iBpLq7/cxUXFgyJdecywvNqJ0PkA==", + "dependencies": [ + "@scalar/helpers", + "nanoid@5.1.11", + "type-fest", + "zod" + ] + }, + "@smithy/chunked-blob-reader-native@4.2.3": { + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "dependencies": [ + "@smithy/util-base64", + "tslib" + ] + }, + "@smithy/chunked-blob-reader@5.2.2": { + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/config-resolver@4.4.17": { + "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", + "dependencies": [ + "@smithy/node-config-provider", + "@smithy/types", + "@smithy/util-config-provider", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "tslib" + ] + }, + "@smithy/core@3.23.17": { + "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-middleware", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "@smithy/uuid", + "tslib" + ] + }, + "@smithy/credential-provider-imds@4.2.14": { + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "dependencies": [ + "@smithy/node-config-provider", + "@smithy/property-provider", + "@smithy/types", + "@smithy/url-parser", + "tslib" + ] + }, + "@smithy/eventstream-codec@4.2.14": { + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "dependencies": [ + "@aws-crypto/crc32", + "@smithy/types", + "@smithy/util-hex-encoding", + "tslib" + ] + }, + "@smithy/eventstream-serde-browser@4.2.14": { + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", + "dependencies": [ + "@smithy/eventstream-serde-universal", + "@smithy/types", + "tslib" + ] + }, + "@smithy/eventstream-serde-config-resolver@4.3.14": { + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/eventstream-serde-node@4.2.14": { + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", + "dependencies": [ + "@smithy/eventstream-serde-universal", + "@smithy/types", + "tslib" + ] + }, + "@smithy/eventstream-serde-universal@4.2.14": { + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", + "dependencies": [ + "@smithy/eventstream-codec", + "@smithy/types", + "tslib" + ] + }, + "@smithy/fetch-http-handler@5.3.17": { + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/querystring-builder", + "@smithy/types", + "@smithy/util-base64", + "tslib" + ] + }, + "@smithy/hash-blob-browser@4.2.15": { + "integrity": "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA==", + "dependencies": [ + "@smithy/chunked-blob-reader", + "@smithy/chunked-blob-reader-native", + "@smithy/types", + "tslib" + ] + }, + "@smithy/hash-node@4.2.14": { + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", + "dependencies": [ + "@smithy/types", + "@smithy/util-buffer-from@4.2.2", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/hash-stream-node@4.2.14": { + "integrity": "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ==", + "dependencies": [ + "@smithy/types", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/invalid-dependency@4.2.14": { + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/is-array-buffer@2.2.0": { + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/is-array-buffer@4.2.2": { + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/md5-js@4.2.14": { + "integrity": "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA==", + "dependencies": [ + "@smithy/types", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/middleware-content-length@4.2.14": { + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@smithy/middleware-endpoint@4.4.32": { + "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "dependencies": [ + "@smithy/core", + "@smithy/middleware-serde", + "@smithy/node-config-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-middleware", + "tslib" + ] + }, + "@smithy/middleware-retry@4.5.7": { + "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "dependencies": [ + "@smithy/core", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/service-error-classification", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/uuid", + "tslib" + ] + }, + "@smithy/middleware-serde@4.2.20": { + "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", + "dependencies": [ + "@smithy/core", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@smithy/middleware-stack@4.2.14": { + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/node-config-provider@4.3.14": { + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", + "dependencies": [ + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@smithy/node-http-handler@4.6.1": { + "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/querystring-builder", + "@smithy/types", + "tslib" + ] + }, + "@smithy/property-provider@4.2.14": { + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/protocol-http@5.3.14": { + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/querystring-builder@4.2.14": { + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", + "dependencies": [ + "@smithy/types", + "@smithy/util-uri-escape", + "tslib" + ] + }, + "@smithy/querystring-parser@4.2.14": { + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/service-error-classification@4.3.1": { + "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", + "dependencies": [ + "@smithy/types" + ] + }, + "@smithy/shared-ini-file-loader@4.4.9": { + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/signature-v4@5.3.14": { + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", + "dependencies": [ + "@smithy/is-array-buffer@4.2.2", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-hex-encoding", + "@smithy/util-middleware", + "@smithy/util-uri-escape", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/smithy-client@4.12.13": { + "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", + "dependencies": [ + "@smithy/core", + "@smithy/middleware-endpoint", + "@smithy/middleware-stack", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-stream", + "tslib" + ] + }, + "@smithy/types@4.14.1": { + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/url-parser@4.2.14": { + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", + "dependencies": [ + "@smithy/querystring-parser", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-base64@4.3.2": { + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dependencies": [ + "@smithy/util-buffer-from@4.2.2", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/util-body-length-browser@4.2.2": { + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-body-length-node@4.2.3": { + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-buffer-from@2.2.0": { + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": [ + "@smithy/is-array-buffer@2.2.0", + "tslib" + ] + }, + "@smithy/util-buffer-from@4.2.2": { + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dependencies": [ + "@smithy/is-array-buffer@4.2.2", + "tslib" + ] + }, + "@smithy/util-config-provider@4.2.2": { + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-defaults-mode-browser@4.3.49": { + "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", + "dependencies": [ + "@smithy/property-provider", + "@smithy/smithy-client", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-defaults-mode-node@4.2.54": { + "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", + "dependencies": [ + "@smithy/config-resolver", + "@smithy/credential-provider-imds", + "@smithy/node-config-provider", + "@smithy/property-provider", + "@smithy/smithy-client", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-endpoints@3.4.2": { + "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", + "dependencies": [ + "@smithy/node-config-provider", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-hex-encoding@4.2.2": { + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-middleware@4.2.14": { + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-retry@4.3.8": { + "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", + "dependencies": [ + "@smithy/service-error-classification", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-stream@4.5.25": { + "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", + "dependencies": [ + "@smithy/fetch-http-handler", + "@smithy/node-http-handler", + "@smithy/types", + "@smithy/util-base64", + "@smithy/util-buffer-from@4.2.2", + "@smithy/util-hex-encoding", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/util-uri-escape@4.2.2": { + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-utf8@2.3.0": { + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": [ + "@smithy/util-buffer-from@2.2.0", + "tslib" + ] + }, + "@smithy/util-utf8@4.2.2": { + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dependencies": [ + "@smithy/util-buffer-from@4.2.2", + "tslib" + ] + }, + "@smithy/util-waiter@4.3.0": { + "integrity": "sha512-JyjYmLAfS+pdxF92o4yLgEoy0zhayKTw73FU1aofLWwLcJw7iSqIY2exGmMTrl/lmZugP5p/zxdFSippJDfKWA==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/uuid@1.1.2": { + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dependencies": [ + "tslib" + ] + }, + "@stablelib/base64@1.0.1": { + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==" + }, + "@standard-schema/spec@1.1.0": { + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" + }, + "@tybys/wasm-util@0.10.2": { + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dependencies": [ + "tslib" + ] + }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, + "atomic-sleep@1.0.0": { + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "better-auth@1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9": { + "integrity": "sha512-gzYaywJuhAkv9bTuFj1k6zaSKEAcabxAzYsBj0kXSMaQJVE9uS/qp2592IZmuvtMHO1ohLOP92jDPV6xVsZSoQ==", + "dependencies": [ + "@better-auth/core", + "@better-auth/drizzle-adapter", + "@better-auth/kysely-adapter", + "@better-auth/memory-adapter", + "@better-auth/mongo-adapter", + "@better-auth/prisma-adapter", + "@better-auth/telemetry", + "@better-auth/utils", + "@better-fetch/fetch", + "@noble/ciphers", + "@noble/hashes", + "better-call", + "defu", + "drizzle-kit", + "drizzle-orm", + "jose", + "kysely", + "nanostores", + "zod" + ], + "optionalPeers": [ + "drizzle-kit", + "drizzle-orm" + ] + }, + "better-call@1.3.5_zod@4.4.3": { + "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", + "dependencies": [ + "@better-auth/utils", + "@better-fetch/fetch", + "rou3", + "set-cookie-parser", + "zod" + ], + "optionalPeers": [ + "zod" + ] + }, + "bowser@2.14.1": { + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" + }, + "buffer-from@1.1.2": { + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, + "bullmq@5.76.6": { + "integrity": "sha512-vlmL3B3NVMRy6se3c7jPHn1Nhqxrg7+wlv1t3XAQFBYZNJDMLP0OO5x2AX5ca7DAuS1SU/C+VfYi+NHVoFK1QQ==", + "dependencies": [ + "cron-parser", + "ioredis", + "msgpackr", + "node-abort-controller", + "semver", + "tslib" + ] + }, + "cluster-key-slot@1.1.2": { + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" + }, + "colorette@2.0.20": { + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "cron-parser@4.9.0": { + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": [ + "luxon" + ] + }, + "dateformat@4.6.3": { + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "defu@6.1.7": { + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==" + }, + "denque@2.1.0": { + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "detect-libc@2.1.2": { + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + }, + "dotenv-expand@13.0.0": { + "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", + "dependencies": [ + "dotenv" + ] + }, + "dotenv@17.4.2": { + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==" + }, + "drizzle-kit@0.31.10": { + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "dependencies": [ + "@drizzle-team/brocli", + "@esbuild-kit/esm-loader", + "esbuild@0.25.12", + "tsx" + ], + "bin": true + }, + "drizzle-orm@0.45.2_kysely@0.28.17_postgres@3.4.9": { + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", + "dependencies": [ + "kysely", + "postgres" + ], + "optionalPeers": [ + "kysely", + "postgres" + ] + }, + "drizzle-zod@0.8.3_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_zod@4.4.3_kysely@0.28.17_postgres@3.4.9": { + "integrity": "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==", + "dependencies": [ + "drizzle-orm", + "zod" + ] + }, + "end-of-stream@1.4.5": { + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": [ + "once" + ] + }, + "esbuild@0.18.20": { + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "optionalDependencies": [ + "@esbuild/android-arm@0.18.20", + "@esbuild/android-arm64@0.18.20", + "@esbuild/android-x64@0.18.20", + "@esbuild/darwin-arm64@0.18.20", + "@esbuild/darwin-x64@0.18.20", + "@esbuild/freebsd-arm64@0.18.20", + "@esbuild/freebsd-x64@0.18.20", + "@esbuild/linux-arm@0.18.20", + "@esbuild/linux-arm64@0.18.20", + "@esbuild/linux-ia32@0.18.20", + "@esbuild/linux-loong64@0.18.20", + "@esbuild/linux-mips64el@0.18.20", + "@esbuild/linux-ppc64@0.18.20", + "@esbuild/linux-riscv64@0.18.20", + "@esbuild/linux-s390x@0.18.20", + "@esbuild/linux-x64@0.18.20", + "@esbuild/netbsd-x64@0.18.20", + "@esbuild/openbsd-x64@0.18.20", + "@esbuild/sunos-x64@0.18.20", + "@esbuild/win32-arm64@0.18.20", + "@esbuild/win32-ia32@0.18.20", + "@esbuild/win32-x64@0.18.20" + ], + "scripts": true, + "bin": true + }, + "esbuild@0.25.12": { + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "optionalDependencies": [ + "@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" + ], + "scripts": true, + "bin": true + }, + "esbuild@0.27.7": { + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "optionalDependencies": [ + "@esbuild/aix-ppc64@0.27.7", + "@esbuild/android-arm@0.27.7", + "@esbuild/android-arm64@0.27.7", + "@esbuild/android-x64@0.27.7", + "@esbuild/darwin-arm64@0.27.7", + "@esbuild/darwin-x64@0.27.7", + "@esbuild/freebsd-arm64@0.27.7", + "@esbuild/freebsd-x64@0.27.7", + "@esbuild/linux-arm@0.27.7", + "@esbuild/linux-arm64@0.27.7", + "@esbuild/linux-ia32@0.27.7", + "@esbuild/linux-loong64@0.27.7", + "@esbuild/linux-mips64el@0.27.7", + "@esbuild/linux-ppc64@0.27.7", + "@esbuild/linux-riscv64@0.27.7", + "@esbuild/linux-s390x@0.27.7", + "@esbuild/linux-x64@0.27.7", + "@esbuild/netbsd-arm64@0.27.7", + "@esbuild/netbsd-x64@0.27.7", + "@esbuild/openbsd-arm64@0.27.7", + "@esbuild/openbsd-x64@0.27.7", + "@esbuild/openharmony-arm64@0.27.7", + "@esbuild/sunos-x64@0.27.7", + "@esbuild/win32-arm64@0.27.7", + "@esbuild/win32-ia32@0.27.7", + "@esbuild/win32-x64@0.27.7" + ], + "scripts": true, + "bin": true + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events@3.3.0": { + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "fast-copy@4.0.3": { + "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==" + }, + "fast-safe-stringify@2.1.1": { + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "fast-sha256@1.3.0": { + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==" + }, + "fast-xml-builder@1.2.0": { + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "dependencies": [ + "path-expression-matcher", + "xml-naming" + ] + }, + "fast-xml-parser@5.7.2": { + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "dependencies": [ + "@nodable/entities", + "fast-xml-builder", + "path-expression-matcher", + "strnum" + ], + "bin": true + }, + "fdir@6.5.0_picomatch@4.0.4": { + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dependencies": [ + "picomatch" + ], + "optionalPeers": [ + "picomatch" + ] + }, + "fetch-retry@6.0.0": { + "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==" + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true + }, + "get-tsconfig@4.14.0": { + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dependencies": [ + "resolve-pkg-maps" + ] + }, + "help-me@5.0.0": { + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "hono-pino@0.10.3_hono@4.12.18_pino@10.3.1": { + "integrity": "sha512-n0RNPIFOoq25Fg8b4D5gus4sVqI0z+8I17ibl96+p43d07UnZ0EMM/It0qSgfc7UtaC+XP5FkFmRHwBp6owsNA==", + "dependencies": [ + "defu", + "hono", + "pino" + ] + }, + "hono@4.12.18": { + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==" + }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ioredis@5.10.1": { + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "dependencies": [ + "@ioredis/commands", + "cluster-key-slot", + "debug", + "denque", + "lodash.defaults", + "lodash.isarguments", + "redis-errors", + "redis-parser", + "standard-as-callback" + ] + }, + "jose@6.2.3": { + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" + }, + "joycon@3.1.1": { + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" + }, + "kysely@0.28.17": { + "integrity": "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==" + }, + "lightningcss-android-arm64@1.32.0": { + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-arm64@1.32.0": { + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-x64@1.32.0": { + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "lightningcss-freebsd-x64@1.32.0": { + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "lightningcss-linux-arm-gnueabihf@1.32.0": { + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "lightningcss-linux-arm64-gnu@1.32.0": { + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-arm64-musl@1.32.0": { + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-x64-gnu@1.32.0": { + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-linux-x64-musl@1.32.0": { + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-win32-arm64-msvc@1.32.0": { + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "lightningcss-win32-x64-msvc@1.32.0": { + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "os": ["win32"], + "cpu": ["x64"] + }, + "lightningcss@1.32.0": { + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dependencies": [ + "detect-libc" + ], + "optionalDependencies": [ + "lightningcss-android-arm64", + "lightningcss-darwin-arm64", + "lightningcss-darwin-x64", + "lightningcss-freebsd-x64", + "lightningcss-linux-arm-gnueabihf", + "lightningcss-linux-arm64-gnu", + "lightningcss-linux-arm64-musl", + "lightningcss-linux-x64-gnu", + "lightningcss-linux-x64-musl", + "lightningcss-win32-arm64-msvc", + "lightningcss-win32-x64-msvc" + ] + }, + "lodash.defaults@4.2.0": { + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "lodash.isarguments@3.1.0": { + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "luxon@3.7.2": { + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==" + }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "msgpackr-extract@3.0.3": { + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dependencies": [ + "node-gyp-build-optional-packages" + ], + "optionalDependencies": [ + "@msgpackr-extract/msgpackr-extract-darwin-arm64", + "@msgpackr-extract/msgpackr-extract-darwin-x64", + "@msgpackr-extract/msgpackr-extract-linux-arm", + "@msgpackr-extract/msgpackr-extract-linux-arm64", + "@msgpackr-extract/msgpackr-extract-linux-x64", + "@msgpackr-extract/msgpackr-extract-win32-x64" + ], + "scripts": true, + "bin": true + }, + "msgpackr@2.0.1": { + "integrity": "sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==", + "optionalDependencies": [ + "msgpackr-extract" + ] + }, + "nanoid@3.3.12": { + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "bin": true + }, + "nanoid@5.1.11": { + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "bin": true + }, + "nanostores@1.3.0": { + "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==" + }, + "node-abort-controller@3.1.1": { + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node-gyp-build-optional-packages@5.2.2": { + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dependencies": [ + "detect-libc" + ], + "bin": true + }, + "on-exit-leak-free@2.1.2": { + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" + }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, + "openapi3-ts@4.5.0": { + "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", + "dependencies": [ + "yaml" + ] + }, + "path-expression-matcher@1.5.0": { + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch@4.0.4": { + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" + }, + "pino-abstract-transport@1.2.0": { + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": [ + "readable-stream", + "split2" + ] + }, + "pino-abstract-transport@3.0.0": { + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dependencies": [ + "split2" + ] + }, + "pino-pretty@13.1.3": { + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "dependencies": [ + "colorette", + "dateformat", + "fast-copy", + "fast-safe-stringify", + "help-me", + "joycon", + "minimist", + "on-exit-leak-free", + "pino-abstract-transport@3.0.0", + "pump", + "secure-json-parse", + "sonic-boom", + "strip-json-comments" + ], + "bin": true + }, + "pino-std-serializers@7.1.0": { + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==" + }, + "pino@10.3.1": { + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dependencies": [ + "@pinojs/redact", + "atomic-sleep", + "on-exit-leak-free", + "pino-abstract-transport@3.0.0", + "pino-std-serializers", + "process-warning", + "quick-format-unescaped", + "real-require", + "safe-stable-stringify", + "sonic-boom", + "thread-stream" + ], + "bin": true + }, + "postal-mime@2.7.4": { + "integrity": "sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==" + }, + "postcss@8.5.14": { + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dependencies": [ + "nanoid@3.3.12", + "picocolors", + "source-map-js" + ] + }, + "postgres@3.4.9": { + "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==" + }, + "process-warning@5.0.0": { + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" + }, + "process@0.11.10": { + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "pump@3.0.4": { + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dependencies": [ + "end-of-stream", + "once" + ] + }, + "quick-format-unescaped@4.0.4": { + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "readable-stream@4.7.0": { + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": [ + "abort-controller", + "buffer", + "events", + "process", + "string_decoder" + ] + }, + "real-require@0.2.0": { + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" + }, + "redis-errors@1.2.0": { + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser@3.0.0": { + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": [ + "redis-errors" + ] + }, + "resend@6.12.3": { + "integrity": "sha512-FkEi6YPnVL96/LvH8+QP7NaeaBy5brYXwlRqUCqZZeNL0/iyKij18IPmyPXYauT/2ODn1JG04qKz+qlJfzqzTw==", + "dependencies": [ + "postal-mime", + "svix" + ] + }, + "resolve-pkg-maps@1.0.0": { + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" + }, + "rolldown@1.0.0-rc.18": { + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "dependencies": [ + "@oxc-project/types", + "@rolldown/pluginutils" + ], + "optionalDependencies": [ + "@rolldown/binding-android-arm64", + "@rolldown/binding-darwin-arm64", + "@rolldown/binding-darwin-x64", + "@rolldown/binding-freebsd-x64", + "@rolldown/binding-linux-arm-gnueabihf", + "@rolldown/binding-linux-arm64-gnu", + "@rolldown/binding-linux-arm64-musl", + "@rolldown/binding-linux-ppc64-gnu", + "@rolldown/binding-linux-s390x-gnu", + "@rolldown/binding-linux-x64-gnu", + "@rolldown/binding-linux-x64-musl", + "@rolldown/binding-openharmony-arm64", + "@rolldown/binding-wasm32-wasi", + "@rolldown/binding-win32-arm64-msvc", + "@rolldown/binding-win32-x64-msvc" + ], + "bin": true + }, + "rou3@0.7.12": { + "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==" + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-stable-stringify@2.5.0": { + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, + "secure-json-parse@4.1.0": { + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==" + }, + "semver@7.7.4": { + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "bin": true + }, + "set-cookie-parser@3.1.0": { + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==" + }, + "sonic-boom@4.2.1": { + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dependencies": [ + "atomic-sleep" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "source-map-support@0.5.21": { + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": [ + "buffer-from", + "source-map" + ] + }, + "source-map@0.6.1": { + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, + "standard-as-callback@2.1.0": { + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "standardwebhooks@1.0.0": { + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "dependencies": [ + "@stablelib/base64", + "fast-sha256" + ] + }, + "stoker@2.0.1_@hono+zod-openapi@1.4.0__hono@4.12.18__zod@4.4.3_hono@4.12.18_zod@4.4.3": { + "integrity": "sha512-liSQNnJmn8fWSEan7sVaFe6iSHuN3X02fDGLS6snwW+FUuKi5HmKUHm3P+Kzr5xiDPqRpmSTtmGEBbSL9H2zkQ==", + "dependencies": [ + "@hono/zod-openapi", + "hono" + ], + "optionalPeers": [ + "@hono/zod-openapi" + ] + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": [ + "safe-buffer" + ] + }, + "strip-json-comments@5.0.3": { + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==" + }, + "strnum@2.3.0": { + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==" + }, + "svix@1.92.2": { + "integrity": "sha512-ZmuA3UVvlnF9EgxlzmPtF7CKjQb64Z6OFlyfdDfU0sdcC7dJa+3aOYX5B9mA+RS6ch1AxBa4UP/l6KmqfGtWBQ==", + "dependencies": [ + "standardwebhooks" + ] + }, + "tagged-tag@1.0.0": { + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" + }, + "thread-stream@4.0.0": { + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "dependencies": [ + "real-require" + ] + }, + "tinyglobby@0.2.16": { + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dependencies": [ + "fdir", + "picomatch" + ] + }, + "tslib@2.8.1": { + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "tsx@4.21.0": { + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dependencies": [ + "esbuild@0.27.7", + "get-tsconfig" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "type-fest@5.6.0": { + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "dependencies": [ + "tagged-tag" + ] + }, + "vite@8.0.11": { + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "dependencies": [ + "lightningcss", + "picomatch", + "postcss", + "rolldown", + "tinyglobby" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "xml-naming@0.1.0": { + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==" + }, + "yaml@2.8.4": { + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "bin": true + }, + "zod@4.4.3": { + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/expect@^1.0.19", + "jsr:@std/testing@^1.0.18" + ], + "packageJson": { + "dependencies": [ + "npm:@biomejs/biome@2.3.7" + ] + }, + "members": { + "apps/backend": { + "dependencies": [ + "npm:@aws-sdk/client-s3@*", + "npm:@aws-sdk/s3-request-presigner@*", + "npm:@axiomhq/pino@*", + "npm:@hono/swagger-ui@*", + "npm:@hono/zod-openapi@*", + "npm:@hono/zod-validator@*", + "npm:@node-rs/argon2@*", + "npm:@scalar/hono-api-reference@*", + "npm:better-auth@*", + "npm:bullmq@*", + "npm:dotenv-expand@*", + "npm:dotenv@*", + "npm:drizzle-kit@*", + "npm:drizzle-orm@*", + "npm:drizzle-zod@*", + "npm:hono-pino@*", + "npm:hono@*", + "npm:ioredis@*", + "npm:pino-pretty@*", + "npm:pino@*", + "npm:postgres@*", + "npm:resend@*", + "npm:stoker@*", + "npm:zod@*" + ] + }, + "packages/db": { + "dependencies": [ + "npm:dotenv@*", + "npm:drizzle-kit@*", + "npm:drizzle-orm@*", + "npm:drizzle-zod@*", + "npm:postgres@*" + ] + }, + "packages/shared": { + "dependencies": [ + "npm:zod@*" + ] + } + } + } +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 13173d5..084fbf5 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -17,7 +17,7 @@ services: volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: [ "CMD", "pg_isready", "-U", "${POSTGRES_USER:-orcta}" ] + test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-orcta}"] interval: 10s timeout: 5s retries: 5 @@ -28,7 +28,7 @@ services: volumes: - redis_data:/data healthcheck: - test: [ "CMD", "redis-cli", "ping" ] + test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 diff --git a/docs/BATTERIES.md b/docs/BATTERIES.md index 6e39798..8772f35 100644 --- a/docs/BATTERIES.md +++ b/docs/BATTERIES.md @@ -1,7 +1,9 @@ + # Batteries -Everything opt-in beyond the basics — each section only activates once you add the relevant env vars. +Everything opt-in beyond the basics — each section only activates once you add +the relevant env vars. **Contents** @@ -19,35 +21,38 @@ Everything opt-in beyond the basics — each section only activates once you add ## Auth -Authentication is always on — no env var required. Powered by [better-auth](https://better-auth.com) with a Drizzle adapter. +Authentication is always on — no env var required. Powered by +[better-auth](https://better-auth.com) with a Drizzle adapter. ### What's included - Email + password sign-up / sign-in - Session management (7-day expiry, rolling 1-day refresh) -- A `role` field on every user (default `"user"`) +- A `role` field on every user (default `"buyer"`) - Auth routes mounted at `/api/auth/**` - OpenAPI docs auto-generated for all auth endpoints ### Session in a handler -The `authMiddleware` (applied to all routes in the `routes` array) populates `c.get("session")`: +The `authMiddleware` (applied to all routes in the `routes` array) populates +`c.get("session")`: ```typescript import type { AppRouteHandler } from "@/lib/types"; export const myHandler: AppRouteHandler = async (c) => { const session = c.get("session"); - const userId = session.userId; - const role = session.user.role; // "user" | your custom roles + const userId = session.userId; + const role = session.user.role; // "buyer" | "seller" | "admin" // ... }; ``` ### Extending the user model -Add fields in `apps/backend/src/lib/auth.ts` under `user.additionalFields`, then add the -matching column to `packages/db/src/schema/users.ts` and run a migration. +Add fields in `apps/backend/src/lib/auth.ts` under `user.additionalFields`, then +add the matching column to `packages/db/src/schema/users.ts` and run a +migration. ### Frontend client @@ -67,11 +72,12 @@ await signInWithProvider("github", "/dashboard"); ### Social OAuth (Google + GitHub) -**Requires env vars:** at least one of `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` or `GITHUB_CLIENT_ID`/`GITHUB_CLIENT_SECRET` +**Requires env vars:** at least one of `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` +or `GITHUB_CLIENT_ID`/`GITHUB_CLIENT_SECRET` -Providers are activated only when both their vars are present — leaving vars blank will -not break the app, the button just will not appear. No DB migration needed (the existing -`accounts` table already handles OAuth tokens). +Providers are activated only when both their vars are present — leaving vars +blank will not break the app, the button just will not appear. No DB migration +needed (the existing `accounts` table already handles OAuth tokens). Add to `.env`: @@ -98,9 +104,11 @@ http://localhost:9999/api/auth/callback/github ## File Uploads -Upload files directly to S3 or Cloudflare R2 using presigned URLs. Files never touch the server. +Upload files directly to S3 or Cloudflare R2 using presigned URLs. Files never +touch the server. -**Requires env vars:** `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` +**Requires env vars:** `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION`, +`S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` ### Setup @@ -119,7 +127,12 @@ Works with AWS S3, Cloudflare R2, MinIO, or any S3-compatible store. ### API ```typescript -import { generateKey, getUploadUrl, getDownloadUrl, deleteFile } from "@/lib/storage"; +import { + deleteFile, + generateKey, + getDownloadUrl, + getUploadUrl, +} from "@/lib/storage"; // 1. Generate a collision-free key const key = generateKey("photo.jpg", "avatars"); @@ -188,43 +201,46 @@ Real-time communication with room support. import { upgradeWebSocket } from "hono/ws"; import { wsManager } from "@/lib/ws"; -app.get("/ws", upgradeWebSocket((c) => { - // capture the id in the closure so all callbacks share it - let connectionId: string; - - return { - onOpen(_event, ws) { - connectionId = crypto.randomUUID(); - wsManager.add(connectionId, ws, c.get("session")?.userId); - wsManager.join(connectionId, "global"); - }, - - onMessage(event, _ws) { - const data = JSON.parse(String(event.data)); - // handle incoming messages - }, - - onClose() { - wsManager.remove(connectionId); - }, - }; -})); +app.get( + "/ws", + upgradeWebSocket((c) => { + // capture the id in the closure so all callbacks share it + let connectionId: string; + + return { + onOpen(_event, ws) { + connectionId = crypto.randomUUID(); + wsManager.add(connectionId, ws, c.get("session")?.userId); + wsManager.join(connectionId, "global"); + }, + + onMessage(event, _ws) { + const data = JSON.parse(String(event.data)); + // handle incoming messages + }, + + onClose() { + wsManager.remove(connectionId); + }, + }; + }), +); ``` ### WebSocketManager API -| Method | Description | -|--------|-------------| -| `add(id, ws, userId?)` | Register a new connection | -| `remove(id)` | Remove a connection | -| `get(id)` | Get a connection by id | -| `join(id, room)` | Add a connection to a room | -| `leave(id, room)` | Remove a connection from a room | -| `send(id, data)` | Send to one connection | -| `broadcast(room, data, excludeId?)` | Send to all connections in a room | -| `sendToUser(userId, data)` | Send to all connections belonging to a user | -| `broadcastAll(data, excludeId?)` | Send to every connected client | -| `size` | Number of active connections | +| Method | Description | +| ----------------------------------- | ------------------------------------------- | +| `add(id, ws, userId?)` | Register a new connection | +| `remove(id)` | Remove a connection | +| `get(id)` | Get a connection by id | +| `join(id, room)` | Add a connection to a room | +| `leave(id, room)` | Remove a connection from a room | +| `send(id, data)` | Send to one connection | +| `broadcast(room, data, excludeId?)` | Send to all connections in a room | +| `sendToUser(userId, data)` | Send to all connections belonging to a user | +| `broadcastAll(data, excludeId?)` | Send to every connected client | +| `size` | Number of active connections | ### Client usage @@ -239,7 +255,9 @@ ws.onmessage = (event) => { ws.send(JSON.stringify({ type: "join", room: "chat-123" })); ``` -> **Production note** — `wsManager` is in-process. If you run multiple server instances, connections are not shared across them. Add a Redis pub/sub layer if you need cross-instance broadcasting. +> **Production note** — `wsManager` is in-process. If you run multiple server +> instances, connections are not shared across them. Add a Redis pub/sub layer +> if you need cross-instance broadcasting. --- @@ -263,9 +281,9 @@ Defined in `apps/backend/src/jobs/index.ts`: export type JobName = "email" | "cleanup" | "sync"; export interface JobData { - email: { to: string; template: string; data: Record }; + email: { to: string; template: string; data: Record }; cleanup: { olderThanDays: number }; - sync: { userId: string }; + sync: { userId: string }; } ``` @@ -283,16 +301,18 @@ await addJob("email", { // With options await addJob("cleanup", { olderThanDays: 30 }, { - delay: 60_000, // wait 1 min before processing - priority: 10, // higher = processed first + delay: 60_000, // wait 1 min before processing + priority: 10, // higher = processed first }); ``` -Jobs are automatically kept for the last 100 successes and 1 000 failures in Redis. +Jobs are automatically kept for the last 100 successes and 1 000 failures in +Redis. ### Process jobs -Add your logic in `apps/backend/src/jobs/worker.ts` inside the `processors` object: +Add your logic in `apps/backend/src/jobs/worker.ts` inside the `processors` +object: ```typescript const processors = { @@ -313,23 +333,27 @@ const processors = { }; ``` -Each worker runs with **concurrency 5** and handles graceful shutdown on `SIGTERM`/`SIGINT`. +Each worker runs with **concurrency 5** and handles graceful shutdown on +`SIGTERM`/`SIGINT`. ### Run workers ```bash -pnpm --filter backend jobs +deno run --env-file=apps/backend/.env -A apps/backend/src/jobs/worker.ts ``` -In production, run this as a separate process or container alongside the HTTP server. +In production, run this as a separate process or container alongside the HTTP +server. ### Adding a new job type -1. Add the name to the `JobName` union and its payload to `JobData` in `jobs/index.ts` +1. Add the name to the `JobName` union and its payload to `JobData` in + `jobs/index.ts` 2. Create a queue getter following the existing pattern (`getSyncQueue` etc.) 3. Add the queue to the `queueMap` inside `addJob` 4. Add a processor in `worker.ts` -5. Add the job name to the workers array: `(["email", "cleanup", "sync", "yourJob"] as JobName[])` +5. Add the job name to the workers array: + `(["email", "cleanup", "sync", "yourJob"] as JobName[])` --- @@ -346,10 +370,13 @@ import { rateLimit } from "@/lib/rate-limit"; app.use("/api/*", rateLimit()); // Custom window -app.use("/api/search", rateLimit({ - windowMs: 60_000, // 1 minute - max: 20, -})); +app.use( + "/api/search", + rateLimit({ + windowMs: 60_000, // 1 minute + max: 20, + }), +); ``` ### Presets @@ -357,31 +384,38 @@ app.use("/api/search", rateLimit({ ```typescript import { authRateLimit, strictRateLimit } from "@/lib/rate-limit"; -app.post("/api/auth/*", authRateLimit); // 5 req / 5 min — for login / sign-up +app.post("/api/auth/*", authRateLimit); // 5 req / 5 min — for login / sign-up app.post("/api/export", strictRateLimit); // 10 req / min — for expensive operations ``` ### Rate limit by user ```typescript -app.use("/api/*", rateLimit({ - keyGenerator: (c) => c.get("session")?.userId - ?? c.req.header("x-forwarded-for") - ?? "anon", -})); +app.use( + "/api/*", + rateLimit({ + keyGenerator: (c) => + c.get("session")?.userId ?? + c.req.header("x-forwarded-for") ?? + "anon", + }), +); ``` ### Response headers Every rate-limited response includes: -| Header | Meaning | -|--------|---------| -| `X-RateLimit-Limit` | Max requests allowed in the window | -| `X-RateLimit-Remaining` | Requests left this window | -| `Retry-After` | Seconds until reset (only on 429) | +| Header | Meaning | +| ----------------------- | ---------------------------------- | +| `X-RateLimit-Limit` | Max requests allowed in the window | +| `X-RateLimit-Remaining` | Requests left this window | +| `Retry-After` | Seconds until reset (only on 429) | -> **Production note** — the store is in-memory and resets on restart. It is not shared across multiple server instances. For multi-instance deployments, replace the store with a Redis-backed implementation (e.g. `rate-limit-redis`). +> **Production note** — the store is in-memory and resets on restart. It is not +> shared across multiple server instances. For multi-instance deployments, +> replace the store with a Redis-backed implementation (e.g. +> `rate-limit-redis`). --- @@ -401,12 +435,15 @@ RESEND_API_KEY=re_xxxxx ```typescript import { Resend } from "resend"; -import { welcomeEmail, passwordResetEmail } from "@repo/email-templates"; +import { passwordResetEmail, welcomeEmail } from "@repo/email-templates"; const resend = new Resend(process.env.RESEND_API_KEY); // Welcome email -const welcome = welcomeEmail({ name: "Alex", actionUrl: "https://app.example.com/verify?token=xxx" }); +const welcome = welcomeEmail({ + name: "Alex", + actionUrl: "https://app.example.com/verify?token=xxx", +}); await resend.emails.send({ from: "hello@yourdomain.com", @@ -417,7 +454,10 @@ await resend.emails.send({ }); // Password reset -const reset = passwordResetEmail({ name: "Alex", actionUrl: "https://app.example.com/reset?token=xxx" }); +const reset = passwordResetEmail({ + name: "Alex", + actionUrl: "https://app.example.com/reset?token=xxx", +}); await resend.emails.send({ from: "hello@yourdomain.com", @@ -430,9 +470,9 @@ await resend.emails.send({ ### Available templates -| Function | Subject | -|----------|---------| -| `welcomeEmail({ name, actionUrl? })` | `Welcome, {name}!` | +| Function | Subject | +| ----------------------------------------- | --------------------- | +| `welcomeEmail({ name, actionUrl? })` | `Welcome, {name}!` | | `passwordResetEmail({ name, actionUrl })` | `Reset your password` | ### Adding templates @@ -440,29 +480,36 @@ await resend.emails.send({ Edit `packages/email-templates/src/index.ts`: ```typescript -export function invoiceEmail({ amount, dueDate }: { amount: number; dueDate: string }): EmailTemplate { +export function invoiceEmail( + { amount, dueDate }: { amount: number; dueDate: string }, +): EmailTemplate { return { subject: `Invoice for $${amount}`, - html: baseTemplate("Invoice", `

Amount due: $${amount}

Due by: ${dueDate}

`), + html: baseTemplate( + "Invoice", + `

Amount due: $${amount}

Due by: ${dueDate}

`, + ), text: `Invoice\n\nAmount due: $${amount}\nDue by: ${dueDate}`, }; } ``` -`baseTemplate(title, body)` handles the outer HTML shell and sign-off. All templates export `{ subject, html, text }`. +`baseTemplate(title, body)` handles the outer HTML shell and sign-off. All +templates export `{ subject, html, text }`. --- ## Redis -A shared `ioredis` client is available for caching, pub/sub, or anything else that needs Redis. +A shared `ioredis` client is available for caching, pub/sub, or anything else +that needs Redis. **Requires env var:** `REDIS_URL` ### Usage ```typescript -import { redis, getRedis } from "@/lib/redis"; +import { getRedis, redis } from "@/lib/redis"; // redis is null if REDIS_URL is not set — safe to import unconditionally if (redis) { @@ -487,17 +534,17 @@ Pagination utilities live in `@repo/shared` — no infrastructure required. ```typescript import { - paginationSchema, - paginationQuery, - paginatedSuccessSchema, paginate, + paginatedSuccessSchema, + paginationQuery, + paginationSchema, } from "@repo/shared"; ``` ### Route definition ```typescript -import { paginationSchema, paginatedSuccessSchema } from "@repo/shared"; +import { paginatedSuccessSchema, paginationSchema } from "@repo/shared"; import { apiErrorSchema } from "@repo/shared"; export const listPosts = createRoute({ @@ -505,7 +552,7 @@ export const listPosts = createRoute({ path: "/posts", tags, request: { - query: paginationSchema, // parses ?page=1&limit=20 with defaults + query: paginationSchema, // parses ?page=1&limit=20 with defaults }, responses: { [OK]: jsonRes(paginatedSuccessSchema(selectPostSchema), "Paginated posts"), @@ -557,12 +604,13 @@ export const listPostsHandler: AppRouteHandler = async (c) => { A thin read-through cache over the existing Redis client. -**Requires env var:** `REDIS_URL` — degrades gracefully to a direct function call when Redis is absent. +**Requires env var:** `REDIS_URL` — degrades gracefully to a direct function +call when Redis is absent. ### API ```typescript -import { withCache, invalidateCache, cacheKey } from "@/lib/cache"; +import { cacheKey, invalidateCache, withCache } from "@/lib/cache"; ``` ### Read-through cache @@ -571,8 +619,8 @@ import { withCache, invalidateCache, cacheKey } from "@/lib/cache"; // In a repository: export async function findUserById(id: string) { return withCache( - cacheKey("user", id), // → "user:abc123" - 300, // TTL: 5 minutes + cacheKey("user", id), // → "user:abc123" + 300, // TTL: 5 minutes () => db.query.users.findFirst({ where: eq(users.id, id) }), ); } @@ -594,9 +642,9 @@ await invalidateCache( ### Behaviour -| Scenario | Result | -|----------|--------| -| Redis available, key exists | Returns cached value (no DB call) | -| Redis available, cache miss | Calls `fn`, stores result, returns value | -| Redis down or absent | Calls `fn` directly — request never fails | -| Cache write fails | Swallowed — the value is still returned | +| Scenario | Result | +| --------------------------- | ----------------------------------------- | +| Redis available, key exists | Returns cached value (no DB call) | +| Redis available, cache miss | Calls `fn`, stores result, returns value | +| Redis down or absent | Calls `fn` directly — request never fails | +| Cache write fails | Swallowed — the value is still returned | diff --git a/docs/DENO_WORKSPACE_SCOPE.md b/docs/DENO_WORKSPACE_SCOPE.md new file mode 100644 index 0000000..42f8a9b --- /dev/null +++ b/docs/DENO_WORKSPACE_SCOPE.md @@ -0,0 +1,375 @@ +# Deno Workspace Migration Scope + +Current architecture: flat root `deno.json` with a global import map that +resolves `@repo/*`, `@/*`, and all `npm:` dependencies. This works, but +per-package config is crowded into one file. + +Goal: use Deno's native workspace system so each package is self-describing. + +--- + +## Current State + +``` +deno.json (root) ← 40+ import map entries, 10 tasks, lint/fmt/test config +├── apps/ +│ ├── backend/ ← 20 @/ imports + 10 @repo/ imports from root deno.json +│ └── frontend/ ← pnpm only, no @repo imports in source +├── packages/ +│ ├── shared/ ← package.json only, no deno.json; 3 source files, 2 test files +│ ├── db/ ← package.json only, no deno.json; schema dir, no tests +│ └── email-templates/ ← package.json only, no deno.json; 1 source, 1 test file +├── package.json ← pnpm root +└── pnpm-workspace.yaml ← MISSING (but frontend package.json has workspace:* deps) +``` + +### Dependency graph + +``` +apps/backend + └── @repo/shared (20 import sites across handlers, repos, use-cases) + └── @repo/db (7 import sites, mostly @repo/db/schema) + └── @repo/email-templates (not yet imported — future) + +packages/db + └── drizzle-orm, postgres (npm: deps, currently in root import map) + +packages/shared — zero external deps, pure TypeScript +packages/email-templates — zero external deps, pure TypeScript + +apps/frontend + └── @repo/shared (package.json dep, NOT imported in source) + └── @repo/db (package.json dep, NOT imported in source) +``` + +--- + +## Target State + +### Root `deno.json` + +```json +{ + "workspace": [ + "apps/backend", + "packages/shared", + "packages/db", + "packages/email-templates" + ], + "tasks": { + "dev": "deno task --cwd=apps/backend dev", + "dev:frontend": "deno run -A npm:vite dev --config apps/frontend/vite.config.ts", + "check": "deno check", + "lint": "deno lint", + "fmt": "deno fmt" + }, + "imports": { + "@std/expect": "jsr:@std/expect@^1.0.19", + "@std/testing/bdd": "jsr:@std/testing@^1.0.18/bdd" + }, + "nodeModulesDir": "auto", + "sloppyImports": true +} +``` + +Root owns: workspace membership, shared dev/test deps, top-level convenience +tasks. No npm runtime deps, no `@repo/*` entries, no `@/*` aliases. + +### `packages/shared/deno.json` — NEW + +```json +{ + "name": "@repo/shared", + "version": "0.1.0", + "exports": "./src/index.ts", + "exclude": ["node_modules"] +} +``` + +Zero external deps — pure TypeScript. The `name` field lets Deno resolve +`@repo/shared` as a bare specifier via workspace resolution, replacing the root +import map entry. + +### `packages/db/deno.json` — NEW + +```json +{ + "name": "@repo/db", + "version": "0.1.0", + "exports": { + ".": "./src/index.ts", + "./schema": "./src/schema/index.ts" + }, + "imports": { + "drizzle-orm": "npm:drizzle-orm", + "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", + "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", + "postgres": "npm:postgres" + }, + "exclude": ["node_modules"] +} +``` + +The `exports` with sub-path `./schema` preserves `@repo/db/schema` imports +without needing a root import map entry. + +### `packages/email-templates/deno.json` — NEW + +```json +{ + "name": "@repo/email-templates", + "version": "0.1.0", + "exports": "./src/index.ts", + "exclude": ["node_modules"] +} +``` + +### `apps/backend/deno.json` — MOVE + EXPAND + +```json +{ + "tasks": { + "dev": "deno run --watch --env-file=.env -A src/index.ts", + "start": "deno run --env-file=.env -A src/index.ts", + "test": "deno test --env-file=.env -A", + "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", + "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", + "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" + }, + "imports": { + "@/": "./src/", + "@/app": "./src/app.ts", + "@/db": "./src/db/index.ts", + "@/env": "./src/env.ts", + "@/lib/auth": "./src/lib/auth.ts", + "@/lib/create-app": "./src/lib/create-app.ts", + "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", + "@/lib/types": "./src/lib/types.ts", + "@/lib/redis": "./src/lib/redis.ts", + "@/lib/error": "./src/lib/error.ts", + "@/lib/infra": "./src/lib/infra.ts", + "@/lib/cache": "./src/lib/cache.ts", + "@/lib/storage": "./src/lib/storage.ts", + "@/lib/rate-limit": "./src/lib/rate-limit.ts", + "@/lib/ws": "./src/lib/ws.ts", + "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", + "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", + "@/middlewares/auth": "./src/middlewares/auth.ts", + "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", + "@/modules/health": "./src/modules/health/index.ts", + "@/modules/health/handlers": "./src/modules/health/handlers.ts", + "@/modules/health/routes": "./src/modules/health/routes.ts", + "@/modules/users": "./src/modules/users/index.ts", + "@/modules/users/handlers": "./src/modules/users/handlers.ts", + "@/modules/users/routes": "./src/modules/users/routes.ts", + "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", + "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", + "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", + "@/jobs/index": "./src/jobs/index.ts", + "@/jobs/worker": "./src/jobs/worker.ts", + "hono": "npm:hono", + "hono/cors": "npm:hono/cors", + "hono/dev": "npm:hono/dev", + "hono/ws": "npm:hono/ws", + "@hono/zod-openapi": "npm:@hono/zod-openapi", + "@hono/swagger-ui": "npm:@hono/swagger-ui", + "@hono/zod-validator": "npm:@hono/zod-validator", + "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", + "better-auth": "npm:better-auth", + "better-auth/adapters": "npm:better-auth/adapters", + "better-auth/plugins": "npm:better-auth/plugins", + "ioredis": "npm:ioredis", + "bullmq": "npm:bullmq", + "pino": "npm:pino", + "pino-pretty": "npm:pino-pretty", + "hono-pino": "npm:hono-pino", + "stoker": "npm:stoker", + "stoker/middlewares": "npm:stoker/middlewares", + "stoker/openapi": "npm:stoker/openapi", + "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", + "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", + "zod": "npm:zod", + "drizzle-zod": "npm:drizzle-zod", + "resend": "npm:resend", + "dotenv": "npm:dotenv", + "dotenv-expand": "npm:dotenv-expand", + "@axiomhq/pino": "npm:@axiomhq/pino" + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any", "no-non-null-assertion"] + } + }, + "test": { + "include": ["src/**/*.test.ts"] + } +} +``` + +No `@repo/*` entries — those resolve through workspace bare specifiers. No +`drizzle-kit` import — that's a CLI tool, used via `npx` or +`deno run -A npm:drizzle-kit`. + +--- + +## Steps + +### Step 1 — Add `deno.json` to each workspace member + +Create 4 files: + +- `packages/shared/deno.json` +- `packages/db/deno.json` +- `packages/email-templates/deno.json` +- `apps/backend/deno.json` (migrate from root) + +**Risk**: low. Adding config files is additive — nothing breaks yet. + +### Step 2 — Update root `deno.json` + +Add `"workspace"` field, strip `@repo/*` and `/*` entries from `imports`, move +lint/fmt/test config to member files, strip backend tasks. + +**Risk**: medium. If workspace resolution doesn't kick in correctly, +`deno check` and `deno test` will fail with module-not-found errors. + +### Step 3 — Verify `deno check` across all members + +```bash +deno check +``` + +This should type-check all workspace members. If a member has type errors (e.g. +`packages/db` importing `drizzle-orm` but not declaring it in its own +`imports`), fix per-member config. + +### Step 4 — Verify `deno test` across all members + +```bash +deno test -A +``` + +Tests in `apps/backend`, `packages/shared`, `packages/email-templates` should +all run with per-member test configs. + +### Step 5 — Clean up orphaned files + +- Remove `packages/shared/vitest.config.ts` (Deno doesn't use it) +- Remove `packages/email-templates/vitest.config.ts` +- Remove `apps/backend/vitest.config.ts` (if it exists) +- Remove `apps/backend/package.json` — it only says `"type": "module"` which + Deno doesn't need (Deno treats `.ts` as ESM by default, `.js` inherits from + nearest `package.json` — but there's no `package.json` with `"type": "module"` + for Deno paths anymore) + - **BUT**: keep it if `npm:@better-auth/cli generate` or `drizzle-kit` needs + it to detect ESM +- Remove `apps/backend/tsconfig.json`, `packages/*/tsconfig.json` (orphaned from + old TypeScript setup) + +### Step 6 — Recreate `pnpm-workspace.yaml` + +If it was deleted, recreate it. Without it, `pnpm install` can't resolve +`workspace:*` protocol in the frontend's `package.json`. + +```yaml +packages: + - "packages/*" +``` + +The frontend's `package.json` lists `@repo/shared` and `@repo/db` as workspace +dependencies but never imports them in source. Optionally remove those unused +deps from `apps/frontend/package.json` — simplifies the pnpm workspace and +eliminates the dependency entirely. + +### Step 7 — Update scripts + +- `scripts/new-module.sh`: the scaffolded `handlers.test.ts` already uses + `@std/testing/bdd`, so no change needed for tests. Still references + `biome.json` for formatting — keep since Biome is still used for frontend. +- `scripts/setup.sh`: already updated for Deno. + +### Step 8 — Update root `package.json` + +Remove `"engines": { "deno": ">=2.0.0" }` — Deno doesn't read `engines` from +`package.json`. Keep the `"packageManager"` field for pnpm. + +--- + +## Edge cases & risks + +### 1. Module resolution order + +Deno resolves bare specifiers in this order (workspace members → import map → +npm): + +1. Check if specifier matches a workspace member's `name` +2. Check the local `deno.json` `imports` +3. Check the root `deno.json` `imports` + +So `apps/backend` can still use `@repo/shared` even if it's not in anyone's +`imports` — it resolves through step 1 (workspace member name). **This is the +core mechanism** that lets us remove `@repo/*` from the root import map. + +### 2. Duplicate npm import declarations + +Both `packages/db` and `apps/backend` import `drizzle-orm` in their own +`deno.json`. Deno should deduplicate these to a single npm install. Verify with +`deno info` after the migration. + +### 3. Backend `package.json` removal + +The backend `package.json` is minimal +(`{"name": "backend", "type": "module", "private": true}`). It exists solely for +the pnpm workspace (so `pnpm -r` finds it) and for `type: "module"` (so +Node-based tools like drizzle-kit detect ESM). + +**If we keep it**: no change needed. It's inert for Deno. **If we remove it**: +`npm:@better-auth/cli generate` might fail if it probes `type` from +`package.json`. The safe call is to keep it. + +### 4. `pnpm-workspace.yaml` status + +Currently missing. The frontend `package.json` lists +`"@repo/shared": "workspace:*"` and `"@repo/db": "workspace:*"` but never +imports them in source code. Two options: + +- **Keep deps + recreate yaml**: simplest, no code changes +- **Remove unused deps**: cleaner but requires verifying nothing at build time + depends on them (better-auth might resolve types through them) + +### 5. `deno check` on member packages + +`packages/shared` and `packages/email-templates` are pure TypeScript with zero +deps. `deno check` should pass instantly. + +`packages/db` depends on `drizzle-orm` and `postgres`. If these npm packages +don't have Deno-compatible type declarations, `deno check` might fail. The +current root import map already has these entries, so they're already working — +moving them to `packages/db/deno.json` shouldn't change resolution. + +`apps/backend` has the most complex dep graph. Moving its imports out of root +scope and into its own `deno.json` should be transparent since workspace member +imports take priority. + +### 6. LSP behavior + +With per-package `deno.json` files, VS Code's Deno LSP should correctly resolve +imports within each workspace member using that member's config. This is the +main UX improvement over the flat approach. + +--- + +## Summary + +| Item | Effort | Risk | +| --------------------------------- | -------------- | -------------- | +| Create 4 `deno.json` files | Small | Low | +| Restructure root `deno.json` | Medium | Medium | +| Remove orphaned config files | Small | Low | +| Recreate `pnpm-workspace.yaml` | Trivial | Low | +| Verify `deno check` + `deno test` | Medium | Medium | +| **Total** | **~2-3 hours** | **Low-Medium** | + +The migration is straightforward: add `deno.json` to each package, strip them +from the root, verify resolution. The risk is in edge cases (npm type +declarations, pnpm workspace sync, LSP cache invalidation). diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 1ce3309..c59c0d5 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -4,12 +4,12 @@ Get your app live in 15 minutes. ## TL;DR -| Part | Where | Cost | -|------|-------|------| -| Backend | Railway, Render, or any VPS | $5-20/mo | -| Frontend | Vercel | Free | -| Database | Supabase, Neon, or Railway | Free tier available | -| Redis | Upstash or Railway | Free tier available | +| Part | Where | Cost | +| -------- | --------------------------- | ------------------- | +| Backend | Railway, Render, or any VPS | $5-20/mo | +| Frontend | Vercel | Free | +| Database | Supabase, Neon, or Railway | Free tier available | +| Redis | Upstash or Railway | Free tier available | ## 1. Database @@ -92,7 +92,7 @@ sudo systemctl reload caddy 2. Import at [vercel.com/new](https://vercel.com/new) 3. Set: - **Root Directory**: `apps/frontend` - - **Build Command**: `cd ../.. && pnpm build:frontend` + - **Build Command**: `cd ../.. && pnpm --filter frontend build` - **Output Directory**: `dist` 4. Add environment variable: - `VITE_API_URL` = `https://api.yourdomain.com` @@ -146,7 +146,8 @@ If you're using the job queue, run the worker alongside your API: Add a second service pointing to the same repo: -- **Start Command**: `pnpm --filter backend jobs` +- **Start Command**: + `deno run --env-file=.env -A apps/backend/src/jobs/worker.ts` ### VPS @@ -157,7 +158,7 @@ docker run -d \ -e DATABASE_URL="..." \ -e REDIS_URL="..." \ api \ - node src/jobs/worker.js + deno run -A src/jobs/worker.ts ``` --- @@ -207,14 +208,11 @@ curl https://api.yourdomain.com/api/health ## Troubleshooting -**502 Bad Gateway** -→ Backend isn't running. Check logs: `docker logs api` +**502 Bad Gateway** → Backend isn't running. Check logs: `docker logs api` -**CORS errors** -→ Make sure `FRONTEND_URL` matches exactly (including https) +**CORS errors** → Make sure `FRONTEND_URL` matches exactly (including https) -**Auth not working** -→ Check `BETTER_AUTH_URL` matches your API domain +**Auth not working** → Check `BETTER_AUTH_URL` matches your API domain -**Database connection refused** -→ Whitelist your server IP in your database provider's dashboard +**Database connection refused** → Whitelist your server IP in your database +provider's dashboard diff --git a/docs/PHILOSOPHY.md b/docs/PHILOSOPHY.md index 3487e1f..756ac1a 100644 --- a/docs/PHILOSOPHY.md +++ b/docs/PHILOSOPHY.md @@ -1,18 +1,30 @@ # Engineering Philosophy -The beliefs that anchor every decision in this codebase. Not rules — beliefs. Rules can be followed without understanding. Beliefs change how you see the problem. +The beliefs that anchor every decision in this codebase. Not rules — beliefs. +Rules can be followed without understanding. Beliefs change how you see the +problem. --- ## Simple Is Not Easy -Simple code is the hardest code to write. It requires you to fully understand the problem before you touch the keyboard, make real choices about what to leave out, and resist the pull of every interesting abstraction that presents itself along the way. +Simple code is the hardest code to write. It requires you to fully understand +the problem before you touch the keyboard, make real choices about what to leave +out, and resist the pull of every interesting abstraction that presents itself +along the way. -Complex code is easy. Every time you're uncertain, you add a layer. Every time requirements might change, you add an interface. Every time a pattern seems reusable, you generalize it. The result is a codebase that looks thorough and feels impressive — until you have to change something, and suddenly you're paying for every assumption you ever deferred. +Complex code is easy. Every time you're uncertain, you add a layer. Every time +requirements might change, you add an interface. Every time a pattern seems +reusable, you generalize it. The result is a codebase that looks thorough and +feels impressive — until you have to change something, and suddenly you're +paying for every assumption you ever deferred. -Simple systems scale. Not because they're small, but because they're honest. They say exactly what they do, do exactly what they say, and leave no hidden state for the next person to stumble into. +Simple systems scale. Not because they're small, but because they're honest. +They say exactly what they do, do exactly what they say, and leave no hidden +state for the next person to stumble into. -**The discipline:** before you add something, ask what it would cost to remove it. If you can't answer that, you don't understand it well enough to add it. +**The discipline:** before you add something, ask what it would cost to remove +it. If you can't answer that, you don't understand it well enough to add it. --- @@ -20,21 +32,37 @@ Simple systems scale. Not because they're small, but because they're honest. The Tools change. The principles that make tools worth choosing don't. -ESLint becomes Biome. Prisma becomes Drizzle. Radix becomes Base UI. The tools in this codebase are specific choices made at a specific time — they can and will be replaced. What doesn't change is the question we ask when choosing them: does this tool help the next person understand what's happening, or does it add machinery they have to learn before they can read the code? +ESLint becomes Biome. Prisma becomes Drizzle. Radix becomes Base UI. The tools +in this codebase are specific choices made at a specific time — they can and +will be replaced. What doesn't change is the question we ask when choosing them: +does this tool help the next person understand what's happening, or does it add +machinery they have to learn before they can read the code? -The best tool is often the one that teaches you the least on the way to the thing you were actually trying to build. +The best tool is often the one that teaches you the least on the way to the +thing you were actually trying to build. --- ## Progressive Abstraction -Start with the simplest implementation that solves the real problem in front of you. +Start with the simplest implementation that solves the real problem in front of +you. -Not the problem you might have in six months. Not the pattern you read about last week. The exact problem you have right now, in the simplest form that handles it correctly. +Not the problem you might have in six months. Not the pattern you read about +last week. The exact problem you have right now, in the simplest form that +handles it correctly. -Abstractions earn their existence by appearing more than once. A `tryInfra` wrapper exists because every repository function needed to catch infrastructure errors and every one needed to do it the same way. A `Result` type exists because every fallible function needed a way to return failures without throwing. Neither of these was designed upfront — they crystallized after the pattern repeated. +Abstractions earn their existence by appearing more than once. A `tryInfra` +wrapper exists because every repository function needed to catch infrastructure +errors and every one needed to do it the same way. A `Result` type exists +because every fallible function needed a way to return failures without +throwing. Neither of these was designed upfront — they crystallized after the +pattern repeated. -The danger of premature abstraction is that it looks like wisdom. It has interfaces and generics and thoughtful naming. It also locks in assumptions about how the system will be used before you know how it will be used. Those assumptions are wrong more often than they're right. +The danger of premature abstraction is that it looks like wisdom. It has +interfaces and generics and thoughtful naming. It also locks in assumptions +about how the system will be used before you know how it will be used. Those +assumptions are wrong more often than they're right. **The discipline:** write it twice before you abstract it. @@ -42,39 +70,77 @@ The danger of premature abstraction is that it looks like wisdom. It has interfa ## Craftsmanship -Code is read far more than it is written. The primary audience for the code you're writing is not the computer — it's the person who reads it next, which will usually be you. +Code is read far more than it is written. The primary audience for the code +you're writing is not the computer — it's the person who reads it next, which +will usually be you. -Craftsmanship is not cleverness. A clever solution is one that only the person who wrote it understands. A crafted solution is one where the next reader can see exactly what it does and why — where the variable names, the function boundaries, and the comment choices all reduce the cognitive load rather than increasing it. +Craftsmanship is not cleverness. A clever solution is one that only the person +who wrote it understands. A crafted solution is one where the next reader can +see exactly what it does and why — where the variable names, the function +boundaries, and the comment choices all reduce the cognitive load rather than +increasing it. -This shows up in small things: naming a variable `userId` instead of `id`, splitting a 40-line handler into a use-case and a handler, writing a comment that says *why* instead of *what*. None of these changes are individually significant. Accumulated across a codebase, they're the difference between code that new teammates can navigate in a day and code that requires a guided tour. +This shows up in small things: naming a variable `userId` instead of `id`, +splitting a 40-line handler into a use-case and a handler, writing a comment +that says _why_ instead of _what_. None of these changes are individually +significant. Accumulated across a codebase, they're the difference between code +that new teammates can navigate in a day and code that requires a guided tour. --- ## Human Experience First -Performance, reliability, and security are not engineering concerns — they're user concerns. Every millisecond of latency is a real person waiting. Every 500 error is a real person seeing a broken page. Every security gap is a real person's data at risk. +Performance, reliability, and security are not engineering concerns — they're +user concerns. Every millisecond of latency is a real person waiting. Every 500 +error is a real person seeing a broken page. Every security gap is a real +person's data at risk. -This doesn't mean premature optimization. It means holding onto the fact that the code is not the end product. The experience of the person using what you built is the end product. Engineering choices that look neutral — how errors are returned, how loading states are handled, how auth tokens are managed — are all choices that eventually affect a real human in a real moment. +This doesn't mean premature optimization. It means holding onto the fact that +the code is not the end product. The experience of the person using what you +built is the end product. Engineering choices that look neutral — how errors are +returned, how loading states are handled, how auth tokens are managed — are all +choices that eventually affect a real human in a real moment. -Build with that person in mind. Not as an abstraction, but as a real person with limited time and zero patience for things that don't work. +Build with that person in mind. Not as an abstraction, but as a real person with +limited time and zero patience for things that don't work. --- ## Influences -These are the thinkers and practitioners whose work shaped how we think about building software. +These are the thinkers and practitioners whose work shaped how we think about +building software. -**[37signals / Jason Fried / DHH](https://37signals.com)** — the source of "simple is not easy". Their argument: most complexity is chosen, not necessary. Constraints produce better software. Doing less, deliberately, is a form of quality. Read *Getting Real*, *REWORK*, and *Shape Up*. +**[37signals / Jason Fried / DHH](https://37signals.com)** — the source of +"simple is not easy". Their argument: most complexity is chosen, not necessary. +Constraints produce better software. Doing less, deliberately, is a form of +quality. Read _Getting Real_, _REWORK_, and _Shape Up_. -**[The Primeagen](https://www.youtube.com/@ThePrimeagen)** — the performance-first mindset and the insistence on understanding what your code actually does. Not what you think it does, not what the abstraction says it does — what it *actually* does, at the level of the machine. That clarity transfers upward to architecture. +**[The Primeagen](https://www.youtube.com/@ThePrimeagen)** — the +performance-first mindset and the insistence on understanding what your code +actually does. Not what you think it does, not what the abstraction says it does +— what it _actually_ does, at the level of the machine. That clarity transfers +upward to architecture. -**[Charity Majors](https://charity.wtf)** — observability and the idea that you're not done when it's deployed, you're done when you understand how it behaves in production. Also: earned opinions. Don't recommend things you haven't been burned by. Write from scar tissue. +**[Charity Majors](https://charity.wtf)** — observability and the idea that +you're not done when it's deployed, you're done when you understand how it +behaves in production. Also: earned opinions. Don't recommend things you haven't +been burned by. Write from scar tissue. -**[Vercel](https://vercel.com)** — what great developer experience looks like in practice. The best DX is the one that makes the right path the easy path. Zero-config defaults. Errors that tell you what to do. A tool that gets out of your way. +**[Vercel](https://vercel.com)** — what great developer experience looks like in +practice. The best DX is the one that makes the right path the easy path. +Zero-config defaults. Errors that tell you what to do. A tool that gets out of +your way. -**[Netflix Engineering](https://netflixtechblog.com)** — what quality engineering looks like at scale. Not because we're at that scale, but because the principles — chaos engineering, observability, culture of ownership — apply at any size. You own what you ship, past the merge button. +**[Netflix Engineering](https://netflixtechblog.com)** — what quality +engineering looks like at scale. Not because we're at that scale, but because +the principles — chaos engineering, observability, culture of ownership — apply +at any size. You own what you ship, past the merge button. -**[Cal Newport](https://calnewport.com)** — the case for depth over breadth, and for protecting the cognitive space required to do hard things well. Multi-tasking is a myth. Half-done work is worse than not-started work. Finish what you start. +**[Cal Newport](https://calnewport.com)** — the case for depth over breadth, and +for protecting the cognitive space required to do hard things well. +Multi-tasking is a myth. Half-done work is worse than not-started work. Finish +what you start. --- @@ -82,10 +148,19 @@ These are the thinkers and practitioners whose work shaped how we think about bu Every decision in this codebase connects back to these beliefs: -- **The 40-line Result type** instead of Effect or neverthrow — progressive abstraction. We own exactly what we need, nothing more. -- **Biome over ESLint + Prettier** — tools over principles, but the simpler tool. One binary, one config, zero plugin conflicts. -- **Base UI over Radix** — the tool that stays out of our way. No z-index negotiation, no portal fighting. -- **`tryInfra` as the single catch boundary** — craftsmanship. One place where infrastructure errors are caught, so there's one place to look when something breaks. -- **The module scaffolder** — making the right path the easy path. Convention enforced by a script is better than convention enforced by a wiki. - -When you're making a decision that isn't covered by the existing patterns, come back to these beliefs. They're the question to ask before you add something. +- **The 40-line Result type** instead of Effect or neverthrow — progressive + abstraction. We own exactly what we need, nothing more. +- **Deno lint/fmt (backend) + Biome (frontend) over ESLint + Prettier** — tools + over principles, but the simpler tool. Deno's built-in linter and formatter + handle the backend; Biome handles the frontend. One binary each, no plugin + conflicts. +- **Base UI over Radix** — the tool that stays out of our way. No z-index + negotiation, no portal fighting. +- **`tryInfra` as the single catch boundary** — craftsmanship. One place where + infrastructure errors are caught, so there's one place to look when something + breaks. +- **The module scaffolder** — making the right path the easy path. Convention + enforced by a script is better than convention enforced by a wiki. + +When you're making a decision that isn't covered by the existing patterns, come +back to these beliefs. They're the question to ask before you add something. diff --git a/docs/WRITING.md b/docs/WRITING.md index b71c2f5..85cd8be 100644 --- a/docs/WRITING.md +++ b/docs/WRITING.md @@ -1,72 +1,110 @@ # Writing Style Guide -This document covers how we write — articles, docs, READMEs, commit messages, everything that a human reads rather than a compiler. +This document covers how we write — articles, docs, READMEs, commit messages, +everything that a human reads rather than a compiler. --- ## The Four Teachers -These four writers shaped the voice we're going for. Each one contributes something specific. +These four writers shaped the voice we're going for. Each one contributes +something specific. -### William Zinsser — *On Writing Well* +### William Zinsser — _On Writing Well_ -Every sentence earns its place. Cut the word that restates what the previous word already said. No "in order to" when "to" works. No "the reason is because" when "because" works. No filler clause that delays the real sentence. +Every sentence earns its place. Cut the word that restates what the previous +word already said. No "in order to" when "to" works. No "the reason is because" +when "because" works. No filler clause that delays the real sentence. The reader's time is not yours to waste. -Read: *On Writing Well* (the whole book, not a summary) +Read: _On Writing Well_ (the whole book, not a summary) ### Charity Majors — earned opinions, no hedging -Write from scar tissue, not authority. Don't say *"you should handle errors as values"* — say *"I've debugged enough midnight incidents where a thrown exception vanished into a catch-all to know: if it can fail, the type needs to say so."* +Write from scar tissue, not authority. Don't say _"you should handle errors as +values"_ — say _"I've debugged enough midnight incidents where a thrown +exception vanished into a catch-all to know: if it can fail, the type needs to +say so."_ -State opinions flatly. Qualifiers only when the qualifier is the point. "It depends" is never a final answer — follow it with the actual answer for the context you're in. +State opinions flatly. Qualifiers only when the qualifier is the point. "It +depends" is never a final answer — follow it with the actual answer for the +context you're in. -Read: [charity.wtf](https://charity.wtf) — particularly her posts on observability and engineering management +Read: [charity.wtf](https://charity.wtf) — particularly her posts on +observability and engineering management ### Casey Muratori — show the machine -Don't describe the pattern, show the actual code, then explain *why* the shape of it is right. Walk the reader through the thinking as it happened, not the cleaned-up post-hoc version. Treat the reader as someone smart who just hasn't seen this yet. +Don't describe the pattern, show the actual code, then explain _why_ the shape +of it is right. Walk the reader through the thinking as it happened, not the +cleaned-up post-hoc version. Treat the reader as someone smart who just hasn't +seen this yet. -The tutorial that works is the one that never skips the step where things were confusing. +The tutorial that works is the one that never skips the step where things were +confusing. -Read: [Handmade Hero](https://handmadehero.org) — watch how he narrates code as he writes it +Read: [Handmade Hero](https://handmadehero.org) — watch how he narrates code as +he writes it ### Cal Newport — one claim per section, defended, closed -Each section has one argument. You know what the section argues before you read it. You know it's done when the argument lands. No meandering. No "as we've seen above". No recap of what was just said. +Each section has one argument. You know what the section argues before you read +it. You know it's done when the argument lands. No meandering. No "as we've seen +above". No recap of what was just said. -Read: *Deep Work* and *Digital Minimalism* — notice the structure more than the content +Read: _Deep Work_ and _Digital Minimalism_ — notice the structure more than the +content --- ## The Synthesized Voice -These rules apply whether you're writing an article, a PR description, or a section of the README. +These rules apply whether you're writing an article, a PR description, or a +section of the README. -**Open with the real problem.** Make the reader feel the friction before you sell the solution. If the opener is "here is a solution", you've skipped the reason anyone should care. +**Open with the real problem.** Make the reader feel the friction before you +sell the solution. If the opener is "here is a solution", you've skipped the +reason anyone should care. -**Use "I" and "you" freely.** This is knowledge transfer between two people, not a whitepaper. "We recommend" is the voice of a committee. "I use this because" is the voice of someone who's actually been there. +**Use "I" and "you" freely.** This is knowledge transfer between two people, not +a whitepaper. "We recommend" is the voice of a committee. "I use this because" +is the voice of someone who's actually been there. -**Show code early and let it do work.** Prose explains what the code alone can't — the *why*, the tradeoffs, the history. If you're writing prose to describe something fully expressible in code, write the code instead. +**Show code early and let it do work.** Prose explains what the code alone can't +— the _why_, the tradeoffs, the history. If you're writing prose to describe +something fully expressible in code, write the code instead. **Short paragraphs.** One idea. Three sentences max. Move on. -**No throat-clearing introductions.** The first sentence of each section is already in the middle of the thought. "In this section we will explore..." is a sentence that could always be deleted. +**No throat-clearing introductions.** The first sentence of each section is +already in the middle of the thought. "In this section we will explore..." is a +sentence that could always be deleted. -**Last sentence of a section closes the claim.** Not "and that's why X matters" — that's a restatement. The last sentence draws the conclusion or sets up the next section directly. +**Last sentence of a section closes the claim.** Not "and that's why X matters" +— that's a restatement. The last sentence draws the conclusion or sets up the +next section directly. --- ## On Technical Writing Specifically -**Don't soften opinions you've earned.** If you've debugged something, built it, shipped it, and formed a view — state the view. Hedging with "this might work for some teams" when you mean "this worked for me and here's why" is a disservice to the reader. +**Don't soften opinions you've earned.** If you've debugged something, built it, +shipped it, and formed a view — state the view. Hedging with "this might work +for some teams" when you mean "this worked for me and here's why" is a +disservice to the reader. -**Explain the tradeoff, not just the choice.** Every technical decision has a cost. Readers trust writing that acknowledges what was given up more than writing that only defends what was gained. +**Explain the tradeoff, not just the choice.** Every technical decision has a +cost. Readers trust writing that acknowledges what was given up more than +writing that only defends what was gained. -**Real code from the real repo.** No pseudocode, no cleaned-up examples that don't compile. If the example changed to make it prettier for the article, say so. If it didn't, don't say so — just show it. +**Real code from the real repo.** No pseudocode, no cleaned-up examples that +don't compile. If the example changed to make it prettier for the article, say +so. If it didn't, don't say so — just show it. -**Name alternatives you rejected and say why.** "I chose X" is half the information. "I chose X over Y because Z" is the part that helps someone in a different context make their own call. +**Name alternatives you rejected and say why.** "I chose X" is half the +information. "I chose X over Y because Z" is the part that helps someone in a +different context make their own call. --- @@ -82,4 +120,7 @@ These rules apply whether you're writing an article, a PR description, or a sect ## Applied to This Project -When writing `docs/DECISIONS.md`, `docs/PATTERNS.md`, inline code comments, or PR descriptions, the same rules apply at a smaller scale. A good comment is a single sentence that says *why*, not *what*. A good PR description opens with the problem, not the solution. +When writing `docs/DECISIONS.md`, `docs/PATTERNS.md`, inline code comments, or +PR descriptions, the same rules apply at a smaller scale. A good comment is a +single sentence that says _why_, not _what_. A good PR description opens with +the problem, not the solution. diff --git a/package.json b/package.json index f1df854..a3a0dad 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,23 @@ { - "name": "orcta-stack", - "version": "1.0.0", - "license": "MIT", - "private": true, - "type": "module", - "scripts": { - "setup": "./scripts/setup.sh", - "dev": "pnpm build:packages && pnpm --parallel --filter \"./apps/*\" dev", - "dev:backend": "pnpm build:packages && pnpm --filter backend dev", - "dev:frontend": "pnpm build:packages && pnpm --filter frontend dev", - "build": "pnpm build:packages && pnpm build:apps", - "build:packages": "pnpm -r --filter \"@repo/*\" build", - "build:apps": "pnpm -r --filter ./apps/frontend build", - "typecheck": "pnpm --recursive typecheck", - "lint": "pnpm --recursive lint", - "test": "pnpm --recursive test", - "clean": "rm -rf apps/*/dist packages/*/dist node_modules/.cache", - "db:generate": "pnpm --filter @repo/db db:generate", - "db:migrate": "pnpm --filter @repo/db db:migrate", - "db:studio": "pnpm --filter @repo/db db:studio", - "new:module": "./scripts/new-module.sh" - }, - "devDependencies": { - "@biomejs/biome": "2.3.7", - "@types/node": "^25.0.3", - "tsx": "^4.21.0", - "typescript": "^5.9.3" - }, - "dependencies": { - "zod": "4.2.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "packageManager": "pnpm@9.15.0" + "name": "orcta-stack", + "version": "1.0.0", + "license": "MIT", + "private": true, + "type": "module", + "scripts": { + "setup": "./scripts/setup.sh", + "dev:frontend": "pnpm --filter frontend dev", + "build": "pnpm -r --filter ./apps/frontend build", + "lint": "deno lint", + "fmt": "deno fmt", + "clean": "rm -rf apps/*/dist packages/*/dist node_modules/.cache" + }, + "devDependencies": { + "@biomejs/biome": "2.3.7" + }, + "dependencies": {}, + "engines": { + "deno": ">=2.0.0" + }, + "packageManager": "pnpm@9.15.0" } diff --git a/packages/db/deno.json b/packages/db/deno.json new file mode 100644 index 0000000..97fa8e1 --- /dev/null +++ b/packages/db/deno.json @@ -0,0 +1,23 @@ +{ + "name": "@repo/db", + "version": "0.1.0", + "exports": { + ".": "./src/index.ts", + "./schema": "./src/schema/index.ts" + }, + "imports": { + "drizzle-orm": "npm:drizzle-orm", + "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", + "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", + "drizzle-zod": "npm:drizzle-zod", + "postgres": "npm:postgres", + "dotenv": "npm:dotenv", + "drizzle-kit": "npm:drizzle-kit" + }, + "exclude": ["node_modules"], + "lint": { + "rules": { + "exclude": ["no-slow-types"] + } + } +} diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts index 230ba39..431ecc6 100644 --- a/packages/db/drizzle.config.ts +++ b/packages/db/drizzle.config.ts @@ -1,14 +1,15 @@ +import process from "node:process"; import { config } from "dotenv"; import { defineConfig } from "drizzle-kit"; config({ path: "../../apps/backend/.env" }); export default defineConfig({ - schema: "./src/schema/*.ts", - out: "./migrations", - dialect: "postgresql", - dbCredentials: { - url: process.env.DATABASE_URL || "", - }, - verbose: true, - strict: true, + schema: "./src/schema/*.ts", + out: "./migrations", + dialect: "postgresql", + dbCredentials: { + url: process.env.DATABASE_URL || "", + }, + verbose: true, + strict: true, }); diff --git a/packages/db/migrations/0001_curious_fantastic_four.sql b/packages/db/migrations/0001_curious_fantastic_four.sql new file mode 100644 index 0000000..7643db2 --- /dev/null +++ b/packages/db/migrations/0001_curious_fantastic_four.sql @@ -0,0 +1,9 @@ +ALTER TABLE "users" ALTER COLUMN "role" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "users" ALTER COLUMN "role" SET DEFAULT 'buyer'::text;--> statement-breakpoint +DROP TYPE "public"."user_role";--> statement-breakpoint +CREATE TYPE "public"."user_role" AS ENUM('buyer', 'seller', 'admin');--> statement-breakpoint +ALTER TABLE "users" ALTER COLUMN "role" SET DEFAULT 'buyer'::"public"."user_role";--> statement-breakpoint +ALTER TABLE "users" ALTER COLUMN "role" SET DATA TYPE "public"."user_role" USING "role"::"public"."user_role";--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "two_factor_enabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "two_factor_secret" text;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "backup_codes" text; \ No newline at end of file diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json index 42d5486..5defa07 100644 --- a/packages/db/migrations/meta/0000_snapshot.json +++ b/packages/db/migrations/meta/0000_snapshot.json @@ -336,4 +336,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/packages/db/migrations/meta/0001_snapshot.json b/packages/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..add9c3a --- /dev/null +++ b/packages/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,359 @@ +{ + "id": "161a6497-08a5-4c29-8d47-6e0139642c4b", + "prevId": "d0249629-e758-4ae0-9c54-abb9f15f502f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_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 + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "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 with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "user_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'buyer'" + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "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 with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.user_role": { + "name": "user_role", + "schema": "public", + "values": [ + "buyer", + "seller", + "admin" + ] + } + }, + "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 472214b..7113f41 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1771546879712, "tag": "0000_loose_cassandra_nova", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1778377769195, + "tag": "0001_curious_fantastic_four", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json index 1b79a9a..2555c7a 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,50 +1,5 @@ { - "name": "@repo/db", - "version": "0.0.0", - "private": true, - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - }, - "./schema": { - "types": "./dist/schema/index.d.ts", - "import": "./dist/schema/index.js" - }, - "./types": { - "types": "./dist/types.d.ts", - "import": "./dist/types.js" - } - }, - "scripts": { - "prebuild": "rimraf dist tsconfig.tsbuildinfo", - "build": "tsc --build --verbose", - "dev": "tsc --build --watch", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit push", - "db:push:ci": "drizzle-kit push --force", - "db:studio": "drizzle-kit studio" - }, - "dependencies": { - "dotenv": "^16.6.1", - "drizzle-orm": "^0.44.7", - "drizzle-zod": "0.8.3", - "zod": "^4.2.1" - }, - "devDependencies": { - "@types/node": "^22.19.3", - "drizzle-kit": "^0.31.8", - "postgres": "^3.4.7", - "rimraf": "^6.1.2", - "typescript": "^5.9.3" - }, - "sideEffects": false, - "engines": { - "node": ">=20.0.0" - } + "name": "@repo/db", + "private": true, + "type": "module" } diff --git a/packages/db/src/schema/sessions.ts b/packages/db/src/schema/sessions.ts index 7e9a989..a9360d8 100644 --- a/packages/db/src/schema/sessions.ts +++ b/packages/db/src/schema/sessions.ts @@ -2,58 +2,58 @@ import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; import { users } from "./users.ts"; export const sessions = pgTable("sessions", { - id: text("id").primaryKey(), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - token: text("token").notNull().unique(), - expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), - ipAddress: text("ip_address"), - userAgent: text("user_agent"), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .defaultNow() - .notNull(), + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + token: text("token").notNull().unique(), + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), }); export const accounts = pgTable("accounts", { - id: text("id").primaryKey(), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - accountId: text("account_id").notNull(), - providerId: text("provider_id").notNull(), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - accessTokenExpiresAt: timestamp("access_token_expires_at", { - withTimezone: true, - }), - refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { - withTimezone: true, - }), - scope: text("scope"), - password: text("password"), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .defaultNow() - .notNull(), + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at", { + withTimezone: true, + }), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { + withTimezone: true, + }), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), }); export const verifications = pgTable("verifications", { - id: text("id").primaryKey(), - identifier: text("identifier").notNull(), - value: text("value").notNull(), - expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .defaultNow() - .notNull(), + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), }); export type Session = typeof sessions.$inferSelect; diff --git a/packages/db/src/schema/users.ts b/packages/db/src/schema/users.ts index c77dbd8..25c63da 100644 --- a/packages/db/src/schema/users.ts +++ b/packages/db/src/schema/users.ts @@ -1,15 +1,18 @@ import { boolean, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core"; import { createInsertSchema, createSelectSchema } from "drizzle-zod"; -export const userRoleEnum = pgEnum("user_role", ["user", "admin"]); +export const userRoleEnum = pgEnum("user_role", ["buyer", "seller", "admin"]); export const users = pgTable("users", { id: text("id").primaryKey(), email: text("email").notNull().unique(), name: text("name").notNull(), image: text("image"), - role: userRoleEnum("role").default("user").notNull(), + role: userRoleEnum("role").default("buyer").notNull(), emailVerified: boolean("email_verified").default(false).notNull(), + twoFactorEnabled: boolean("two_factor_enabled").default(false).notNull(), + twoFactorSecret: text("two_factor_secret"), + backupCodes: text("backup_codes"), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index eacc29f..eeff6d0 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -1,8 +1,8 @@ -import type { Account, Session } from "./schema/sessions.js"; -import type { InsertUser, User } from "./schema/users.js"; +import type { Account, Session } from "./schema/sessions.ts"; +import type { InsertUser, User } from "./schema/users.ts"; // Re-export schema types -export type { User, InsertUser, Session, Account }; +export type { Account, InsertUser, Session, User }; // Database-specific types export type UserRole = "user" | "admin"; diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json deleted file mode 100644 index e1890a0..0000000 --- a/packages/db/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src", - "baseUrl": ".", - "paths": { - "@repo/db": ["./src/index.ts"], - "@repo/db/schema": ["./src/schema/index.ts"], - "@repo/db/types": ["./src/types.ts"], - "@repo/db/dtos": ["./src/dtos/index.ts"] - }, - "composite": true, - "declaration": true, - "declarationMap": true, - "module": "ESNext", - "moduleResolution": "node", - "target": "ES2022", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "allowImportingTsExtensions": true, - "rewriteRelativeImportExtensions": true, - "noEmit": false, - "verbatimModuleSyntax": true - }, - "include": ["src"], - "exclude": ["node_modules", "dist", "migrations"] -} diff --git a/packages/email-templates/deno.json b/packages/email-templates/deno.json new file mode 100644 index 0000000..0e01e0f --- /dev/null +++ b/packages/email-templates/deno.json @@ -0,0 +1,6 @@ +{ + "name": "@repo/email-templates", + "version": "0.1.0", + "exports": "./src/index.ts", + "exclude": ["node_modules"] +} diff --git a/packages/email-templates/package.json b/packages/email-templates/package.json index 2e015f0..5ef76e7 100644 --- a/packages/email-templates/package.json +++ b/packages/email-templates/package.json @@ -1,30 +1,5 @@ { - "name": "@repo/email-templates", - "version": "1.0.0", - "private": true, - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - } - }, - "scripts": { - "prebuild": "rimraf dist tsconfig.tsbuildinfo", - "build": "tsc --build", - "dev": "tsc --build --watch", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "test": "vitest run" - }, - "devDependencies": { - "@types/node": "^22.19.3", - "typescript": "^5.9.3", - "vitest": "^4.0.16" - }, - "dependencies": { - "rimraf": "^6.1.2" - } + "name": "@repo/email-templates", + "private": true, + "type": "module" } diff --git a/packages/email-templates/src/__tests__/index.test.ts b/packages/email-templates/src/__tests__/index.test.ts index faeaaaa..cd3db46 100644 --- a/packages/email-templates/src/__tests__/index.test.ts +++ b/packages/email-templates/src/__tests__/index.test.ts @@ -1,117 +1,118 @@ -import { describe, expect, it } from "vitest"; -import { passwordResetEmail, welcomeEmail } from "../index.js"; +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; +import { passwordResetEmail, welcomeEmail } from "../index.ts"; // ─── welcomeEmail ───────────────────────────────────────────────────────────── describe("welcomeEmail", () => { - it("uses the user's name in the subject", () => { - const { subject } = welcomeEmail({ name: "Alice" }); - expect(subject).toBe("Welcome, Alice!"); - }); - - it("includes the user's name in the plain text body", () => { - const { text } = welcomeEmail({ name: "Alice" }); - expect(text).toContain("Alice"); - }); - - it("includes the standard sign-off in the plain text body", () => { - const { text } = welcomeEmail({ name: "Alice" }); - expect(text).toContain("— The Team"); - }); - - it("includes the action URL in the plain text body when provided", () => { - const { text } = welcomeEmail({ - name: "Bob", - actionUrl: "https://example.com/verify", - }); - expect(text).toContain("https://example.com/verify"); - }); - - it("omits the action URL from the plain text body when not provided", () => { - const { text } = welcomeEmail({ name: "Bob" }); - expect(text).not.toContain("http"); - }); - - it("includes a verify email button in the HTML when actionUrl is provided", () => { - const { html } = welcomeEmail({ - name: "Bob", - actionUrl: "https://example.com/verify", - }); - expect(html).toContain("Verify Email"); - expect(html).toContain("https://example.com/verify"); - }); - - it("omits the verify button from the HTML when actionUrl is not provided", () => { - const { html } = welcomeEmail({ name: "Bob" }); - expect(html).not.toContain("Verify Email"); - }); - - it("returns valid HTML structure", () => { - const { html } = welcomeEmail({ name: "Charlie" }); - expect(html).toContain(""); - expect(html).toContain(" { + const { subject } = welcomeEmail({ name: "Alice" }); + expect(subject).toBe("Welcome, Alice!"); + }); + + it("includes the user's name in the plain text body", () => { + const { text } = welcomeEmail({ name: "Alice" }); + expect(text).toContain("Alice"); + }); + + it("includes the standard sign-off in the plain text body", () => { + const { text } = welcomeEmail({ name: "Alice" }); + expect(text).toContain("— The Team"); + }); + + it("includes the action URL in the plain text body when provided", () => { + const { text } = welcomeEmail({ + name: "Bob", + actionUrl: "https://example.com/verify", + }); + expect(text).toContain("https://example.com/verify"); + }); + + it("omits the action URL from the plain text body when not provided", () => { + const { text } = welcomeEmail({ name: "Bob" }); + expect(text).not.toContain("http"); + }); + + it("includes a verify email button in the HTML when actionUrl is provided", () => { + const { html } = welcomeEmail({ + name: "Bob", + actionUrl: "https://example.com/verify", + }); + expect(html).toContain("Verify Email"); + expect(html).toContain("https://example.com/verify"); + }); + + it("omits the verify button from the HTML when actionUrl is not provided", () => { + const { html } = welcomeEmail({ name: "Bob" }); + expect(html).not.toContain("Verify Email"); + }); + + it("returns valid HTML structure", () => { + const { html } = welcomeEmail({ name: "Charlie" }); + expect(html).toContain(""); + expect(html).toContain(" { - it("has the correct subject line", () => { - const { subject } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(subject).toBe("Reset your password"); - }); - - it("includes the reset URL in the plain text body", () => { - const { text } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(text).toContain("https://example.com/reset"); - }); - - it("mentions the expiry period in the plain text body", () => { - const { text } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(text).toContain("1 hour"); - }); - - it("includes the user's name in the plain text body", () => { - const { text } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(text).toContain("Alice"); - }); - - it("includes the standard sign-off in the plain text body", () => { - const { text } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(text).toContain("— The Team"); - }); - - it("includes the reset URL in the HTML", () => { - const { html } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(html).toContain("https://example.com/reset"); - expect(html).toContain("Reset Password"); - }); - - it("returns valid HTML structure", () => { - const { html } = passwordResetEmail({ - name: "Alice", - actionUrl: "https://example.com/reset", - }); - expect(html).toContain(""); - expect(html).toContain(" { + const { subject } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(subject).toBe("Reset your password"); + }); + + it("includes the reset URL in the plain text body", () => { + const { text } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(text).toContain("https://example.com/reset"); + }); + + it("mentions the expiry period in the plain text body", () => { + const { text } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(text).toContain("1 hour"); + }); + + it("includes the user's name in the plain text body", () => { + const { text } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(text).toContain("Alice"); + }); + + it("includes the standard sign-off in the plain text body", () => { + const { text } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(text).toContain("— The Team"); + }); + + it("includes the reset URL in the HTML", () => { + const { html } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(html).toContain("https://example.com/reset"); + expect(html).toContain("Reset Password"); + }); + + it("returns valid HTML structure", () => { + const { html } = passwordResetEmail({ + name: "Alice", + actionUrl: "https://example.com/reset", + }); + expect(html).toContain(""); + expect(html).toContain(" + return `

${title}

@@ -20,33 +20,36 @@ ${body} } export function welcomeEmail({ name, actionUrl }: EmailProps): EmailTemplate { - const button = actionUrl - ? `

Verify Email

` - : ""; + const button = actionUrl + ? `

Verify Email

` + : ""; - return { - subject: `Welcome, ${name}!`, - html: baseTemplate( - `Welcome, ${name}!`, - `

Thanks for signing up.

${button}`, - ), - text: `Welcome, ${name}!\n\nThanks for signing up.${actionUrl ? `\n\nVerify: ${actionUrl}` : ""}\n\n— The Team`, - }; + return { + subject: `Welcome, ${name}!`, + html: baseTemplate( + `Welcome, ${name}!`, + `

Thanks for signing up.

${button}`, + ), + text: `Welcome, ${name}!\n\nThanks for signing up.${ + actionUrl ? `\n\nVerify: ${actionUrl}` : "" + }\n\n— The Team`, + }; } export function passwordResetEmail({ - name, - actionUrl, + name, + actionUrl, }: EmailProps): EmailTemplate { - return { - subject: "Reset your password", - html: baseTemplate( - "Reset your password", - ` + return { + subject: "Reset your password", + html: baseTemplate( + "Reset your password", + `

Hi ${name}, click below to reset your password. Link expires in 1 hour.

Reset Password

`, - ), - text: `Hi ${name},\n\nReset your password: ${actionUrl}\n\nLink expires in 1 hour.\n\n— The Team`, - }; + ), + text: + `Hi ${name},\n\nReset your password: ${actionUrl}\n\nLink expires in 1 hour.\n\n— The Team`, + }; } diff --git a/packages/email-templates/tsconfig.json b/packages/email-templates/tsconfig.json deleted file mode 100644 index 6fa276e..0000000 --- a/packages/email-templates/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "module": "node16", - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"] -} diff --git a/packages/email-templates/vitest.config.ts b/packages/email-templates/vitest.config.ts deleted file mode 100644 index dab44ae..0000000 --- a/packages/email-templates/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - globals: true, - environment: "node", - include: ["src/**/*.test.ts"], - coverage: { - reporter: ["text", "json", "html"], - }, - }, -}); diff --git a/packages/shared/deno.json b/packages/shared/deno.json new file mode 100644 index 0000000..f5accc3 --- /dev/null +++ b/packages/shared/deno.json @@ -0,0 +1,14 @@ +{ + "name": "@repo/shared", + "version": "0.1.0", + "exports": "./src/index.ts", + "imports": { + "zod": "npm:zod" + }, + "exclude": ["node_modules"], + "lint": { + "rules": { + "exclude": ["no-slow-types"] + } + } +} diff --git a/packages/shared/package.json b/packages/shared/package.json index 8e51a18..4d5f092 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,38 +1,5 @@ { - "name": "@repo/shared", - "version": "0.0.0", - "private": true, - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - }, - "./types": { - "types": "./dist/types.d.ts", - "import": "./dist/types.js" - }, - "./schemas": { - "types": "./dist/schemas.d.ts", - "import": "./dist/schemas.js" - } - }, - "scripts": { - "prebuild": "rimraf dist tsconfig.tsbuildinfo", - "build": "tsc --build", - "dev": "tsc --build --watch", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "test": "vitest run" - }, - "dependencies": { - "zod": "^4.2.1" - }, - "devDependencies": { - "rimraf": "^6.1.2", - "typescript": "^5.9.3", - "vitest": "^4.0.16" - } + "name": "@repo/shared", + "private": true, + "type": "module" } diff --git a/packages/shared/src/__tests__/result.test.ts b/packages/shared/src/__tests__/result.test.ts index 102423a..cb4a80c 100644 --- a/packages/shared/src/__tests__/result.test.ts +++ b/packages/shared/src/__tests__/result.test.ts @@ -1,200 +1,222 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import { - andThen, - andThenAsync, - err, - isErr, - isOk, - map, - match, - ok, - unwrap, -} from "../result.js"; + andThen, + andThenAsync, + err, + isErr, + isOk, + map, + match, + ok, + unwrap, +} from "../result.ts"; // ─── ok / err constructors ──────────────────────────────────────────────────── describe("ok", () => { - it("creates an Ok result with the provided value", () => { - const result = ok(42); - expect(result).toEqual({ ok: true, value: 42 }); - }); - - it("works with object values", () => { - const result = ok({ id: "1", name: "Alice" }); - expect(result.ok).toBe(true); - if (result.ok) expect(result.value.name).toBe("Alice"); - }); + it("creates an Ok result with the provided value", () => { + const result = ok(42); + expect(result).toEqual({ ok: true, value: 42 }); + }); + + it("works with object values", () => { + const result = ok({ id: "1", name: "Alice" }); + expect(result.ok).toBe(true); + if (result.ok) expect(result.value.name).toBe("Alice"); + }); }); describe("err", () => { - it("creates an Err result with the provided error", () => { - const result = err({ type: "NOT_FOUND" }); - expect(result).toEqual({ ok: false, error: { type: "NOT_FOUND" } }); - }); - - it("ok property is false", () => { - expect(err("oops").ok).toBe(false); - }); + it("creates an Err result with the provided error", () => { + const result = err({ type: "NOT_FOUND" }); + expect(result).toEqual({ ok: false, error: { type: "NOT_FOUND" } }); + }); + + it("ok property is false", () => { + expect(err("oops").ok).toBe(false); + }); }); // ─── Type guards ───────────────────────────────────────────────────────────── describe("isOk", () => { - it("returns true for Ok results", () => { - expect(isOk(ok("yes"))).toBe(true); - }); + it("returns true for Ok results", () => { + expect(isOk(ok("yes"))).toBe(true); + }); - it("returns false for Err results", () => { - expect(isOk(err("no"))).toBe(false); - }); + it("returns false for Err results", () => { + expect(isOk(err("no"))).toBe(false); + }); }); describe("isErr", () => { - it("returns true for Err results", () => { - expect(isErr(err("bad"))).toBe(true); - }); + it("returns true for Err results", () => { + expect(isErr(err("bad"))).toBe(true); + }); - it("returns false for Ok results", () => { - expect(isErr(ok("good"))).toBe(false); - }); + it("returns false for Ok results", () => { + expect(isErr(ok("good"))).toBe(false); + }); }); // ─── unwrap ────────────────────────────────────────────────────────────────── describe("unwrap", () => { - it("returns the value from an Ok result", () => { - expect(unwrap(ok("hello"))).toBe("hello"); - }); - - it("throws when called on an Err result", () => { - expect(() => unwrap(err("oh no"))).toThrow( - "Called unwrap on an Err result", - ); - }); + it("returns the value from an Ok result", () => { + expect(unwrap(ok("hello"))).toBe("hello"); + }); + + it("throws when called on an Err result", () => { + expect(() => unwrap(err("oh no"))).toThrow( + "Called unwrap on an Err result", + ); + }); }); // ─── map ───────────────────────────────────────────────────────────────────── describe("map", () => { - it("transforms the Ok value with the provided function", () => { - const result = map(ok(2), (n) => n * 3); - expect(result).toEqual(ok(6)); - }); - - it("passes Err through without calling the function", () => { - const fn = vi.fn(); - const result = map(err("fail"), fn); - expect(result).toEqual(err("fail")); - expect(fn).not.toHaveBeenCalled(); - }); - - it("allows changing the value type", () => { - const result = map(ok(42), (n) => String(n)); - expect(result).toEqual(ok("42")); - }); + it("transforms the Ok value with the provided function", () => { + const result = map(ok(2), (n) => n * 3); + expect(result).toEqual(ok(6)); + }); + + it("passes Err through without calling the function", () => { + let called = false; + const fn = () => { + called = true; + }; + const result = map(err("fail"), fn); + expect(result).toEqual(err("fail")); + expect(called).toBe(false); + }); + + it("allows changing the value type", () => { + const result = map(ok(42), (n) => String(n)); + expect(result).toEqual(ok("42")); + }); }); // ─── andThen ───────────────────────────────────────────────────────────────── describe("andThen", () => { - it("chains the function on an Ok result", () => { - const result = andThen(ok(4), (n) => ok(n * 2)); - expect(result).toEqual(ok(8)); - }); - - it("short-circuits on an Err result without calling the function", () => { - const fn = vi.fn(); - const result = andThen(err("first failure"), fn); - expect(result).toEqual(err("first failure")); - expect(fn).not.toHaveBeenCalled(); - }); - - it("propagates an Err returned from the chained function", () => { - const result = andThen(ok(0), (n) => - n === 0 ? err({ type: "DIVISION_BY_ZERO" }) : ok(1 / n), - ); - expect(result).toEqual(err({ type: "DIVISION_BY_ZERO" })); - }); - - it("chains multiple operations together", () => { - const double = (n: number) => ok(n * 2); - const addOne = (n: number) => ok(n + 1); - - const result = andThen(andThen(ok(3), double), addOne); - expect(result).toEqual(ok(7)); - }); + it("chains the function on an Ok result", () => { + const result = andThen(ok(4), (n) => ok(n * 2)); + expect(result).toEqual(ok(8)); + }); + + it("short-circuits on an Err result without calling the function", () => { + let called = false; + const fn = () => { + called = true; + return ok(0); + }; + const result = andThen(err("first failure"), fn); + expect(result).toEqual(err("first failure")); + expect(called).toBe(false); + }); + + it("propagates an Err returned from the chained function", () => { + const result = andThen( + ok(0), + (n) => n === 0 ? err({ type: "DIVISION_BY_ZERO" }) : ok(1 / n), + ); + expect(result).toEqual(err({ type: "DIVISION_BY_ZERO" })); + }); + + it("chains multiple operations together", () => { + const double = (n: number) => ok(n * 2); + const addOne = (n: number) => ok(n + 1); + + const result = andThen(andThen(ok(3), double), addOne); + expect(result).toEqual(ok(7)); + }); }); // ─── andThenAsync ───────────────────────────────────────────────────────────── describe("andThenAsync", () => { - it("chains an async function on an Ok result", async () => { - const result = await andThenAsync(ok(5), async (n) => ok(n * 2)); - expect(result).toEqual(ok(10)); - }); - - it("short-circuits on an Err result without calling the function", async () => { - const fn = vi.fn(); - const result = await andThenAsync(err("already failed"), fn); - expect(result).toEqual(err("already failed")); - expect(fn).not.toHaveBeenCalled(); - }); - - it("propagates an async Err from the chained function", async () => { - const result = await andThenAsync(ok("user"), async (_) => - err({ type: "CONFLICT", detail: "duplicate" }), - ); - expect(result).toEqual(err({ type: "CONFLICT", detail: "duplicate" })); - }); + it("chains an async function on an Ok result", async () => { + const result = await andThenAsync(ok(5), async (n) => ok(n * 2)); + expect(result).toEqual(ok(10)); + }); + + it("short-circuits on an Err result without calling the function", async () => { + let called = false; + const fn = async () => { + called = true; + return ok(0); + }; + const result = await andThenAsync(err("already failed"), fn); + expect(result).toEqual(err("already failed")); + expect(called).toBe(false); + }); + + it("propagates an async Err from the chained function", async () => { + const result = await andThenAsync( + ok("user"), + async (_) => err({ type: "CONFLICT", detail: "duplicate" }), + ); + expect(result).toEqual(err({ type: "CONFLICT", detail: "duplicate" })); + }); }); // ─── match ─────────────────────────────────────────────────────────────────── describe("match", () => { - it("calls ok handler on an Ok result", () => { - const output = match(ok(10), { - ok: (n) => `value is ${n}`, - err: (_) => "error", - }); - expect(output).toBe("value is 10"); - }); - - it("calls err handler on an Err result", () => { - const output = match(err({ type: "NOT_FOUND" }), { - ok: (_) => "found", - err: (e) => `missing: ${e.type}`, - }); - expect(output).toBe("missing: NOT_FOUND"); - }); - - it("allows ok and err handlers to return different types", () => { - // This tests the R1 | R2 type signature — the compiler allows distinct types. - const result = match( - ok(1) as ReturnType> | ReturnType>, - { - ok: (n) => n + 1, - err: (e) => e.toUpperCase(), - }, - ); - // result is number | string - expect(result).toBe(2); - }); - - it("does not call the err handler when result is Ok", () => { - const errHandler = vi.fn(() => "should not run"); - match(ok("success"), { - ok: (v) => v, - err: errHandler, - }); - expect(errHandler).not.toHaveBeenCalled(); - }); - - it("does not call the ok handler when result is Err", () => { - const okHandler = vi.fn(() => "should not run"); - match(err("oops"), { - ok: okHandler, - err: (e) => e, - }); - expect(okHandler).not.toHaveBeenCalled(); - }); + it("calls ok handler on an Ok result", () => { + const output = match(ok(10), { + ok: (n) => `value is ${n}`, + err: (_) => "error", + }); + expect(output).toBe("value is 10"); + }); + + it("calls err handler on an Err result", () => { + const output = match(err({ type: "NOT_FOUND" }), { + ok: (_) => "found", + err: (e) => `missing: ${e.type}`, + }); + expect(output).toBe("missing: NOT_FOUND"); + }); + + it("allows ok and err handlers to return different types", () => { + // This tests the R1 | R2 type signature — the compiler allows distinct types. + const result = match( + ok(1) as ReturnType> | ReturnType>, + { + ok: (n) => n + 1, + err: (e) => e.toUpperCase(), + }, + ); + // result is number | string + expect(result).toBe(2); + }); + + it("does not call the err handler when result is Ok", () => { + let called = false; + const errHandler = () => { + called = true; + return "should not run"; + }; + match(ok("success"), { + ok: (v) => v, + err: errHandler, + }); + expect(called).toBe(false); + }); + + it("does not call the ok handler when result is Err", () => { + let called = false; + const okHandler = () => { + called = true; + return "should not run"; + }; + match(err("oops"), { + ok: okHandler, + err: (e) => e, + }); + expect(called).toBe(false); + }); }); diff --git a/packages/shared/src/__tests__/schemas.test.ts b/packages/shared/src/__tests__/schemas.test.ts index 6cbf7b3..74e52c0 100644 --- a/packages/shared/src/__tests__/schemas.test.ts +++ b/packages/shared/src/__tests__/schemas.test.ts @@ -1,137 +1,137 @@ -import { describe, expect, it } from "vitest"; +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; import { z } from "zod"; import { - apiErrorSchema, - apiSuccessSchema, - idParamSchema, - paginationSchema, -} from "../schemas.js"; + apiErrorSchema, + apiSuccessSchema, + idParamSchema, + paginationSchema, +} from "../schemas.ts"; // ─── paginationSchema ───────────────────────────────────────────────────────── describe("paginationSchema", () => { - it("defaults page to 1 and limit to 20 when not provided", () => { - const result = paginationSchema.parse({}); - expect(result).toEqual({ page: 1, limit: 20 }); - }); - - it("coerces string numbers to integers", () => { - const result = paginationSchema.parse({ page: "3", limit: "50" }); - expect(result).toEqual({ page: 3, limit: 50 }); - }); - - it("rejects a limit greater than 100", () => { - expect(() => paginationSchema.parse({ limit: 101 })).toThrow(); - }); - - it("rejects non-positive page numbers", () => { - expect(() => paginationSchema.parse({ page: 0 })).toThrow(); - expect(() => paginationSchema.parse({ page: -1 })).toThrow(); - }); - - it("accepts limit of exactly 100", () => { - const result = paginationSchema.parse({ limit: 100 }); - expect(result.limit).toBe(100); - }); + it("defaults page to 1 and limit to 20 when not provided", () => { + const result = paginationSchema.parse({}); + expect(result).toEqual({ page: 1, limit: 20 }); + }); + + it("coerces string numbers to integers", () => { + const result = paginationSchema.parse({ page: "3", limit: "50" }); + expect(result).toEqual({ page: 3, limit: 50 }); + }); + + it("rejects a limit greater than 100", () => { + expect(() => paginationSchema.parse({ limit: 101 })).toThrow(); + }); + + it("rejects non-positive page numbers", () => { + expect(() => paginationSchema.parse({ page: 0 })).toThrow(); + expect(() => paginationSchema.parse({ page: -1 })).toThrow(); + }); + + it("accepts limit of exactly 100", () => { + const result = paginationSchema.parse({ limit: 100 }); + expect(result.limit).toBe(100); + }); }); // ─── idParamSchema ──────────────────────────────────────────────────────────── describe("idParamSchema", () => { - it("accepts a valid id string", () => { - const result = idParamSchema.parse({ id: "abc-123" }); - expect(result.id).toBe("abc-123"); - }); - - it("rejects an empty string id", () => { - expect(() => idParamSchema.parse({ id: "" })).toThrow(); - }); - - it("rejects a missing id field", () => { - expect(() => idParamSchema.parse({})).toThrow(); - }); + it("accepts a valid id string", () => { + const result = idParamSchema.parse({ id: "abc-123" }); + expect(result.id).toBe("abc-123"); + }); + + it("rejects an empty string id", () => { + expect(() => idParamSchema.parse({ id: "" })).toThrow(); + }); + + it("rejects a missing id field", () => { + expect(() => idParamSchema.parse({})).toThrow(); + }); }); // ─── apiSuccessSchema ───────────────────────────────────────────────────────── describe("apiSuccessSchema", () => { - it("validates a well-formed success response", () => { - const schema = apiSuccessSchema(z.string()); - const result = schema.parse({ success: true, data: "hello" }); - expect(result).toEqual({ success: true, data: "hello" }); - }); - - it("validates with an object data schema", () => { - const schema = apiSuccessSchema( - z.object({ id: z.string(), name: z.string() }), - ); - const result = schema.parse({ - success: true, - data: { id: "1", name: "Alice" }, - }); - expect(result.data.name).toBe("Alice"); - }); - - it("rejects when success is false", () => { - const schema = apiSuccessSchema(z.string()); - expect(() => schema.parse({ success: false, data: "hello" })).toThrow(); - }); - - it("rejects when data does not match the inner schema", () => { - const schema = apiSuccessSchema(z.number()); - expect(() => - schema.parse({ success: true, data: "not a number" }), - ).toThrow(); - }); - - it("rejects when data is missing", () => { - const schema = apiSuccessSchema(z.string()); - expect(() => schema.parse({ success: true })).toThrow(); - }); + it("validates a well-formed success response", () => { + const schema = apiSuccessSchema(z.string()); + const result = schema.parse({ success: true, data: "hello" }); + expect(result).toEqual({ success: true, data: "hello" }); + }); + + it("validates with an object data schema", () => { + const schema = apiSuccessSchema( + z.object({ id: z.string(), name: z.string() }), + ); + const result = schema.parse({ + success: true, + data: { id: "1", name: "Alice" }, + }); + expect(result.data.name).toBe("Alice"); + }); + + it("rejects when success is false", () => { + const schema = apiSuccessSchema(z.string()); + expect(() => schema.parse({ success: false, data: "hello" })).toThrow(); + }); + + it("rejects when data does not match the inner schema", () => { + const schema = apiSuccessSchema(z.number()); + expect(() => schema.parse({ success: true, data: "not a number" })) + .toThrow(); + }); + + it("rejects when data is missing", () => { + const schema = apiSuccessSchema(z.string()); + expect(() => schema.parse({ success: true })).toThrow(); + }); }); // ─── apiErrorSchema ─────────────────────────────────────────────────────────── describe("apiErrorSchema", () => { - it("validates a well-formed error response", () => { - const result = apiErrorSchema.parse({ - success: false, - error: { code: "NOT_FOUND", message: "Resource not found" }, - }); - expect(result.success).toBe(false); - expect(result.error.code).toBe("NOT_FOUND"); - }); - - it("accepts an optional details field", () => { - const result = apiErrorSchema.parse({ - success: false, - error: { - code: "VALIDATION_ERROR", - message: "Invalid input", - details: { field: "email", issue: "required" }, - }, - }); - expect(result.error.details).toEqual({ field: "email", issue: "required" }); - }); - - it("rejects when success is true", () => { - expect(() => - apiErrorSchema.parse({ - success: true, - error: { code: "OOPS", message: "bad" }, - }), - ).toThrow(); - }); - - it("rejects when code is missing", () => { - expect(() => - apiErrorSchema.parse({ success: false, error: { message: "oops" } }), - ).toThrow(); - }); - - it("rejects when message is missing", () => { - expect(() => - apiErrorSchema.parse({ success: false, error: { code: "ERR" } }), - ).toThrow(); - }); + it("validates a well-formed error response", () => { + const result = apiErrorSchema.parse({ + success: false, + error: { code: "NOT_FOUND", message: "Resource not found" }, + }); + expect(result.success).toBe(false); + expect(result.error.code).toBe("NOT_FOUND"); + }); + + it("accepts an optional details field", () => { + const result = apiErrorSchema.parse({ + success: false, + error: { + code: "VALIDATION_ERROR", + message: "Invalid input", + details: { field: "email", issue: "required" }, + }, + }); + expect(result.error.details).toEqual({ field: "email", issue: "required" }); + }); + + it("rejects when success is true", () => { + expect(() => + apiErrorSchema.parse({ + success: true, + error: { code: "OOPS", message: "bad" }, + }) + ).toThrow(); + }); + + it("rejects when code is missing", () => { + expect(() => + apiErrorSchema.parse({ success: false, error: { message: "oops" } }) + ).toThrow(); + }); + + it("rejects when message is missing", () => { + expect(() => + apiErrorSchema.parse({ success: false, error: { code: "ERR" } }) + ).toThrow(); + }); }); diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 83883cc..22001d3 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,3 @@ -export * from "./result.js"; -export * from "./schemas.js"; -export * from "./types.js"; +export * from "./result.ts"; +export * from "./schemas.ts"; +export * from "./types.ts"; diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts index 703f02a..bf18e88 100644 --- a/packages/shared/src/schemas.ts +++ b/packages/shared/src/schemas.ts @@ -3,8 +3,8 @@ import { z } from "zod"; // ─── Pagination ────────────────────────────────────────────────────────────── export const paginationSchema = z.object({ - page: z.coerce.number().int().positive().default(1), - limit: z.coerce.number().int().positive().max(100).default(20), + page: z.coerce.number().int().positive().default(1), + limit: z.coerce.number().int().positive().max(100).default(20), }); export type PaginationInput = z.infer; @@ -17,10 +17,10 @@ export type PaginationInput = z.infer; * const rows = await db.query.posts.findMany({ limit, offset }); */ export function paginationQuery(input: PaginationInput) { - return { - limit: input.limit, - offset: (input.page - 1) * input.limit, - }; + return { + limit: input.limit, + offset: (input.page - 1) * input.limit, + }; } /** @@ -35,21 +35,21 @@ export function paginationQuery(input: PaginationInput) { * return c.json(success(paginate(rows, Number(count), input)), OK); */ export function paginate(items: T[], total: number, input: PaginationInput) { - return { - items, - meta: { - page: input.page, - limit: input.limit, - total, - totalPages: Math.ceil(total / input.limit), - hasMore: input.page * input.limit < total, - }, - }; + return { + items, + meta: { + page: input.page, + limit: input.limit, + total, + totalPages: Math.ceil(total / input.limit), + hasMore: input.page * input.limit < total, + }, + }; } // Common params export const idParamSchema = z.object({ - id: z.string().min(1), + id: z.string().min(1), }); // Type exports @@ -70,19 +70,19 @@ export type IdParam = z.infer; // Wraps any data schema in { success: true, data: T }. export const apiSuccessSchema = (dataSchema: T) => - z.object({ - success: z.literal(true), - data: dataSchema, - }); + z.object({ + success: z.literal(true), + data: dataSchema, + }); // Fixed shape for all error responses: { success: false, error: { code, message, details? } }. export const apiErrorSchema = z.object({ - success: z.literal(false), - error: z.object({ - code: z.string(), - message: z.string(), - details: z.record(z.string(), z.unknown()).optional(), - }), + success: z.literal(false), + error: z.object({ + code: z.string(), + message: z.string(), + details: z.record(z.string(), z.unknown()).optional(), + }), }); /** @@ -95,15 +95,15 @@ export const apiErrorSchema = z.object({ * } */ export const paginatedSuccessSchema = (itemSchema: T) => - apiSuccessSchema( - z.object({ - items: z.array(itemSchema), - meta: z.object({ - page: z.number(), - limit: z.number(), - total: z.number(), - totalPages: z.number(), - hasMore: z.boolean(), - }), - }), - ); + apiSuccessSchema( + z.object({ + items: z.array(itemSchema), + meta: z.object({ + page: z.number(), + limit: z.number(), + total: z.number(), + totalPages: z.number(), + hasMore: z.boolean(), + }), + }), + ); diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 91f8eb6..9561577 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -1,16 +1,16 @@ // API Response Types export interface ApiSuccess { - success: true; - data: T; + success: true; + data: T; } export interface ApiError { - success: false; - error: { - code: string; - message: string; - details?: Record; - }; + success: false; + error: { + code: string; + message: string; + details?: Record; + }; } export type ApiResponse = ApiSuccess | ApiError; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json deleted file mode 100644 index 6fa276e..0000000 --- a/packages/shared/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "module": "node16", - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"] -} diff --git a/packages/shared/vitest.config.ts b/packages/shared/vitest.config.ts deleted file mode 100644 index dab44ae..0000000 --- a/packages/shared/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - globals: true, - environment: "node", - include: ["src/**/*.test.ts"], - coverage: { - reporter: ["text", "json", "html"], - }, - }, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index e1a1cd0..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,7279 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - zod: - specifier: 4.2.1 - version: 4.2.1 - devDependencies: - '@biomejs/biome': - specifier: 2.3.7 - version: 2.3.7 - '@types/node': - specifier: ^25.0.3 - version: 25.3.0 - tsx: - specifier: ^4.21.0 - version: 4.21.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - apps/backend: - dependencies: - '@asteasolutions/zod-to-openapi': - specifier: 8.0.0 - version: 8.0.0(zod@4.2.1) - '@aws-sdk/client-s3': - specifier: ^3.750.0 - version: 3.994.0 - '@aws-sdk/s3-request-presigner': - specifier: ^3.750.0 - version: 3.994.0 - '@axiomhq/pino': - specifier: ^1.4.0 - version: 1.4.0 - '@hono/node-server': - specifier: ^1.19.7 - version: 1.19.9(hono@4.12.0) - '@hono/node-ws': - specifier: ^1.2.0 - version: 1.3.0(@hono/node-server@1.19.9(hono@4.12.0))(hono@4.12.0) - '@hono/swagger-ui': - specifier: ^0.5.3 - version: 0.5.3(hono@4.12.0) - '@hono/zod-openapi': - specifier: ^1.2.0 - version: 1.2.2(hono@4.12.0)(zod@4.2.1) - '@hono/zod-validator': - specifier: ^0.7.6 - version: 0.7.6(hono@4.12.0)(zod@4.2.1) - '@repo/db': - specifier: workspace:* - version: link:../../packages/db - '@repo/email-templates': - specifier: workspace:* - version: link:../../packages/email-templates - '@repo/shared': - specifier: workspace:* - version: link:../../packages/shared - '@scalar/hono-api-reference': - specifier: ^0.9.30 - version: 0.9.44(hono@4.12.0) - better-auth: - specifier: ^1.4.9 - version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.0.18) - bullmq: - specifier: ^5.34.0 - version: 5.69.3 - dotenv: - specifier: ^16.6.1 - version: 16.6.1 - dotenv-expand: - specifier: ^12.0.3 - version: 12.0.3 - drizzle-orm: - specifier: ^0.44.7 - version: 0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8) - drizzle-zod: - specifier: 0.8.3 - version: 0.8.3(drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8))(zod@4.2.1) - hono: - specifier: ^4.11.3 - version: 4.12.0 - hono-pino: - specifier: ^0.7.2 - version: 0.7.2(hono@4.12.0)(pino@9.14.0) - ioredis: - specifier: ^5.4.1 - version: 5.9.3 - neverthrow: - specifier: ^8.2.0 - version: 8.2.0 - pg: - specifier: ^8.16.3 - version: 8.18.0 - pino: - specifier: ^9.14.0 - version: 9.14.0 - pino-pretty: - specifier: ^13.1.3 - version: 13.1.3 - postgres: - specifier: ^3.4.7 - version: 3.4.8 - resend: - specifier: ^6.6.0 - version: 6.9.2 - stoker: - specifier: 2.0.1 - version: 2.0.1(@asteasolutions/zod-to-openapi@8.0.0(zod@4.2.1))(@hono/zod-openapi@1.2.2(hono@4.12.0)(zod@4.2.1))(hono@4.12.0)(openapi3-ts@4.5.0) - zod: - specifier: ^4.2.1 - version: 4.2.1 - devDependencies: - '@biomejs/biome': - specifier: 2.3.7 - version: 2.3.7 - '@swc/cli': - specifier: ^0.7.9 - version: 0.7.10(@swc/core@1.15.11) - '@swc/core': - specifier: ^1.15.8 - version: 1.15.11 - '@types/node': - specifier: ^22.19.3 - version: 22.19.11 - '@types/pg': - specifier: ^8.16.0 - version: 8.16.0 - '@vitest/ui': - specifier: ^4.0.16 - version: 4.0.18(vitest@4.0.18) - cross-env: - specifier: ^7.0.3 - version: 7.0.3 - drizzle-kit: - specifier: ^0.31.8 - version: 0.31.9 - tsc-alias: - specifier: ^1.8.16 - version: 1.8.16 - tsx: - specifier: ^4.21.0 - version: 4.21.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.16 - version: 4.0.18(@types/node@22.19.11)(@vitest/ui@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - - apps/frontend: - dependencies: - '@base-ui/react': - specifier: ^1.2.0 - version: 1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@better-fetch/fetch': - specifier: ^1.1.18 - version: 1.1.21 - '@repo/db': - specifier: workspace:* - version: link:../../packages/db - '@repo/shared': - specifier: workspace:* - version: link:../../packages/shared - '@tailwindcss/vite': - specifier: ^4.1.17 - version: 4.2.0(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) - '@tanstack/react-form': - specifier: ^1.0.0 - version: 1.28.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-query': - specifier: ^5.90.7 - version: 5.90.21(react@19.2.4) - '@tanstack/react-router': - specifier: ^1.134.13 - version: 1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-router-devtools': - specifier: ^1.134.13 - version: 1.161.1(@tanstack/react-router@1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.161.1)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - better-auth: - specifier: ^1.3.34 - version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.0.18) - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - lucide-react: - specifier: ^0.553.0 - version: 0.553.0(react@19.2.4) - react: - specifier: ^19.1.1 - version: 19.2.4 - react-dom: - specifier: ^19.1.1 - version: 19.2.4(react@19.2.4) - sonner: - specifier: ^2.0.7 - version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - tailwind-merge: - specifier: ^3.3.1 - version: 3.5.0 - tailwindcss: - specifier: ^4.1.17 - version: 4.2.0 - vite-tsconfig-paths: - specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) - zod: - specifier: ^4.2.1 - version: 4.2.1 - zustand: - specifier: ^5.0.8 - version: 5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) - devDependencies: - '@tanstack/router-plugin': - specifier: ^1.134.14 - version: 1.161.1(@tanstack/react-router@1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) - '@types/react': - specifier: ^19.1.16 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.1.9 - version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^5.0.4 - version: 5.1.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) - tw-animate-css: - specifier: ^1.4.0 - version: 1.4.0 - typescript: - specifier: ~5.9.3 - version: 5.9.3 - vite: - specifier: ^7.1.7 - version: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - - packages/db: - dependencies: - dotenv: - specifier: ^16.6.1 - version: 16.6.1 - drizzle-orm: - specifier: ^0.44.7 - version: 0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8) - drizzle-zod: - specifier: 0.8.3 - version: 0.8.3(drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8))(zod@4.2.1) - zod: - specifier: ^4.2.1 - version: 4.2.1 - devDependencies: - '@types/node': - specifier: ^22.19.3 - version: 22.19.11 - drizzle-kit: - specifier: ^0.31.8 - version: 0.31.9 - postgres: - specifier: ^3.4.7 - version: 3.4.8 - rimraf: - specifier: ^6.1.2 - version: 6.1.3 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/email-templates: - dependencies: - rimraf: - specifier: ^6.1.2 - version: 6.1.3 - devDependencies: - '@types/node': - specifier: ^22.19.3 - version: 22.19.11 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.16 - version: 4.0.18(@types/node@22.19.11)(@vitest/ui@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - - packages/shared: - dependencies: - zod: - specifier: ^4.2.1 - version: 4.2.1 - devDependencies: - rimraf: - specifier: ^6.1.2 - version: 6.1.3 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.16 - version: 4.0.18(@types/node@25.3.0)(@vitest/ui@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - -packages: - - '@asteasolutions/zod-to-openapi@8.0.0': - resolution: {integrity: sha512-C56hBPiraeSWUNLz8mB5Z0/0LdfaFD5d6WB/+hdUg0MiC7egTgvWRGh3M3jZ3CRl03l/NJWnmv5D3OUAz+JGeg==} - peerDependencies: - zod: ^4.0.0 - - '@asteasolutions/zod-to-openapi@8.4.1': - resolution: {integrity: sha512-WmJUsFINbnWxGvHSd16aOjgKf+5GsfdxruO2YDLcgplsidakCauik1lhlk83YDH06265Yd1XtUyF24o09uygpw==} - peerDependencies: - zod: ^4.0.0 - - '@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.994.0': - resolution: {integrity: sha512-zIVQt/XfE2zTFrcPEf8R+KRaRD1++XHMPRhxXM2kVA6NA6Aq/cFCUyYOYYwSbWLF/XeToaX1auYGn3IoZKruPQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/client-sso@3.993.0': - resolution: {integrity: sha512-VLUN+wIeNX24fg12SCbzTUBnBENlL014yMKZvRhPkcn4wHR6LKgNrjsG3fZ03Xs0XoKaGtNFi1VVrq666sGBoQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/core@3.973.11': - resolution: {integrity: sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/crc64-nvme@3.972.0': - resolution: {integrity: sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-env@3.972.9': - resolution: {integrity: sha512-ZptrOwQynfupubvcngLkbdIq/aXvl/czdpEG8XJ8mN8Nb19BR0jaK0bR+tfuMU36Ez9q4xv7GGkHFqEEP2hUUQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-http@3.972.11': - resolution: {integrity: sha512-hECWoOoH386bGr89NQc9vA/abkGf5TJrMREt+lhNcnSNmoBS04fK7vc3LrJBSQAUGGVj0Tz3f4dHB3w5veovig==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-ini@3.972.9': - resolution: {integrity: sha512-zr1csEu9n4eDiHMTYJabX1mDGuGLgjgUnNckIivvk43DocJC9/f6DefFrnUPZXE+GHtbW50YuXb+JIxKykU74A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-login@3.972.9': - resolution: {integrity: sha512-m4RIpVgZChv0vWS/HKChg1xLgZPpx8Z+ly9Fv7FwA8SOfuC6I3htcSaBz2Ch4bneRIiBUhwP4ziUo0UZgtJStQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-node@3.972.10': - resolution: {integrity: sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-process@3.972.9': - resolution: {integrity: sha512-gOWl0Fe2gETj5Bk151+LYKpeGi2lBDLNu+NMNpHRlIrKHdBmVun8/AalwMK8ci4uRfG5a3/+zvZBMpuen1SZ0A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-sso@3.972.9': - resolution: {integrity: sha512-ey7S686foGTArvFhi3ifQXmgptKYvLSGE2250BAQceMSXZddz7sUSNERGJT2S7u5KIe/kgugxrt01hntXVln6w==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.972.9': - resolution: {integrity: sha512-8LnfS76nHXoEc9aRRiMMpxZxJeDG0yusdyo3NvPhCgESmBUgpMa4luhGbClW5NoX/qRcGxxM6Z/esqANSNMTow==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-bucket-endpoint@3.972.3': - resolution: {integrity: sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-expect-continue@3.972.3': - resolution: {integrity: sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-flexible-checksums@3.972.9': - resolution: {integrity: sha512-E663+r/UQpvF3aJkD40p5ZANVQFsUcbE39jifMtN7wc0t1M0+2gJJp3i75R49aY9OiSX5lfVyPUNjN/BNRCCZA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-host-header@3.972.3': - resolution: {integrity: sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-location-constraint@3.972.3': - resolution: {integrity: sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-logger@3.972.3': - resolution: {integrity: sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-recursion-detection@3.972.3': - resolution: {integrity: sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-sdk-s3@3.972.11': - resolution: {integrity: sha512-Qr0T7ZQTRMOuR6ahxEoJR1thPVovfWrKB2a6KBGR+a8/ELrFodrgHwhq50n+5VMaGuLtGhHiISU3XGsZmtmVXQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-ssec@3.972.3': - resolution: {integrity: sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-user-agent@3.972.11': - resolution: {integrity: sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/nested-clients@3.993.0': - resolution: {integrity: sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/region-config-resolver@3.972.3': - resolution: {integrity: sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/s3-request-presigner@3.994.0': - resolution: {integrity: sha512-g/jYc++IunLJZpeyLJrbC39XAf57BYjWjKkxRQlwj5fC90Scg4t/2FS0BV2u/U9UZX5HqpQQahI60tPEN84JqQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.994.0': - resolution: {integrity: sha512-8y04Lv497KKd7f2TVlm2RaKQaNfnY17ZH8d3m+7sW/3R3BhZvHgWQZyqTb/vcN2ERz1YAnWx6woJyB3ZNFvakw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.993.0': - resolution: {integrity: sha512-+35g4c+8r7sB9Sjp1KPdM8qxGn6B/shBjJtEUN4e+Edw9UEQlZKIzioOGu3UAbyE0a/s450LdLZr4wbJChtmww==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/types@3.973.1': - resolution: {integrity: sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==} - 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.993.0': - resolution: {integrity: sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-endpoints@3.994.0': - resolution: {integrity: sha512-L2obUBw4ACMMd1F/SG5LdfPyZ0xJNs9Maifwr3w0uWO+4YvHmk9FfRskfSfE/SLZ9S387oSZ+1xiP7BfVCP/Og==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-format-url@3.972.3': - resolution: {integrity: sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g==} - 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.3': - resolution: {integrity: sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==} - - '@aws-sdk/util-user-agent-node@3.972.9': - resolution: {integrity: sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA==} - engines: {node: '>=20.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/xml-builder@3.972.5': - resolution: {integrity: sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA==} - engines: {node: '>=20.0.0'} - - '@aws/lambda-invoke-store@0.2.3': - resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==} - engines: {node: '>=18.0.0'} - - '@axiomhq/js@1.4.0': - resolution: {integrity: sha512-wC5x1ud/QJMstrjpicATkyY8+ZVWEl4WlXMtA5EZf7hkj0+b191yv4yynLxLEfr/MveXora9m6CWdJq4DsbcAg==} - engines: {node: '>=20'} - - '@axiomhq/pino@1.4.0': - resolution: {integrity: sha512-7ujZM1kqbA98BWl8ltWdTLSDE7+67nOvR30/hm8P6omISjaQV0xgtdtZtDGe/BH2VmZeQWPGfAI1FnUNYC3CtQ==} - engines: {node: '>=20'} - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.0': - resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@base-ui/react@1.2.0': - resolution: {integrity: sha512-O6aEQHcm+QyGTFY28xuwRD3SEJGZOBDpyjN2WvpfWYFVhg+3zfXPysAILqtM0C1kWC82MccOE/v1j+GHXE4qIw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - - '@base-ui/utils@0.2.5': - resolution: {integrity: sha512-oYC7w0gp76RI5MxprlGLV0wze0SErZaRl3AAkeP3OnNB/UBMb6RqNf6ZSIlxOc9Qp68Ab3C2VOcJQyRs7Xc7Vw==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - - '@better-auth/core@1.4.18': - resolution: {integrity: sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg==} - peerDependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - better-call: 1.1.8 - jose: ^6.1.0 - kysely: ^0.28.5 - nanostores: ^1.0.1 - - '@better-auth/telemetry@1.4.18': - resolution: {integrity: sha512-e5rDF8S4j3Um/0LIVATL2in9dL4lfO2fr2v1Wio4qTMRbfxqnUDTa+6SZtwdeJrbc4O+a3c+IyIpjG9Q/6GpfQ==} - peerDependencies: - '@better-auth/core': 1.4.18 - - '@better-auth/utils@0.3.0': - resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} - - '@better-fetch/fetch@1.1.21': - resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} - - '@biomejs/biome@2.3.7': - resolution: {integrity: sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==} - engines: {node: '>=14.21.3'} - hasBin: true - - '@biomejs/cli-darwin-arm64@2.3.7': - resolution: {integrity: sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [darwin] - - '@biomejs/cli-darwin-x64@2.3.7': - resolution: {integrity: sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [darwin] - - '@biomejs/cli-linux-arm64-musl@2.3.7': - resolution: {integrity: sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-arm64@2.3.7': - resolution: {integrity: sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-x64-musl@2.3.7': - resolution: {integrity: sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-linux-x64@2.3.7': - resolution: {integrity: sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-win32-arm64@2.3.7': - resolution: {integrity: sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [win32] - - '@biomejs/cli-win32-x64@2.3.7': - resolution: {integrity: sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [win32] - - '@borewit/text-codec@0.2.1': - resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} - - '@drizzle-team/brocli@0.10.2': - resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - - '@esbuild-kit/core-utils@3.3.2': - resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} - deprecated: 'Merged into tsx: https://tsx.is' - - '@esbuild-kit/esm-loader@2.6.5': - resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} - deprecated: 'Merged into tsx: https://tsx.is' - - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.18.20': - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.18.20': - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.18.20': - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.18.20': - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.18.20': - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.18.20': - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.18.20': - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.18.20': - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.18.20': - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.18.20': - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.18.20': - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.18.20': - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.18.20': - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.18.20': - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.18.20': - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.18.20': - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - 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'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.18.20': - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - 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'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.18.20': - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - 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'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.18.20': - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.18.20': - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.18.20': - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.18.20': - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@floating-ui/core@1.7.4': - resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} - - '@floating-ui/dom@1.7.5': - resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} - - '@floating-ui/react-dom@2.1.7': - resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - - '@hono/node-server@1.19.9': - resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: ^4 - - '@hono/node-ws@1.3.0': - resolution: {integrity: sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q==} - engines: {node: '>=18.14.1'} - peerDependencies: - '@hono/node-server': ^1.19.2 - hono: ^4.6.0 - - '@hono/swagger-ui@0.5.3': - resolution: {integrity: sha512-Hn90DOOJ62ICJQplQvCDVpi9Jcn6EhtRaiffyJIS53wA5RmRLtMCDQGVc0bor8vQD7JIwpkweWjs+3cycp+IvA==} - peerDependencies: - hono: '>=4.0.0' - - '@hono/zod-openapi@1.2.2': - resolution: {integrity: sha512-va6vsL23wCJ1d0Vd+vGL1XOt+wPwItxirYafuhlW9iC2MstYr2FvsI7mctb45eBTjZfkqB/3LYDJEppPjOEiHw==} - engines: {node: '>=16.0.0'} - peerDependencies: - hono: '>=4.3.6' - zod: ^4.0.0 - - '@hono/zod-validator@0.7.6': - resolution: {integrity: sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==} - peerDependencies: - hono: '>=3.9.0' - zod: ^3.25.0 || ^4.0.0 - - '@ioredis/commands@1.5.0': - resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} - cpu: [arm64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} - cpu: [x64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} - cpu: [arm64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} - cpu: [arm] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} - cpu: [x64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} - cpu: [x64] - os: [win32] - - '@napi-rs/nice-android-arm-eabi@1.1.1': - resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} - engines: {node: '>= 10'} - cpu: [arm] - os: [android] - - '@napi-rs/nice-android-arm64@1.1.1': - resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@napi-rs/nice-darwin-arm64@1.1.1': - resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@napi-rs/nice-darwin-x64@1.1.1': - resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@napi-rs/nice-freebsd-x64@1.1.1': - resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': - resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@napi-rs/nice-linux-arm64-gnu@1.1.1': - resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@napi-rs/nice-linux-arm64-musl@1.1.1': - resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@napi-rs/nice-linux-ppc64-gnu@1.1.1': - resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} - engines: {node: '>= 10'} - cpu: [ppc64] - os: [linux] - - '@napi-rs/nice-linux-riscv64-gnu@1.1.1': - resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - - '@napi-rs/nice-linux-s390x-gnu@1.1.1': - resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} - engines: {node: '>= 10'} - cpu: [s390x] - os: [linux] - - '@napi-rs/nice-linux-x64-gnu@1.1.1': - resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@napi-rs/nice-linux-x64-musl@1.1.1': - resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@napi-rs/nice-openharmony-arm64@1.1.1': - resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [openharmony] - - '@napi-rs/nice-win32-arm64-msvc@1.1.1': - resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@napi-rs/nice-win32-ia32-msvc@1.1.1': - resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@napi-rs/nice-win32-x64-msvc@1.1.1': - resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@napi-rs/nice@1.1.1': - resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} - engines: {node: '>= 10'} - - '@noble/ciphers@2.1.1': - resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} - engines: {node: '>= 20.19.0'} - - '@noble/hashes@2.0.1': - resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} - engines: {node: '>= 20.19.0'} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - - '@rolldown/pluginutils@1.0.0-rc.3': - resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - - '@rollup/rollup-android-arm-eabi@4.57.1': - resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.57.1': - resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.57.1': - resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.57.1': - resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.57.1': - resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.57.1': - resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.57.1': - resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.57.1': - resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.57.1': - resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-musl@4.57.1': - resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-ppc64-musl@4.57.1': - resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.57.1': - resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.57.1': - resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.57.1': - resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.57.1': - resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-openbsd-x64@4.57.1': - resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.57.1': - resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.57.1': - resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.57.1': - resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.57.1': - resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.57.1': - resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} - cpu: [x64] - os: [win32] - - '@scalar/core@0.3.41': - resolution: {integrity: sha512-IPgiHOSGBDfBcJELbev0lo+ZHc8Q/vA82neX0Ax1iHNGtf/munH+qN5I+p9rGRS60IRQAwABlUo31Y3Gw1c0EA==} - engines: {node: '>=20'} - - '@scalar/helpers@0.2.15': - resolution: {integrity: sha512-hMHXejGFVOS4HwCo7C2qddChuvMJs3sEOALo7gNOvwLS4dGLrW8flbSglDki4ttyremlKQstP5WJuPxmHQU3sA==} - engines: {node: '>=20'} - - '@scalar/hono-api-reference@0.9.44': - resolution: {integrity: sha512-NusQ3S/LYKmEMOwc5kbKi6Can1b0iHTL/145CxfT5klEXtazhzdiXtYNiPNS99vGYKjub7+oaFR/HXJz4y/w2w==} - engines: {node: '>=20'} - peerDependencies: - hono: ^4.11.5 - - '@scalar/types@0.6.6': - resolution: {integrity: sha512-nr3m23p5MnGy4Wb4JFT7aA+jzvYSs/AS40NUEoQMBE1IwtuvG5gtLL0uu6kWpDq4UAfrWGntlAQNX7G8X9D4sg==} - engines: {node: '>=20'} - - '@sindresorhus/is@5.6.0': - resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} - engines: {node: '>=14.16'} - - '@smithy/abort-controller@4.2.8': - resolution: {integrity: sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==} - engines: {node: '>=18.0.0'} - - '@smithy/chunked-blob-reader-native@4.2.1': - resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} - engines: {node: '>=18.0.0'} - - '@smithy/chunked-blob-reader@5.2.0': - resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} - engines: {node: '>=18.0.0'} - - '@smithy/config-resolver@4.4.6': - resolution: {integrity: sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==} - engines: {node: '>=18.0.0'} - - '@smithy/core@3.23.2': - resolution: {integrity: sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA==} - engines: {node: '>=18.0.0'} - - '@smithy/credential-provider-imds@4.2.8': - resolution: {integrity: sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-codec@4.2.8': - resolution: {integrity: sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-browser@4.2.8': - resolution: {integrity: sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-config-resolver@4.3.8': - resolution: {integrity: sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-node@4.2.8': - resolution: {integrity: sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-universal@4.2.8': - resolution: {integrity: sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==} - engines: {node: '>=18.0.0'} - - '@smithy/fetch-http-handler@5.3.9': - resolution: {integrity: sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-blob-browser@4.2.9': - resolution: {integrity: sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-node@4.2.8': - resolution: {integrity: sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-stream-node@4.2.8': - resolution: {integrity: sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==} - engines: {node: '>=18.0.0'} - - '@smithy/invalid-dependency@4.2.8': - resolution: {integrity: sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==} - 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.0': - resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} - engines: {node: '>=18.0.0'} - - '@smithy/md5-js@4.2.8': - resolution: {integrity: sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-content-length@4.2.8': - resolution: {integrity: sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-endpoint@4.4.16': - resolution: {integrity: sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-retry@4.4.33': - resolution: {integrity: sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-serde@4.2.9': - resolution: {integrity: sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-stack@4.2.8': - resolution: {integrity: sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==} - engines: {node: '>=18.0.0'} - - '@smithy/node-config-provider@4.3.8': - resolution: {integrity: sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==} - engines: {node: '>=18.0.0'} - - '@smithy/node-http-handler@4.4.10': - resolution: {integrity: sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==} - engines: {node: '>=18.0.0'} - - '@smithy/property-provider@4.2.8': - resolution: {integrity: sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==} - engines: {node: '>=18.0.0'} - - '@smithy/protocol-http@5.3.8': - resolution: {integrity: sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-builder@4.2.8': - resolution: {integrity: sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-parser@4.2.8': - resolution: {integrity: sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==} - engines: {node: '>=18.0.0'} - - '@smithy/service-error-classification@4.2.8': - resolution: {integrity: sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==} - engines: {node: '>=18.0.0'} - - '@smithy/shared-ini-file-loader@4.4.3': - resolution: {integrity: sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.3.8': - resolution: {integrity: sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.11.5': - resolution: {integrity: sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ==} - engines: {node: '>=18.0.0'} - - '@smithy/types@4.12.0': - resolution: {integrity: sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==} - engines: {node: '>=18.0.0'} - - '@smithy/url-parser@4.2.8': - resolution: {integrity: sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-base64@4.3.0': - resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-browser@4.2.0': - resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-node@4.2.1': - resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-buffer-from@4.2.0': - resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} - engines: {node: '>=18.0.0'} - - '@smithy/util-config-provider@4.2.0': - resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-browser@4.3.32': - resolution: {integrity: sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-node@4.2.35': - resolution: {integrity: sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q==} - engines: {node: '>=18.0.0'} - - '@smithy/util-endpoints@3.2.8': - resolution: {integrity: sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-hex-encoding@4.2.0': - resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-middleware@4.2.8': - resolution: {integrity: sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==} - engines: {node: '>=18.0.0'} - - '@smithy/util-retry@4.2.8': - resolution: {integrity: sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-stream@4.5.12': - resolution: {integrity: sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-uri-escape@4.2.0': - resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@4.2.0': - resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-waiter@4.2.8': - resolution: {integrity: sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==} - engines: {node: '>=18.0.0'} - - '@smithy/uuid@1.1.0': - resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} - engines: {node: '>=18.0.0'} - - '@stablelib/base64@1.0.1': - resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@swc/cli@0.7.10': - resolution: {integrity: sha512-QQ36Q1VwGTT2YzvMeNe/j1x4DKS277DscNhWc57dIwQn//C+zAgvuSupMB/XkmYqPKQX+8hjn5/cHRJrMvWy0Q==} - engines: {node: '>= 16.14.0'} - hasBin: true - peerDependencies: - '@swc/core': ^1.2.66 - chokidar: ^4.0.1 - peerDependenciesMeta: - chokidar: - optional: true - - '@swc/core-darwin-arm64@1.15.11': - resolution: {integrity: sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - - '@swc/core-darwin-x64@1.15.11': - resolution: {integrity: sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - - '@swc/core-linux-arm-gnueabihf@1.15.11': - resolution: {integrity: sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - - '@swc/core-linux-arm64-gnu@1.15.11': - resolution: {integrity: sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-arm64-musl@1.15.11': - resolution: {integrity: sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-x64-gnu@1.15.11': - resolution: {integrity: sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-linux-x64-musl@1.15.11': - resolution: {integrity: sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-win32-arm64-msvc@1.15.11': - resolution: {integrity: sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - - '@swc/core-win32-ia32-msvc@1.15.11': - resolution: {integrity: sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - - '@swc/core-win32-x64-msvc@1.15.11': - resolution: {integrity: sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@swc/core@1.15.11': - resolution: {integrity: sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '>=0.5.17' - peerDependenciesMeta: - '@swc/helpers': - optional: true - - '@swc/counter@0.1.3': - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - - '@swc/types@0.1.25': - resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - - '@szmarczak/http-timer@5.0.1': - resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} - engines: {node: '>=14.16'} - - '@tailwindcss/node@4.2.0': - resolution: {integrity: sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==} - - '@tailwindcss/oxide-android-arm64@4.2.0': - resolution: {integrity: sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.2.0': - resolution: {integrity: sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.2.0': - resolution: {integrity: sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.2.0': - resolution: {integrity: sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0': - resolution: {integrity: sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.0': - resolution: {integrity: sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.2.0': - resolution: {integrity: sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.2.0': - resolution: {integrity: sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.2.0': - resolution: {integrity: sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.2.0': - resolution: {integrity: sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.0': - resolution: {integrity: sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.2.0': - resolution: {integrity: sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.2.0': - resolution: {integrity: sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==} - engines: {node: '>= 20'} - - '@tailwindcss/vite@4.2.0': - resolution: {integrity: sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - - '@tanstack/devtools-event-client@0.4.0': - resolution: {integrity: sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==} - engines: {node: '>=18'} - - '@tanstack/form-core@1.28.3': - resolution: {integrity: sha512-DBhnu1d5VfACAYOAZJO8tsEUHjWczZMJY8v/YrtAJNWpwvL/3ogDuz8e6yUB2m/iVTNq6K8yrnVN2nrX0/BX/w==} - - '@tanstack/history@1.154.14': - resolution: {integrity: sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA==} - engines: {node: '>=12'} - - '@tanstack/pacer-lite@0.1.1': - resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} - engines: {node: '>=18'} - - '@tanstack/query-core@5.90.20': - resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} - - '@tanstack/react-form@1.28.3': - resolution: {integrity: sha512-84yd0swZRcyC3Q46dYBH6bHf1tlIY1flchbdG3VwArg/wLVW5RdBenIrJhleHjk2OxXuF+9HoKQbHglJyWIXQA==} - peerDependencies: - '@tanstack/react-start': '*' - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@tanstack/react-start': - optional: true - - '@tanstack/react-query@5.90.21': - resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} - peerDependencies: - react: ^18 || ^19 - - '@tanstack/react-router-devtools@1.161.1': - resolution: {integrity: sha512-fl+o760gCHbd4Nb64SpVJQjpe77xDh2Mx6NqZy0aKACXvWRd8CDcFPzSvDZu4s7tHqFKMfzXqhNzL/jT+A8Prg==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/react-router': ^1.161.1 - '@tanstack/router-core': ^1.161.1 - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - peerDependenciesMeta: - '@tanstack/router-core': - optional: true - - '@tanstack/react-router@1.161.1': - resolution: {integrity: sha512-RQlCaunj+sleC8/JLxd22sWNpwqTHftcRdwGwNF27tjEzTnj06C6azWmA5sGclTdxGVclEOc/eaW7bUv5klsNw==} - engines: {node: '>=12'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-store@0.8.1': - resolution: {integrity: sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/router-core@1.161.1': - resolution: {integrity: sha512-Ika9RBvxB5cE+ziLxq90rqwhl9sb+j6mlGkRDwuDaGSDODenFeCDzjE0YQlgQ/kBUdSK2K1fFBiQPy5cnl54Og==} - engines: {node: '>=12'} - - '@tanstack/router-devtools-core@1.161.1': - resolution: {integrity: sha512-I3BcTUD2D8l1sKkab4JJM5LHwwWX5sDCbbhD+MGWplycIujzaW7xADbOnwLpeDjtJarc8kY20cUQ2NJ2eaX9kw==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/router-core': ^1.161.1 - csstype: ^3.0.10 - peerDependenciesMeta: - csstype: - optional: true - - '@tanstack/router-generator@1.161.1': - resolution: {integrity: sha512-IvkjrSaqr3WzYDUjdXOug1x5MhJT5Pw+hKkAi+GDA4isaBjyXS71QmY3jhsZZ2Rz08Xjw2JkAoIJCxfqw6AQKw==} - engines: {node: '>=12'} - - '@tanstack/router-plugin@1.161.1': - resolution: {integrity: sha512-1veqinPZRJMWJSgKljk3XF6l9PaDRRqnc2FMEGBRJ5ycmDqvzCP4RaKbA5pfE/DbXHkKF5Z7BiAeateZHgm4jA==} - engines: {node: '>=12'} - peerDependencies: - '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.161.1 - vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' - vite-plugin-solid: ^2.11.10 - webpack: '>=5.92.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - '@tanstack/react-router': - optional: true - vite: - optional: true - vite-plugin-solid: - optional: true - webpack: - optional: true - - '@tanstack/router-utils@1.158.0': - resolution: {integrity: sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg==} - engines: {node: '>=12'} - - '@tanstack/store@0.8.1': - resolution: {integrity: sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw==} - - '@tanstack/virtual-file-routes@1.154.7': - resolution: {integrity: sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg==} - engines: {node: '>=12'} - - '@tokenizer/inflate@0.2.7': - resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} - engines: {node: '>=18'} - - '@tokenizer/token@0.3.0': - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/http-cache-semantics@4.2.0': - resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} - - '@types/node@22.19.11': - resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} - - '@types/node@25.3.0': - resolution: {integrity: sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==} - - '@types/pg@8.16.0': - resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==} - - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - - '@vitejs/plugin-react@5.1.4': - resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} - - '@vitest/ui@4.0.18': - resolution: {integrity: sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==} - peerDependencies: - vitest: 4.0.18 - - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} - - '@xhmikosr/archive-type@7.1.0': - resolution: {integrity: sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==} - engines: {node: '>=18'} - - '@xhmikosr/bin-check@7.1.0': - resolution: {integrity: sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==} - engines: {node: '>=18'} - - '@xhmikosr/bin-wrapper@13.2.0': - resolution: {integrity: sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==} - engines: {node: '>=18'} - - '@xhmikosr/decompress-tar@8.1.0': - resolution: {integrity: sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==} - engines: {node: '>=18'} - - '@xhmikosr/decompress-tarbz2@8.1.0': - resolution: {integrity: sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==} - engines: {node: '>=18'} - - '@xhmikosr/decompress-targz@8.1.0': - resolution: {integrity: sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==} - engines: {node: '>=18'} - - '@xhmikosr/decompress-unzip@7.1.0': - resolution: {integrity: sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==} - engines: {node: '>=18'} - - '@xhmikosr/decompress@10.2.0': - resolution: {integrity: sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==} - engines: {node: '>=18'} - - '@xhmikosr/downloader@15.2.0': - resolution: {integrity: sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==} - engines: {node: '>=18'} - - '@xhmikosr/os-filter-obj@3.0.0': - resolution: {integrity: sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==} - engines: {node: ^14.14.0 || >=16.0.0} - - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ansis@4.2.0: - resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} - engines: {node: '>=14'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - arch@3.0.0: - resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-types@0.16.1: - resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} - engines: {node: '>=4'} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - b4a@1.8.0: - resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} - peerDependencies: - react-native-b4a: '*' - peerDependenciesMeta: - react-native-b4a: - optional: true - - babel-dead-code-elimination@1.0.12: - resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.3: - resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} - engines: {node: 20 || >=22} - - bare-events@2.8.2: - resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} - peerDependencies: - bare-abort-controller: '*' - peerDependenciesMeta: - bare-abort-controller: - optional: true - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} - engines: {node: '>=6.0.0'} - hasBin: true - - better-auth@1.4.18: - resolution: {integrity: sha512-bnyifLWBPcYVltH3RhS7CM62MoelEqC6Q+GnZwfiDWNfepXoQZBjEvn4urcERC7NTKgKq5zNBM8rvPvRBa6xcg==} - peerDependencies: - '@lynx-js/react': '*' - '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 - '@sveltejs/kit': ^2.0.0 - '@tanstack/react-start': ^1.0.0 - '@tanstack/solid-start': ^1.0.0 - better-sqlite3: ^12.0.0 - drizzle-kit: '>=0.31.4' - drizzle-orm: '>=0.41.0' - mongodb: ^6.0.0 || ^7.0.0 - mysql2: ^3.0.0 - next: ^14.0.0 || ^15.0.0 || ^16.0.0 - pg: ^8.0.0 - prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - solid-js: ^1.0.0 - svelte: ^4.0.0 || ^5.0.0 - vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 - vue: ^3.0.0 - peerDependenciesMeta: - '@lynx-js/react': - optional: true - '@prisma/client': - optional: true - '@sveltejs/kit': - optional: true - '@tanstack/react-start': - optional: true - '@tanstack/solid-start': - optional: true - better-sqlite3: - optional: true - drizzle-kit: - optional: true - drizzle-orm: - optional: true - mongodb: - optional: true - mysql2: - optional: true - next: - optional: true - pg: - optional: true - prisma: - optional: true - react: - optional: true - react-dom: - optional: true - solid-js: - optional: true - svelte: - optional: true - vitest: - optional: true - vue: - optional: true - - better-call@1.1.8: - resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==} - peerDependencies: - zod: ^4.0.0 - peerDependenciesMeta: - zod: - optional: true - - bin-version-check@5.1.0: - resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} - engines: {node: '>=12'} - - bin-version@6.0.0: - resolution: {integrity: sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==} - engines: {node: '>=12'} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bowser@2.14.1: - resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - brace-expansion@5.0.2: - resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} - engines: {node: 20 || >=22} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bullmq@5.69.3: - resolution: {integrity: sha512-P9uLsR7fDvejH/1m6uur6j7U9mqY6nNt+XvhlhStOUe7jdwbZoP/c2oWNtE+8ljOlubw4pRUKymtRqkyvloc4A==} - - cacheable-lookup@7.0.0: - resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} - engines: {node: '>=14.16'} - - cacheable-request@10.2.14: - resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} - engines: {node: '>=14.16'} - - caniuse-lite@1.0.30001770: - resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-es@2.0.0: - resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - - cron-parser@4.9.0: - resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} - engines: {node: '>=12.0.0'} - - cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} - hasBin: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - defaults@2.0.2: - resolution: {integrity: sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==} - engines: {node: '>=16'} - - defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} - engines: {node: '>=0.3.1'} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - dotenv-expand@12.0.3: - resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} - engines: {node: '>=12'} - - dotenv@16.6.1: - resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} - engines: {node: '>=12'} - - drizzle-kit@0.31.9: - resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} - hasBin: true - - drizzle-orm@0.44.7: - resolution: {integrity: sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=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.13' - '@prisma/client': '*' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@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: '*' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@libsql/client-wasm': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@prisma/client': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/sql.js': - optional: true - '@upstash/redis': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - gel: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - prisma: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - - drizzle-zod@0.8.3: - resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==} - peerDependencies: - drizzle-orm: '>=0.36.0' - zod: ^3.25.0 || ^4.0.0 - - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - enhanced-resolve@5.19.0: - resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} - engines: {node: '>=10.13.0'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild-register@3.6.0: - resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} - peerDependencies: - esbuild: '>=0.12 <1' - - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - events-universal@1.0.1: - resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - ext-list@2.2.2: - resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} - engines: {node: '>=0.10.0'} - - ext-name@5.0.0: - resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} - engines: {node: '>=4'} - - fast-copy@4.0.2: - resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} - - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - - fast-sha256@1.3.0: - resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} - - fast-xml-parser@5.3.6: - resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==} - hasBin: true - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fetch-retry@6.0.0: - resolution: {integrity: sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==} - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - file-type@20.5.0: - resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} - engines: {node: '>=18'} - - filename-reserved-regex@3.0.0: - resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - filenamify@6.0.0: - resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==} - engines: {node: '>=16'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-versions@5.1.0: - resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==} - engines: {node: '>=12'} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - form-data-encoder@2.1.4: - resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} - engines: {node: '>= 14.17'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - goober@2.1.18: - resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} - peerDependencies: - csstype: ^3.0.10 - - got@13.0.0: - resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} - engines: {node: '>=16'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - - hono-pino@0.7.2: - resolution: {integrity: sha512-uLJOngId4Ia2eHXnCPE8xpyMVkh+AGxAkHZKgvZk8YkmuTbcVDDUMe7aHMEz+YLqCDgd/Hk9ytVmmoQ8QTUXgQ==} - engines: {node: '>=18'} - peerDependencies: - hono: '>=4.0.0' - pino: '>=7.1.0' - - hono@4.12.0: - resolution: {integrity: sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==} - engines: {node: '>=16.9.0'} - - http-cache-semantics@4.2.0: - resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - - http2-wrapper@2.2.1: - resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} - engines: {node: '>=10.19.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - inspect-with-kind@1.0.5: - resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==} - - ioredis@5.9.2: - resolution: {integrity: sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==} - engines: {node: '>=12.22.0'} - - ioredis@5.9.3: - resolution: {integrity: sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==} - engines: {node: '>=12.22.0'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - isbot@5.1.35: - resolution: {integrity: sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg==} - engines: {node: '>=18'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true - - jose@6.1.3: - resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kysely@0.28.11: - resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==} - engines: {node: '>=20.0.0'} - - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} - engines: {node: '>= 12.0.0'} - - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - - lowercase-keys@3.0.0: - resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lru-cache@11.2.6: - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} - engines: {node: 20 || >=22} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lucide-react@0.553.0: - resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - luxon@3.7.2: - resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} - engines: {node: '>=12'} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - mimic-response@4.0.0: - resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - minimatch@10.2.2: - resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} - engines: {node: 18 || 20 || >=22} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - msgpackr-extract@3.0.3: - resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} - hasBin: true - - msgpackr@1.11.5: - resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} - - mylas@2.1.14: - resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==} - engines: {node: '>=16.0.0'} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} - engines: {node: ^18 || >=20} - hasBin: true - - nanostores@1.1.0: - resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==} - engines: {node: ^20.0.0 || >=22.0.0} - - neverthrow@8.2.0: - resolution: {integrity: sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==} - engines: {node: '>=18'} - - node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - - node-gyp-build-optional-packages@5.2.2: - resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} - hasBin: true - - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-url@8.1.1: - resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} - engines: {node: '>=14.16'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - openapi3-ts@4.5.0: - resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} - - p-cancelable@3.0.0: - resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} - engines: {node: '>=12.20'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - - pg-connection-string@2.11.0: - resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.11.0: - resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.11.0: - resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.18.0: - resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} - engines: {node: '>= 16.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pino-abstract-transport@1.2.0: - resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-abstract-transport@3.0.0: - resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - - pino-pretty@13.1.3: - resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} - hasBin: true - - pino-std-serializers@7.1.0: - resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} - hasBin: true - - piscina@4.9.2: - resolution: {integrity: sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==} - - plimit-lit@1.6.1: - resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} - engines: {node: '>=12'} - - postal-mime@2.7.3: - resolution: {integrity: sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.1: - resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - postgres@3.4.8: - resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==} - engines: {node: '>=12'} - - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true - - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - - queue-lit@1.5.2: - resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} - engines: {node: '>=12'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} - peerDependencies: - react: ^19.2.4 - - react-refresh@0.18.0: - resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} - engines: {node: '>=0.10.0'} - - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} - engines: {node: '>=0.10.0'} - - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - recast@0.23.11: - resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} - engines: {node: '>= 4'} - - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} - - reselect@5.1.1: - resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} - - resend@6.9.2: - resolution: {integrity: sha512-uIM6CQ08tS+hTCRuKBFbOBvHIGaEhqZe8s4FOgqsVXSbQLAhmNWpmUhG3UAtRnmcwTWFUqnHa/+Vux8YGPyDBA==} - engines: {node: '>=20'} - peerDependencies: - '@react-email/render': '*' - peerDependenciesMeta: - '@react-email/render': - optional: true - - resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - responselike@3.0.0: - resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} - engines: {node: '>=14.16'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@6.1.3: - resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} - engines: {node: 20 || >=22} - hasBin: true - - rollup@4.57.1: - resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rou3@0.7.12: - resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - - seek-bzip@2.0.0: - resolution: {integrity: sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==} - hasBin: true - - semver-regex@4.0.5: - resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} - engines: {node: '>=12'} - - semver-truncate@3.0.0: - resolution: {integrity: sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==} - engines: {node: '>=12'} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - seroval-plugins@1.5.0: - resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - - seroval@1.5.0: - resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} - engines: {node: '>=10'} - - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - sirv@3.0.2: - resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} - engines: {node: '>=18'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - sonic-boom@4.2.1: - resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} - - sonner@2.0.7: - resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - - sort-keys-length@1.0.1: - resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} - engines: {node: '>=0.10.0'} - - sort-keys@1.1.2: - resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} - engines: {node: '>=0.10.0'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - - standardwebhooks@1.0.0: - resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} - - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - - stoker@2.0.1: - resolution: {integrity: sha512-liSQNnJmn8fWSEan7sVaFe6iSHuN3X02fDGLS6snwW+FUuKi5HmKUHm3P+Kzr5xiDPqRpmSTtmGEBbSL9H2zkQ==} - peerDependencies: - '@asteasolutions/zod-to-openapi': ^8.0.0 - '@hono/zod-openapi': '>=1.0.0' - hono: ^4.0.0 - openapi3-ts: ^4.5.0 - peerDependenciesMeta: - '@asteasolutions/zod-to-openapi': - optional: true - '@hono/zod-openapi': - optional: true - openapi3-ts: - optional: true - - streamx@2.23.0: - resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-dirs@3.0.0: - resolution: {integrity: sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@5.0.3: - resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} - engines: {node: '>=14.16'} - - strnum@2.1.2: - resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} - - strtok3@10.3.4: - resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} - engines: {node: '>=18'} - - svix@1.84.1: - resolution: {integrity: sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==} - - tabbable@6.4.0: - resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - - tailwind-merge@3.5.0: - resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} - - tailwindcss@4.2.0: - resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==} - - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} - engines: {node: '>=6'} - - tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - - text-decoder@1.2.7: - resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} - - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} - engines: {node: '>=18'} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - token-types@6.1.2: - resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} - engines: {node: '>=14.16'} - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - tsc-alias@1.8.16: - resolution: {integrity: sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==} - engines: {node: '>=16.20.2'} - hasBin: true - - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} - engines: {node: '>=18.0.0'} - hasBin: true - - tw-animate-css@1.4.0: - resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - - type-fest@5.4.4: - resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} - engines: {node: '>=20'} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} - engines: {node: '>=18'} - - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - unplugin@2.3.11: - resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} - engines: {node: '>=18.12.0'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - vite-tsconfig-paths@5.1.4: - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} - engines: {node: '>= 14.6'} - hasBin: true - - yauzl@3.2.0: - resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} - engines: {node: '>=12'} - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zod@4.2.1: - resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==} - - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - - zustand@5.0.11: - resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - -snapshots: - - '@asteasolutions/zod-to-openapi@8.0.0(zod@4.2.1)': - dependencies: - openapi3-ts: 4.5.0 - zod: 4.2.1 - - '@asteasolutions/zod-to-openapi@8.4.1(zod@4.2.1)': - dependencies: - openapi3-ts: 4.5.0 - zod: 4.2.1 - - '@aws-crypto/crc32@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.1 - tslib: 2.8.1 - - '@aws-crypto/crc32c@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.1 - 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.1 - '@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.1 - '@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.1 - 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.1 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-sdk/client-s3@3.994.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.11 - '@aws-sdk/credential-provider-node': 3.972.10 - '@aws-sdk/middleware-bucket-endpoint': 3.972.3 - '@aws-sdk/middleware-expect-continue': 3.972.3 - '@aws-sdk/middleware-flexible-checksums': 3.972.9 - '@aws-sdk/middleware-host-header': 3.972.3 - '@aws-sdk/middleware-location-constraint': 3.972.3 - '@aws-sdk/middleware-logger': 3.972.3 - '@aws-sdk/middleware-recursion-detection': 3.972.3 - '@aws-sdk/middleware-sdk-s3': 3.972.11 - '@aws-sdk/middleware-ssec': 3.972.3 - '@aws-sdk/middleware-user-agent': 3.972.11 - '@aws-sdk/region-config-resolver': 3.972.3 - '@aws-sdk/signature-v4-multi-region': 3.994.0 - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-endpoints': 3.994.0 - '@aws-sdk/util-user-agent-browser': 3.972.3 - '@aws-sdk/util-user-agent-node': 3.972.9 - '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.23.2 - '@smithy/eventstream-serde-browser': 4.2.8 - '@smithy/eventstream-serde-config-resolver': 4.3.8 - '@smithy/eventstream-serde-node': 4.2.8 - '@smithy/fetch-http-handler': 5.3.9 - '@smithy/hash-blob-browser': 4.2.9 - '@smithy/hash-node': 4.2.8 - '@smithy/hash-stream-node': 4.2.8 - '@smithy/invalid-dependency': 4.2.8 - '@smithy/md5-js': 4.2.8 - '@smithy/middleware-content-length': 4.2.8 - '@smithy/middleware-endpoint': 4.4.16 - '@smithy/middleware-retry': 4.4.33 - '@smithy/middleware-serde': 4.2.9 - '@smithy/middleware-stack': 4.2.8 - '@smithy/node-config-provider': 4.3.8 - '@smithy/node-http-handler': 4.4.10 - '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.32 - '@smithy/util-defaults-mode-node': 4.2.35 - '@smithy/util-endpoints': 3.2.8 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-retry': 4.2.8 - '@smithy/util-stream': 4.5.12 - '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.2.8 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso@3.993.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.973.11 - '@aws-sdk/middleware-host-header': 3.972.3 - '@aws-sdk/middleware-logger': 3.972.3 - '@aws-sdk/middleware-recursion-detection': 3.972.3 - '@aws-sdk/middleware-user-agent': 3.972.11 - '@aws-sdk/region-config-resolver': 3.972.3 - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-endpoints': 3.993.0 - '@aws-sdk/util-user-agent-browser': 3.972.3 - '@aws-sdk/util-user-agent-node': 3.972.9 - '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.23.2 - '@smithy/fetch-http-handler': 5.3.9 - '@smithy/hash-node': 4.2.8 - '@smithy/invalid-dependency': 4.2.8 - '@smithy/middleware-content-length': 4.2.8 - '@smithy/middleware-endpoint': 4.4.16 - '@smithy/middleware-retry': 4.4.33 - '@smithy/middleware-serde': 4.2.9 - '@smithy/middleware-stack': 4.2.8 - '@smithy/node-config-provider': 4.3.8 - '@smithy/node-http-handler': 4.4.10 - '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.32 - '@smithy/util-defaults-mode-node': 4.2.35 - '@smithy/util-endpoints': 3.2.8 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-retry': 4.2.8 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.973.11': - dependencies: - '@aws-sdk/types': 3.973.1 - '@aws-sdk/xml-builder': 3.972.5 - '@smithy/core': 3.23.2 - '@smithy/node-config-provider': 4.3.8 - '@smithy/property-provider': 4.2.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/signature-v4': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/crc64-nvme@3.972.0': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.972.9': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/types': 3.973.1 - '@smithy/property-provider': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.972.11': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/types': 3.973.1 - '@smithy/fetch-http-handler': 5.3.9 - '@smithy/node-http-handler': 4.4.10 - '@smithy/property-provider': 4.2.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/util-stream': 4.5.12 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.972.9': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/credential-provider-env': 3.972.9 - '@aws-sdk/credential-provider-http': 3.972.11 - '@aws-sdk/credential-provider-login': 3.972.9 - '@aws-sdk/credential-provider-process': 3.972.9 - '@aws-sdk/credential-provider-sso': 3.972.9 - '@aws-sdk/credential-provider-web-identity': 3.972.9 - '@aws-sdk/nested-clients': 3.993.0 - '@aws-sdk/types': 3.973.1 - '@smithy/credential-provider-imds': 4.2.8 - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-login@3.972.9': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/nested-clients': 3.993.0 - '@aws-sdk/types': 3.973.1 - '@smithy/property-provider': 4.2.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-node@3.972.10': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.9 - '@aws-sdk/credential-provider-http': 3.972.11 - '@aws-sdk/credential-provider-ini': 3.972.9 - '@aws-sdk/credential-provider-process': 3.972.9 - '@aws-sdk/credential-provider-sso': 3.972.9 - '@aws-sdk/credential-provider-web-identity': 3.972.9 - '@aws-sdk/types': 3.973.1 - '@smithy/credential-provider-imds': 4.2.8 - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-process@3.972.9': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/types': 3.973.1 - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-sso@3.972.9': - dependencies: - '@aws-sdk/client-sso': 3.993.0 - '@aws-sdk/core': 3.973.11 - '@aws-sdk/token-providers': 3.993.0 - '@aws-sdk/types': 3.973.1 - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-web-identity@3.972.9': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/nested-clients': 3.993.0 - '@aws-sdk/types': 3.973.1 - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/middleware-bucket-endpoint@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-arn-parser': 3.972.2 - '@smithy/node-config-provider': 4.3.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-config-provider': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-expect-continue@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-flexible-checksums@3.972.9': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@aws-crypto/crc32c': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.973.11 - '@aws-sdk/crc64-nvme': 3.972.0 - '@aws-sdk/types': 3.973.1 - '@smithy/is-array-buffer': 4.2.0 - '@smithy/node-config-provider': 4.3.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-stream': 4.5.12 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-host-header@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-location-constraint@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-logger@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-recursion-detection@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@aws/lambda-invoke-store': 0.2.3 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-s3@3.972.11': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-arn-parser': 3.972.2 - '@smithy/core': 3.23.2 - '@smithy/node-config-provider': 4.3.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/signature-v4': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-stream': 4.5.12 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-ssec@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-user-agent@3.972.11': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-endpoints': 3.993.0 - '@smithy/core': 3.23.2 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/nested-clients@3.993.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.973.11 - '@aws-sdk/middleware-host-header': 3.972.3 - '@aws-sdk/middleware-logger': 3.972.3 - '@aws-sdk/middleware-recursion-detection': 3.972.3 - '@aws-sdk/middleware-user-agent': 3.972.11 - '@aws-sdk/region-config-resolver': 3.972.3 - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-endpoints': 3.993.0 - '@aws-sdk/util-user-agent-browser': 3.972.3 - '@aws-sdk/util-user-agent-node': 3.972.9 - '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.23.2 - '@smithy/fetch-http-handler': 5.3.9 - '@smithy/hash-node': 4.2.8 - '@smithy/invalid-dependency': 4.2.8 - '@smithy/middleware-content-length': 4.2.8 - '@smithy/middleware-endpoint': 4.4.16 - '@smithy/middleware-retry': 4.4.33 - '@smithy/middleware-serde': 4.2.9 - '@smithy/middleware-stack': 4.2.8 - '@smithy/node-config-provider': 4.3.8 - '@smithy/node-http-handler': 4.4.10 - '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.32 - '@smithy/util-defaults-mode-node': 4.2.35 - '@smithy/util-endpoints': 3.2.8 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-retry': 4.2.8 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/region-config-resolver@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/config-resolver': 4.4.6 - '@smithy/node-config-provider': 4.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/s3-request-presigner@3.994.0': - dependencies: - '@aws-sdk/signature-v4-multi-region': 3.994.0 - '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-format-url': 3.972.3 - '@smithy/middleware-endpoint': 4.4.16 - '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.994.0': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.11 - '@aws-sdk/types': 3.973.1 - '@smithy/protocol-http': 5.3.8 - '@smithy/signature-v4': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.993.0': - dependencies: - '@aws-sdk/core': 3.973.11 - '@aws-sdk/nested-clients': 3.993.0 - '@aws-sdk/types': 3.973.1 - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/types@3.973.1': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/util-arn-parser@3.972.2': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-endpoints@3.993.0': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-endpoints': 3.2.8 - tslib: 2.8.1 - - '@aws-sdk/util-endpoints@3.994.0': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-endpoints': 3.2.8 - tslib: 2.8.1 - - '@aws-sdk/util-format-url@3.972.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/querystring-builder': 4.2.8 - '@smithy/types': 4.12.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.3': - dependencies: - '@aws-sdk/types': 3.973.1 - '@smithy/types': 4.12.0 - bowser: 2.14.1 - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-node@3.972.9': - dependencies: - '@aws-sdk/middleware-user-agent': 3.972.11 - '@aws-sdk/types': 3.973.1 - '@smithy/node-config-provider': 4.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@aws-sdk/xml-builder@3.972.5': - dependencies: - '@smithy/types': 4.12.0 - fast-xml-parser: 5.3.6 - tslib: 2.8.1 - - '@aws/lambda-invoke-store@0.2.3': {} - - '@axiomhq/js@1.4.0': - dependencies: - fetch-retry: 6.0.0 - - '@axiomhq/pino@1.4.0': - dependencies: - '@axiomhq/js': 1.4.0 - pino-abstract-transport: 1.2.0 - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.0': - dependencies: - '@babel/types': 7.29.0 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/runtime@7.28.6': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@base-ui/react@1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@babel/runtime': 7.28.6 - '@base-ui/utils': 0.2.5(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@floating-ui/utils': 0.2.10 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - tabbable: 6.4.0 - use-sync-external-store: 1.6.0(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - - '@base-ui/utils@0.2.5(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@babel/runtime': 7.28.6 - '@floating-ui/utils': 0.2.10 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - reselect: 5.1.1 - use-sync-external-store: 1.6.0(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - - '@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.2.1))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)': - dependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@standard-schema/spec': 1.1.0 - better-call: 1.1.8(zod@4.3.6) - jose: 6.1.3 - kysely: 0.28.11 - nanostores: 1.1.0 - zod: 4.3.6 - - '@better-auth/telemetry@1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.2.1))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))': - dependencies: - '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.2.1))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - - '@better-auth/utils@0.3.0': {} - - '@better-fetch/fetch@1.1.21': {} - - '@biomejs/biome@2.3.7': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.7 - '@biomejs/cli-darwin-x64': 2.3.7 - '@biomejs/cli-linux-arm64': 2.3.7 - '@biomejs/cli-linux-arm64-musl': 2.3.7 - '@biomejs/cli-linux-x64': 2.3.7 - '@biomejs/cli-linux-x64-musl': 2.3.7 - '@biomejs/cli-win32-arm64': 2.3.7 - '@biomejs/cli-win32-x64': 2.3.7 - - '@biomejs/cli-darwin-arm64@2.3.7': - optional: true - - '@biomejs/cli-darwin-x64@2.3.7': - optional: true - - '@biomejs/cli-linux-arm64-musl@2.3.7': - optional: true - - '@biomejs/cli-linux-arm64@2.3.7': - optional: true - - '@biomejs/cli-linux-x64-musl@2.3.7': - optional: true - - '@biomejs/cli-linux-x64@2.3.7': - optional: true - - '@biomejs/cli-win32-arm64@2.3.7': - optional: true - - '@biomejs/cli-win32-x64@2.3.7': - optional: true - - '@borewit/text-codec@0.2.1': {} - - '@drizzle-team/brocli@0.10.2': {} - - '@esbuild-kit/core-utils@3.3.2': - dependencies: - esbuild: 0.18.20 - source-map-support: 0.5.21 - - '@esbuild-kit/esm-loader@2.6.5': - dependencies: - '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.13.6 - - '@esbuild/aix-ppc64@0.25.12': - optional: true - - '@esbuild/aix-ppc64@0.27.3': - optional: true - - '@esbuild/android-arm64@0.18.20': - optional: true - - '@esbuild/android-arm64@0.25.12': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - - '@esbuild/android-arm@0.18.20': - optional: true - - '@esbuild/android-arm@0.25.12': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - - '@esbuild/android-x64@0.18.20': - optional: true - - '@esbuild/android-x64@0.25.12': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - - '@esbuild/darwin-arm64@0.18.20': - optional: true - - '@esbuild/darwin-arm64@0.25.12': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - - '@esbuild/darwin-x64@0.18.20': - optional: true - - '@esbuild/darwin-x64@0.25.12': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - - '@esbuild/freebsd-arm64@0.18.20': - optional: true - - '@esbuild/freebsd-arm64@0.25.12': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - - '@esbuild/freebsd-x64@0.18.20': - optional: true - - '@esbuild/freebsd-x64@0.25.12': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - - '@esbuild/linux-arm64@0.18.20': - optional: true - - '@esbuild/linux-arm64@0.25.12': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - - '@esbuild/linux-arm@0.18.20': - optional: true - - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - - '@esbuild/linux-ia32@0.18.20': - optional: true - - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - - '@esbuild/linux-loong64@0.18.20': - optional: true - - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - - '@esbuild/linux-mips64el@0.18.20': - optional: true - - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.18.20': - optional: true - - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.18.20': - optional: true - - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.18.20': - optional: true - - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-x64@0.18.20': - optional: true - - '@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.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.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.25.12': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.18.20': - optional: true - - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.18.20': - optional: true - - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@esbuild/win32-x64@0.18.20': - optional: true - - '@esbuild/win32-x64@0.25.12': - optional: true - - '@esbuild/win32-x64@0.27.3': - optional: true - - '@floating-ui/core@1.7.4': - dependencies: - '@floating-ui/utils': 0.2.10 - - '@floating-ui/dom@1.7.5': - dependencies: - '@floating-ui/core': 1.7.4 - '@floating-ui/utils': 0.2.10 - - '@floating-ui/react-dom@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@floating-ui/dom': 1.7.5 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - '@floating-ui/utils@0.2.10': {} - - '@hono/node-server@1.19.9(hono@4.12.0)': - dependencies: - hono: 4.12.0 - - '@hono/node-ws@1.3.0(@hono/node-server@1.19.9(hono@4.12.0))(hono@4.12.0)': - dependencies: - '@hono/node-server': 1.19.9(hono@4.12.0) - hono: 4.12.0 - ws: 8.19.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@hono/swagger-ui@0.5.3(hono@4.12.0)': - dependencies: - hono: 4.12.0 - - '@hono/zod-openapi@1.2.2(hono@4.12.0)(zod@4.2.1)': - dependencies: - '@asteasolutions/zod-to-openapi': 8.4.1(zod@4.2.1) - '@hono/zod-validator': 0.7.6(hono@4.12.0)(zod@4.2.1) - hono: 4.12.0 - openapi3-ts: 4.5.0 - zod: 4.2.1 - - '@hono/zod-validator@0.7.6(hono@4.12.0)(zod@4.2.1)': - dependencies: - hono: 4.12.0 - zod: 4.2.1 - - '@ioredis/commands@1.5.0': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - optional: true - - '@napi-rs/nice-android-arm-eabi@1.1.1': - optional: true - - '@napi-rs/nice-android-arm64@1.1.1': - optional: true - - '@napi-rs/nice-darwin-arm64@1.1.1': - optional: true - - '@napi-rs/nice-darwin-x64@1.1.1': - optional: true - - '@napi-rs/nice-freebsd-x64@1.1.1': - optional: true - - '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': - optional: true - - '@napi-rs/nice-linux-arm64-gnu@1.1.1': - optional: true - - '@napi-rs/nice-linux-arm64-musl@1.1.1': - optional: true - - '@napi-rs/nice-linux-ppc64-gnu@1.1.1': - optional: true - - '@napi-rs/nice-linux-riscv64-gnu@1.1.1': - optional: true - - '@napi-rs/nice-linux-s390x-gnu@1.1.1': - optional: true - - '@napi-rs/nice-linux-x64-gnu@1.1.1': - optional: true - - '@napi-rs/nice-linux-x64-musl@1.1.1': - optional: true - - '@napi-rs/nice-openharmony-arm64@1.1.1': - optional: true - - '@napi-rs/nice-win32-arm64-msvc@1.1.1': - optional: true - - '@napi-rs/nice-win32-ia32-msvc@1.1.1': - optional: true - - '@napi-rs/nice-win32-x64-msvc@1.1.1': - optional: true - - '@napi-rs/nice@1.1.1': - optionalDependencies: - '@napi-rs/nice-android-arm-eabi': 1.1.1 - '@napi-rs/nice-android-arm64': 1.1.1 - '@napi-rs/nice-darwin-arm64': 1.1.1 - '@napi-rs/nice-darwin-x64': 1.1.1 - '@napi-rs/nice-freebsd-x64': 1.1.1 - '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 - '@napi-rs/nice-linux-arm64-gnu': 1.1.1 - '@napi-rs/nice-linux-arm64-musl': 1.1.1 - '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 - '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 - '@napi-rs/nice-linux-s390x-gnu': 1.1.1 - '@napi-rs/nice-linux-x64-gnu': 1.1.1 - '@napi-rs/nice-linux-x64-musl': 1.1.1 - '@napi-rs/nice-openharmony-arm64': 1.1.1 - '@napi-rs/nice-win32-arm64-msvc': 1.1.1 - '@napi-rs/nice-win32-ia32-msvc': 1.1.1 - '@napi-rs/nice-win32-x64-msvc': 1.1.1 - optional: true - - '@noble/ciphers@2.1.1': {} - - '@noble/hashes@2.0.1': {} - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@pinojs/redact@0.4.0': {} - - '@polka/url@1.0.0-next.29': {} - - '@rolldown/pluginutils@1.0.0-rc.3': {} - - '@rollup/rollup-android-arm-eabi@4.57.1': - optional: true - - '@rollup/rollup-android-arm64@4.57.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.57.1': - optional: true - - '@rollup/rollup-darwin-x64@4.57.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.57.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.57.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.57.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.57.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.57.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.57.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.57.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.57.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.57.1': - optional: true - - '@scalar/core@0.3.41': - dependencies: - '@scalar/types': 0.6.6 - - '@scalar/helpers@0.2.15': {} - - '@scalar/hono-api-reference@0.9.44(hono@4.12.0)': - dependencies: - '@scalar/core': 0.3.41 - hono: 4.12.0 - - '@scalar/types@0.6.6': - dependencies: - '@scalar/helpers': 0.2.15 - nanoid: 5.1.6 - type-fest: 5.4.4 - zod: 4.3.6 - - '@sindresorhus/is@5.6.0': {} - - '@smithy/abort-controller@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/chunked-blob-reader-native@4.2.1': - dependencies: - '@smithy/util-base64': 4.3.0 - tslib: 2.8.1 - - '@smithy/chunked-blob-reader@5.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/config-resolver@4.4.6': - dependencies: - '@smithy/node-config-provider': 4.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-config-provider': 4.2.0 - '@smithy/util-endpoints': 3.2.8 - '@smithy/util-middleware': 4.2.8 - tslib: 2.8.1 - - '@smithy/core@3.23.2': - dependencies: - '@smithy/middleware-serde': 4.2.9 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-stream': 4.5.12 - '@smithy/util-utf8': 4.2.0 - '@smithy/uuid': 1.1.0 - tslib: 2.8.1 - - '@smithy/credential-provider-imds@4.2.8': - dependencies: - '@smithy/node-config-provider': 4.3.8 - '@smithy/property-provider': 4.2.8 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - tslib: 2.8.1 - - '@smithy/eventstream-codec@4.2.8': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.12.0 - '@smithy/util-hex-encoding': 4.2.0 - tslib: 2.8.1 - - '@smithy/eventstream-serde-browser@4.2.8': - dependencies: - '@smithy/eventstream-serde-universal': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/eventstream-serde-config-resolver@4.3.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/eventstream-serde-node@4.2.8': - dependencies: - '@smithy/eventstream-serde-universal': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/eventstream-serde-universal@4.2.8': - dependencies: - '@smithy/eventstream-codec': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@5.3.9': - dependencies: - '@smithy/protocol-http': 5.3.8 - '@smithy/querystring-builder': 4.2.8 - '@smithy/types': 4.12.0 - '@smithy/util-base64': 4.3.0 - tslib: 2.8.1 - - '@smithy/hash-blob-browser@4.2.9': - dependencies: - '@smithy/chunked-blob-reader': 5.2.0 - '@smithy/chunked-blob-reader-native': 4.2.1 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/hash-node@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@smithy/hash-stream-node@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@smithy/invalid-dependency@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/is-array-buffer@2.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/is-array-buffer@4.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/md5-js@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@smithy/middleware-content-length@4.2.8': - dependencies: - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.4.16': - dependencies: - '@smithy/core': 3.23.2 - '@smithy/middleware-serde': 4.2.9 - '@smithy/node-config-provider': 4.3.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-middleware': 4.2.8 - tslib: 2.8.1 - - '@smithy/middleware-retry@4.4.33': - dependencies: - '@smithy/node-config-provider': 4.3.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/service-error-classification': 4.2.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-retry': 4.2.8 - '@smithy/uuid': 1.1.0 - tslib: 2.8.1 - - '@smithy/middleware-serde@4.2.9': - dependencies: - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/middleware-stack@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/node-config-provider@4.3.8': - dependencies: - '@smithy/property-provider': 4.2.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/node-http-handler@4.4.10': - dependencies: - '@smithy/abort-controller': 4.2.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/querystring-builder': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/property-provider@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/protocol-http@5.3.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/querystring-builder@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - '@smithy/util-uri-escape': 4.2.0 - tslib: 2.8.1 - - '@smithy/querystring-parser@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/service-error-classification@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - - '@smithy/shared-ini-file-loader@4.4.3': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/signature-v4@5.3.8': - dependencies: - '@smithy/is-array-buffer': 4.2.0 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-middleware': 4.2.8 - '@smithy/util-uri-escape': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@smithy/smithy-client@4.11.5': - dependencies: - '@smithy/core': 3.23.2 - '@smithy/middleware-endpoint': 4.4.16 - '@smithy/middleware-stack': 4.2.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-stream': 4.5.12 - tslib: 2.8.1 - - '@smithy/types@4.12.0': - dependencies: - tslib: 2.8.1 - - '@smithy/url-parser@4.2.8': - dependencies: - '@smithy/querystring-parser': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/util-base64@4.3.0': - dependencies: - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@smithy/util-body-length-browser@4.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-body-length-node@4.2.1': - 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.0': - dependencies: - '@smithy/is-array-buffer': 4.2.0 - tslib: 2.8.1 - - '@smithy/util-config-provider@4.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-defaults-mode-browser@4.3.32': - dependencies: - '@smithy/property-provider': 4.2.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/util-defaults-mode-node@4.2.35': - dependencies: - '@smithy/config-resolver': 4.4.6 - '@smithy/credential-provider-imds': 4.2.8 - '@smithy/node-config-provider': 4.3.8 - '@smithy/property-provider': 4.2.8 - '@smithy/smithy-client': 4.11.5 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/util-endpoints@3.2.8': - dependencies: - '@smithy/node-config-provider': 4.3.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/util-hex-encoding@4.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-middleware@4.2.8': - dependencies: - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/util-retry@4.2.8': - dependencies: - '@smithy/service-error-classification': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/util-stream@4.5.12': - dependencies: - '@smithy/fetch-http-handler': 5.3.9 - '@smithy/node-http-handler': 4.4.10 - '@smithy/types': 4.12.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@smithy/util-uri-escape@4.2.0': - 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.0': - dependencies: - '@smithy/util-buffer-from': 4.2.0 - tslib: 2.8.1 - - '@smithy/util-waiter@4.2.8': - dependencies: - '@smithy/abort-controller': 4.2.8 - '@smithy/types': 4.12.0 - tslib: 2.8.1 - - '@smithy/uuid@1.1.0': - dependencies: - tslib: 2.8.1 - - '@stablelib/base64@1.0.1': {} - - '@standard-schema/spec@1.1.0': {} - - '@swc/cli@0.7.10(@swc/core@1.15.11)': - dependencies: - '@swc/core': 1.15.11 - '@swc/counter': 0.1.3 - '@xhmikosr/bin-wrapper': 13.2.0 - commander: 8.3.0 - minimatch: 9.0.5 - piscina: 4.9.2 - semver: 7.7.4 - slash: 3.0.0 - source-map: 0.7.6 - tinyglobby: 0.2.15 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@swc/core-darwin-arm64@1.15.11': - optional: true - - '@swc/core-darwin-x64@1.15.11': - optional: true - - '@swc/core-linux-arm-gnueabihf@1.15.11': - optional: true - - '@swc/core-linux-arm64-gnu@1.15.11': - optional: true - - '@swc/core-linux-arm64-musl@1.15.11': - optional: true - - '@swc/core-linux-x64-gnu@1.15.11': - optional: true - - '@swc/core-linux-x64-musl@1.15.11': - optional: true - - '@swc/core-win32-arm64-msvc@1.15.11': - optional: true - - '@swc/core-win32-ia32-msvc@1.15.11': - optional: true - - '@swc/core-win32-x64-msvc@1.15.11': - optional: true - - '@swc/core@1.15.11': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.25 - optionalDependencies: - '@swc/core-darwin-arm64': 1.15.11 - '@swc/core-darwin-x64': 1.15.11 - '@swc/core-linux-arm-gnueabihf': 1.15.11 - '@swc/core-linux-arm64-gnu': 1.15.11 - '@swc/core-linux-arm64-musl': 1.15.11 - '@swc/core-linux-x64-gnu': 1.15.11 - '@swc/core-linux-x64-musl': 1.15.11 - '@swc/core-win32-arm64-msvc': 1.15.11 - '@swc/core-win32-ia32-msvc': 1.15.11 - '@swc/core-win32-x64-msvc': 1.15.11 - - '@swc/counter@0.1.3': {} - - '@swc/types@0.1.25': - dependencies: - '@swc/counter': 0.1.3 - - '@szmarczak/http-timer@5.0.1': - dependencies: - defer-to-connect: 2.0.1 - - '@tailwindcss/node@4.2.0': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.19.0 - jiti: 2.6.1 - lightningcss: 1.31.1 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.0 - - '@tailwindcss/oxide-android-arm64@4.2.0': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.2.0': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.2.0': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.2.0': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.0': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.2.0': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.2.0': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.2.0': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.2.0': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.0': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.2.0': - optional: true - - '@tailwindcss/oxide@4.2.0': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.0 - '@tailwindcss/oxide-darwin-arm64': 4.2.0 - '@tailwindcss/oxide-darwin-x64': 4.2.0 - '@tailwindcss/oxide-freebsd-x64': 4.2.0 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.0 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.0 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.0 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.0 - '@tailwindcss/oxide-linux-x64-musl': 4.2.0 - '@tailwindcss/oxide-wasm32-wasi': 4.2.0 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.0 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.0 - - '@tailwindcss/vite@4.2.0(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@tailwindcss/node': 4.2.0 - '@tailwindcss/oxide': 4.2.0 - tailwindcss: 4.2.0 - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - - '@tanstack/devtools-event-client@0.4.0': {} - - '@tanstack/form-core@1.28.3': - dependencies: - '@tanstack/devtools-event-client': 0.4.0 - '@tanstack/pacer-lite': 0.1.1 - '@tanstack/store': 0.8.1 - - '@tanstack/history@1.154.14': {} - - '@tanstack/pacer-lite@0.1.1': {} - - '@tanstack/query-core@5.90.20': {} - - '@tanstack/react-form@1.28.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@tanstack/form-core': 1.28.3 - '@tanstack/react-store': 0.8.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - transitivePeerDependencies: - - react-dom - - '@tanstack/react-query@5.90.21(react@19.2.4)': - dependencies: - '@tanstack/query-core': 5.90.20 - react: 19.2.4 - - '@tanstack/react-router-devtools@1.161.1(@tanstack/react-router@1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.161.1)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@tanstack/react-router': 1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-devtools-core': 1.161.1(@tanstack/router-core@1.161.1)(csstype@3.2.3) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@tanstack/router-core': 1.161.1 - transitivePeerDependencies: - - csstype - - '@tanstack/react-router@1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@tanstack/history': 1.154.14 - '@tanstack/react-store': 0.8.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.161.1 - isbot: 5.1.35 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/react-store@0.8.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@tanstack/store': 0.8.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - use-sync-external-store: 1.6.0(react@19.2.4) - - '@tanstack/router-core@1.161.1': - dependencies: - '@tanstack/history': 1.154.14 - '@tanstack/store': 0.8.1 - cookie-es: 2.0.0 - seroval: 1.5.0 - seroval-plugins: 1.5.0(seroval@1.5.0) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/router-devtools-core@1.161.1(@tanstack/router-core@1.161.1)(csstype@3.2.3)': - dependencies: - '@tanstack/router-core': 1.161.1 - clsx: 2.1.1 - goober: 2.1.18(csstype@3.2.3) - tiny-invariant: 1.3.3 - optionalDependencies: - csstype: 3.2.3 - - '@tanstack/router-generator@1.161.1': - dependencies: - '@tanstack/router-core': 1.161.1 - '@tanstack/router-utils': 1.158.0 - '@tanstack/virtual-file-routes': 1.154.7 - prettier: 3.8.1 - recast: 0.23.11 - source-map: 0.7.6 - tsx: 4.21.0 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@tanstack/router-plugin@1.161.1(@tanstack/react-router@1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@tanstack/router-core': 1.161.1 - '@tanstack/router-generator': 1.161.1 - '@tanstack/router-utils': 1.158.0 - '@tanstack/virtual-file-routes': 1.154.7 - chokidar: 3.6.0 - unplugin: 2.3.11 - zod: 3.25.76 - optionalDependencies: - '@tanstack/react-router': 1.161.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color - - '@tanstack/router-utils@1.158.0': - dependencies: - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - ansis: 4.2.0 - babel-dead-code-elimination: 1.0.12 - diff: 8.0.3 - pathe: 2.0.3 - tinyglobby: 0.2.15 - transitivePeerDependencies: - - supports-color - - '@tanstack/store@0.8.1': {} - - '@tanstack/virtual-file-routes@1.154.7': {} - - '@tokenizer/inflate@0.2.7': - dependencies: - debug: 4.4.3 - fflate: 0.8.2 - token-types: 6.1.2 - transitivePeerDependencies: - - supports-color - - '@tokenizer/token@0.3.0': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/http-cache-semantics@4.2.0': {} - - '@types/node@22.19.11': - dependencies: - undici-types: 6.21.0 - - '@types/node@25.3.0': - dependencies: - undici-types: 7.18.2 - - '@types/pg@8.16.0': - dependencies: - '@types/node': 25.3.0 - pg-protocol: 1.11.0 - pg-types: 2.2.0 - - '@types/react-dom@19.2.3(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react@19.2.14': - dependencies: - csstype: 3.2.3 - - '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-rc.3 - '@types/babel__core': 7.20.5 - react-refresh: 0.18.0 - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@4.0.18': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - chai: 6.2.2 - tinyrainbow: 3.0.3 - - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@22.19.11)(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@22.19.11)(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.0)(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.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - - '@vitest/pretty-format@4.0.18': - dependencies: - tinyrainbow: 3.0.3 - - '@vitest/runner@4.0.18': - dependencies: - '@vitest/utils': 4.0.18 - pathe: 2.0.3 - - '@vitest/snapshot@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.0.18': {} - - '@vitest/ui@4.0.18(vitest@4.0.18)': - dependencies: - '@vitest/utils': 4.0.18 - fflate: 0.8.2 - flatted: 3.3.3 - pathe: 2.0.3 - sirv: 3.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.3.0)(@vitest/ui@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - - '@vitest/utils@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 - - '@xhmikosr/archive-type@7.1.0': - dependencies: - file-type: 20.5.0 - transitivePeerDependencies: - - supports-color - - '@xhmikosr/bin-check@7.1.0': - dependencies: - execa: 5.1.1 - isexe: 2.0.0 - - '@xhmikosr/bin-wrapper@13.2.0': - dependencies: - '@xhmikosr/bin-check': 7.1.0 - '@xhmikosr/downloader': 15.2.0 - '@xhmikosr/os-filter-obj': 3.0.0 - bin-version-check: 5.1.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@xhmikosr/decompress-tar@8.1.0': - dependencies: - file-type: 20.5.0 - is-stream: 2.0.1 - tar-stream: 3.1.7 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@xhmikosr/decompress-tarbz2@8.1.0': - dependencies: - '@xhmikosr/decompress-tar': 8.1.0 - file-type: 20.5.0 - is-stream: 2.0.1 - seek-bzip: 2.0.0 - unbzip2-stream: 1.4.3 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@xhmikosr/decompress-targz@8.1.0': - dependencies: - '@xhmikosr/decompress-tar': 8.1.0 - file-type: 20.5.0 - is-stream: 2.0.1 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@xhmikosr/decompress-unzip@7.1.0': - dependencies: - file-type: 20.5.0 - get-stream: 6.0.1 - yauzl: 3.2.0 - transitivePeerDependencies: - - supports-color - - '@xhmikosr/decompress@10.2.0': - dependencies: - '@xhmikosr/decompress-tar': 8.1.0 - '@xhmikosr/decompress-tarbz2': 8.1.0 - '@xhmikosr/decompress-targz': 8.1.0 - '@xhmikosr/decompress-unzip': 7.1.0 - graceful-fs: 4.2.11 - strip-dirs: 3.0.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@xhmikosr/downloader@15.2.0': - dependencies: - '@xhmikosr/archive-type': 7.1.0 - '@xhmikosr/decompress': 10.2.0 - content-disposition: 0.5.4 - defaults: 2.0.2 - ext-name: 5.0.0 - file-type: 20.5.0 - filenamify: 6.0.0 - get-stream: 6.0.1 - got: 13.0.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - - '@xhmikosr/os-filter-obj@3.0.0': - dependencies: - arch: 3.0.0 - - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - - acorn@8.16.0: {} - - ansis@4.2.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - arch@3.0.0: {} - - array-union@2.1.0: {} - - assertion-error@2.0.1: {} - - ast-types@0.16.1: - dependencies: - tslib: 2.8.1 - - atomic-sleep@1.0.0: {} - - b4a@1.8.0: {} - - babel-dead-code-elimination@1.0.12: - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - balanced-match@1.0.2: {} - - balanced-match@4.0.3: {} - - bare-events@2.8.2: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.0: {} - - better-auth@1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.0.18): - dependencies: - '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.2.1))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) - '@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.2.1))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@noble/ciphers': 2.1.1 - '@noble/hashes': 2.0.1 - better-call: 1.1.8(zod@4.3.6) - defu: 6.1.4 - jose: 6.1.3 - kysely: 0.28.11 - nanostores: 1.1.0 - zod: 4.3.6 - optionalDependencies: - drizzle-kit: 0.31.9 - drizzle-orm: 0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8) - pg: 8.18.0 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - vitest: 4.0.18(@types/node@25.3.0)(@vitest/ui@4.0.18)(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: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - rou3: 0.7.12 - set-cookie-parser: 2.7.2 - optionalDependencies: - zod: 4.3.6 - - bin-version-check@5.1.0: - dependencies: - bin-version: 6.0.0 - semver: 7.7.4 - semver-truncate: 3.0.0 - - bin-version@6.0.0: - dependencies: - execa: 5.1.1 - find-versions: 5.1.0 - - binary-extensions@2.3.0: {} - - bowser@2.14.1: {} - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - brace-expansion@5.0.2: - dependencies: - balanced-match: 4.0.3 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001770 - electron-to-chromium: 1.5.286 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - - buffer-crc32@0.2.13: {} - - buffer-from@1.1.2: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bullmq@5.69.3: - dependencies: - cron-parser: 4.9.0 - ioredis: 5.9.2 - msgpackr: 1.11.5 - node-abort-controller: 3.1.1 - semver: 7.7.4 - tslib: 2.8.1 - uuid: 11.1.0 - transitivePeerDependencies: - - supports-color - - cacheable-lookup@7.0.0: {} - - cacheable-request@10.2.14: - dependencies: - '@types/http-cache-semantics': 4.2.0 - get-stream: 6.0.1 - http-cache-semantics: 4.2.0 - keyv: 4.5.4 - mimic-response: 4.0.0 - normalize-url: 8.1.1 - responselike: 3.0.0 - - caniuse-lite@1.0.30001770: {} - - chai@6.2.2: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - clsx@2.1.1: {} - - cluster-key-slot@1.1.2: {} - - colorette@2.0.20: {} - - commander@6.2.1: {} - - commander@8.3.0: {} - - commander@9.5.0: {} - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - convert-source-map@2.0.0: {} - - cookie-es@2.0.0: {} - - cron-parser@4.9.0: - dependencies: - luxon: 3.7.2 - - cross-env@7.0.3: - dependencies: - cross-spawn: 7.0.6 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - csstype@3.2.3: {} - - dateformat@4.6.3: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - defaults@2.0.2: {} - - defer-to-connect@2.0.1: {} - - defu@6.1.4: {} - - denque@2.1.0: {} - - detect-libc@2.1.2: {} - - diff@8.0.3: {} - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - dotenv-expand@12.0.3: - dependencies: - dotenv: 16.6.1 - - dotenv@16.6.1: {} - - drizzle-kit@0.31.9: - dependencies: - '@drizzle-team/brocli': 0.10.2 - '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.25.12 - esbuild-register: 3.6.0(esbuild@0.25.12) - transitivePeerDependencies: - - supports-color - - drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8): - optionalDependencies: - '@types/pg': 8.16.0 - kysely: 0.28.11 - pg: 8.18.0 - postgres: 3.4.8 - - drizzle-zod@0.8.3(drizzle-orm@0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8))(zod@4.2.1): - dependencies: - drizzle-orm: 0.44.7(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8) - zod: 4.2.1 - - electron-to-chromium@1.5.286: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - enhanced-resolve@5.19.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.0 - - es-module-lexer@1.7.0: {} - - esbuild-register@3.6.0(esbuild@0.25.12): - dependencies: - debug: 4.4.3 - esbuild: 0.25.12 - transitivePeerDependencies: - - supports-color - - esbuild@0.18.20: - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - - esbuild@0.25.12: - optionalDependencies: - '@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: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - - escalade@3.2.0: {} - - esprima@4.0.1: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - event-target-shim@5.0.1: {} - - events-universal@1.0.1: - dependencies: - bare-events: 2.8.2 - transitivePeerDependencies: - - bare-abort-controller - - events@3.3.0: {} - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - expect-type@1.3.0: {} - - ext-list@2.2.2: - dependencies: - mime-db: 1.54.0 - - ext-name@5.0.0: - dependencies: - ext-list: 2.2.2 - sort-keys-length: 1.0.1 - - fast-copy@4.0.2: {} - - fast-fifo@1.3.2: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-safe-stringify@2.1.1: {} - - fast-sha256@1.3.0: {} - - fast-xml-parser@5.3.6: - dependencies: - strnum: 2.1.2 - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - fetch-retry@6.0.0: {} - - fflate@0.8.2: {} - - file-type@20.5.0: - dependencies: - '@tokenizer/inflate': 0.2.7 - strtok3: 10.3.4 - token-types: 6.1.2 - uint8array-extras: 1.5.0 - transitivePeerDependencies: - - supports-color - - filename-reserved-regex@3.0.0: {} - - filenamify@6.0.0: - dependencies: - filename-reserved-regex: 3.0.0 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-versions@5.1.0: - dependencies: - semver-regex: 4.0.5 - - flatted@3.3.3: {} - - form-data-encoder@2.1.4: {} - - fsevents@2.3.3: - optional: true - - gensync@1.0.0-beta.2: {} - - get-stream@6.0.1: {} - - get-tsconfig@4.13.6: - dependencies: - resolve-pkg-maps: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob@13.0.6: - dependencies: - minimatch: 10.2.2 - minipass: 7.1.3 - path-scurry: 2.0.2 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - globrex@0.1.2: {} - - goober@2.1.18(csstype@3.2.3): - dependencies: - csstype: 3.2.3 - - got@13.0.0: - dependencies: - '@sindresorhus/is': 5.6.0 - '@szmarczak/http-timer': 5.0.1 - cacheable-lookup: 7.0.0 - cacheable-request: 10.2.14 - decompress-response: 6.0.0 - form-data-encoder: 2.1.4 - get-stream: 6.0.1 - http2-wrapper: 2.2.1 - lowercase-keys: 3.0.0 - p-cancelable: 3.0.0 - responselike: 3.0.0 - - graceful-fs@4.2.11: {} - - help-me@5.0.0: {} - - hono-pino@0.7.2(hono@4.12.0)(pino@9.14.0): - dependencies: - defu: 6.1.4 - hono: 4.12.0 - pino: 9.14.0 - - hono@4.12.0: {} - - http-cache-semantics@4.2.0: {} - - http2-wrapper@2.2.1: - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - - human-signals@2.1.0: {} - - ieee754@1.2.1: {} - - ignore@5.3.2: {} - - inspect-with-kind@1.0.5: - dependencies: - kind-of: 6.0.3 - - ioredis@5.9.2: - dependencies: - '@ioredis/commands': 1.5.0 - cluster-key-slot: 1.1.2 - debug: 4.4.3 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - - ioredis@5.9.3: - dependencies: - '@ioredis/commands': 1.5.0 - cluster-key-slot: 1.1.2 - debug: 4.4.3 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-plain-obj@1.1.0: {} - - is-stream@2.0.1: {} - - isbot@5.1.35: {} - - isexe@2.0.0: {} - - jiti@2.6.1: {} - - jose@6.1.3: {} - - joycon@3.1.1: {} - - js-tokens@4.0.0: {} - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json5@2.2.3: {} - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kind-of@6.0.3: {} - - kysely@0.28.11: {} - - lightningcss-android-arm64@1.31.1: - optional: true - - lightningcss-darwin-arm64@1.31.1: - optional: true - - lightningcss-darwin-x64@1.31.1: - optional: true - - lightningcss-freebsd-x64@1.31.1: - optional: true - - lightningcss-linux-arm-gnueabihf@1.31.1: - optional: true - - lightningcss-linux-arm64-gnu@1.31.1: - optional: true - - lightningcss-linux-arm64-musl@1.31.1: - optional: true - - lightningcss-linux-x64-gnu@1.31.1: - optional: true - - lightningcss-linux-x64-musl@1.31.1: - optional: true - - lightningcss-win32-arm64-msvc@1.31.1: - optional: true - - lightningcss-win32-x64-msvc@1.31.1: - optional: true - - lightningcss@1.31.1: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.31.1 - lightningcss-darwin-arm64: 1.31.1 - lightningcss-darwin-x64: 1.31.1 - lightningcss-freebsd-x64: 1.31.1 - lightningcss-linux-arm-gnueabihf: 1.31.1 - lightningcss-linux-arm64-gnu: 1.31.1 - lightningcss-linux-arm64-musl: 1.31.1 - lightningcss-linux-x64-gnu: 1.31.1 - lightningcss-linux-x64-musl: 1.31.1 - lightningcss-win32-arm64-msvc: 1.31.1 - lightningcss-win32-x64-msvc: 1.31.1 - - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - - lowercase-keys@3.0.0: {} - - lru-cache@11.2.6: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lucide-react@0.553.0(react@19.2.4): - dependencies: - react: 19.2.4 - - luxon@3.7.2: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mime-db@1.54.0: {} - - mimic-fn@2.1.0: {} - - mimic-response@3.1.0: {} - - mimic-response@4.0.0: {} - - minimatch@10.2.2: - dependencies: - brace-expansion: 5.0.2 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minimist@1.2.8: {} - - minipass@7.1.3: {} - - mrmime@2.0.1: {} - - ms@2.1.3: {} - - msgpackr-extract@3.0.3: - dependencies: - node-gyp-build-optional-packages: 5.2.2 - optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 - optional: true - - msgpackr@1.11.5: - optionalDependencies: - msgpackr-extract: 3.0.3 - - mylas@2.1.14: {} - - nanoid@3.3.11: {} - - nanoid@5.1.6: {} - - nanostores@1.1.0: {} - - neverthrow@8.2.0: - optionalDependencies: - '@rollup/rollup-linux-x64-gnu': 4.57.1 - - node-abort-controller@3.1.1: {} - - node-gyp-build-optional-packages@5.2.2: - dependencies: - detect-libc: 2.1.2 - optional: true - - node-releases@2.0.27: {} - - normalize-path@3.0.0: {} - - normalize-url@8.1.1: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - obug@2.1.1: {} - - on-exit-leak-free@2.1.2: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - openapi3-ts@4.5.0: - dependencies: - yaml: 2.8.2 - - p-cancelable@3.0.0: {} - - package-json-from-dist@1.0.1: {} - - path-key@3.1.1: {} - - path-scurry@2.0.2: - dependencies: - lru-cache: 11.2.6 - minipass: 7.1.3 - - path-type@4.0.0: {} - - pathe@2.0.3: {} - - pend@1.2.0: {} - - pg-cloudflare@1.3.0: - optional: true - - pg-connection-string@2.11.0: {} - - pg-int8@1.0.1: {} - - pg-pool@3.11.0(pg@8.18.0): - dependencies: - pg: 8.18.0 - - pg-protocol@1.11.0: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.1 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - - pg@8.18.0: - dependencies: - pg-connection-string: 2.11.0 - pg-pool: 3.11.0(pg@8.18.0) - pg-protocol: 1.11.0 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.3.0 - - pgpass@1.0.5: - dependencies: - split2: 4.2.0 - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - pino-abstract-transport@1.2.0: - dependencies: - readable-stream: 4.7.0 - split2: 4.2.0 - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-abstract-transport@3.0.0: - dependencies: - split2: 4.2.0 - - pino-pretty@13.1.3: - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 4.0.2 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pump: 3.0.3 - secure-json-parse: 4.1.0 - sonic-boom: 4.2.1 - strip-json-comments: 5.0.3 - - pino-std-serializers@7.1.0: {} - - pino@9.14.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.1 - thread-stream: 3.1.0 - - piscina@4.9.2: - optionalDependencies: - '@napi-rs/nice': 1.1.1 - - plimit-lit@1.6.1: - dependencies: - queue-lit: 1.5.2 - - postal-mime@2.7.3: {} - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postgres-array@2.0.0: {} - - postgres-bytea@1.0.1: {} - - postgres-date@1.0.7: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - - postgres@3.4.8: {} - - prettier@3.8.1: {} - - process-warning@5.0.0: {} - - process@0.11.10: {} - - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - queue-lit@1.5.2: {} - - queue-microtask@1.2.3: {} - - quick-format-unescaped@4.0.4: {} - - quick-lru@5.1.1: {} - - react-dom@19.2.4(react@19.2.4): - dependencies: - react: 19.2.4 - scheduler: 0.27.0 - - react-refresh@0.18.0: {} - - react@19.2.4: {} - - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - real-require@0.2.0: {} - - recast@0.23.11: - dependencies: - ast-types: 0.16.1 - esprima: 4.0.1 - source-map: 0.6.1 - tiny-invariant: 1.3.3 - tslib: 2.8.1 - - redis-errors@1.2.0: {} - - redis-parser@3.0.0: - dependencies: - redis-errors: 1.2.0 - - reselect@5.1.1: {} - - resend@6.9.2: - dependencies: - postal-mime: 2.7.3 - svix: 1.84.1 - - resolve-alpn@1.2.1: {} - - resolve-pkg-maps@1.0.0: {} - - responselike@3.0.0: - dependencies: - lowercase-keys: 3.0.0 - - reusify@1.1.0: {} - - rimraf@6.1.3: - dependencies: - glob: 13.0.6 - package-json-from-dist: 1.0.1 - - rollup@4.57.1: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 - fsevents: 2.3.3 - - rou3@0.7.12: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - safe-buffer@5.2.1: {} - - safe-stable-stringify@2.5.0: {} - - scheduler@0.27.0: {} - - secure-json-parse@4.1.0: {} - - seek-bzip@2.0.0: - dependencies: - commander: 6.2.1 - - semver-regex@4.0.5: {} - - semver-truncate@3.0.0: - dependencies: - semver: 7.7.4 - - semver@6.3.1: {} - - semver@7.7.4: {} - - seroval-plugins@1.5.0(seroval@1.5.0): - dependencies: - seroval: 1.5.0 - - seroval@1.5.0: {} - - set-cookie-parser@2.7.2: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - signal-exit@3.0.7: {} - - sirv@3.0.2: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - - slash@3.0.0: {} - - sonic-boom@4.2.1: - dependencies: - atomic-sleep: 1.0.0 - - sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - sort-keys-length@1.0.1: - dependencies: - sort-keys: 1.1.2 - - sort-keys@1.1.2: - dependencies: - is-plain-obj: 1.1.0 - - source-map-js@1.2.1: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - source-map@0.7.6: {} - - split2@4.2.0: {} - - stackback@0.0.2: {} - - standard-as-callback@2.1.0: {} - - standardwebhooks@1.0.0: - dependencies: - '@stablelib/base64': 1.0.1 - fast-sha256: 1.3.0 - - std-env@3.10.0: {} - - stoker@2.0.1(@asteasolutions/zod-to-openapi@8.0.0(zod@4.2.1))(@hono/zod-openapi@1.2.2(hono@4.12.0)(zod@4.2.1))(hono@4.12.0)(openapi3-ts@4.5.0): - dependencies: - hono: 4.12.0 - optionalDependencies: - '@asteasolutions/zod-to-openapi': 8.0.0(zod@4.2.1) - '@hono/zod-openapi': 1.2.2(hono@4.12.0)(zod@4.2.1) - openapi3-ts: 4.5.0 - - streamx@2.23.0: - dependencies: - events-universal: 1.0.1 - fast-fifo: 1.3.2 - text-decoder: 1.2.7 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-dirs@3.0.0: - dependencies: - inspect-with-kind: 1.0.5 - is-plain-obj: 1.1.0 - - strip-final-newline@2.0.0: {} - - strip-json-comments@5.0.3: {} - - strnum@2.1.2: {} - - strtok3@10.3.4: - dependencies: - '@tokenizer/token': 0.3.0 - - svix@1.84.1: - dependencies: - standardwebhooks: 1.0.0 - uuid: 10.0.0 - - tabbable@6.4.0: {} - - tagged-tag@1.0.0: {} - - tailwind-merge@3.5.0: {} - - tailwindcss@4.2.0: {} - - tapable@2.3.0: {} - - tar-stream@3.1.7: - dependencies: - b4a: 1.8.0 - fast-fifo: 1.3.2 - streamx: 2.23.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - text-decoder@1.2.7: - dependencies: - b4a: 1.8.0 - transitivePeerDependencies: - - react-native-b4a - - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 - - through@2.3.8: {} - - tiny-invariant@1.3.3: {} - - tiny-warning@1.0.3: {} - - tinybench@2.9.0: {} - - tinyexec@1.0.2: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinyrainbow@3.0.3: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - token-types@6.1.2: - dependencies: - '@borewit/text-codec': 0.2.1 - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - - totalist@3.0.1: {} - - tsc-alias@1.8.16: - dependencies: - chokidar: 3.6.0 - commander: 9.5.0 - get-tsconfig: 4.13.6 - globby: 11.1.0 - mylas: 2.1.14 - normalize-path: 3.0.0 - plimit-lit: 1.6.1 - - tsconfck@3.1.6(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - - tslib@2.8.1: {} - - tsx@4.21.0: - dependencies: - esbuild: 0.27.3 - get-tsconfig: 4.13.6 - optionalDependencies: - fsevents: 2.3.3 - - tw-animate-css@1.4.0: {} - - type-fest@5.4.4: - dependencies: - tagged-tag: 1.0.0 - - typescript@5.9.3: {} - - uint8array-extras@1.5.0: {} - - unbzip2-stream@1.4.3: - dependencies: - buffer: 5.7.1 - through: 2.3.8 - - undici-types@6.21.0: {} - - undici-types@7.18.2: {} - - unplugin@2.3.11: - dependencies: - '@jridgewell/remapping': 2.3.5 - acorn: 8.16.0 - picomatch: 4.0.3 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - - use-sync-external-store@1.6.0(react@19.2.4): - dependencies: - react: 19.2.4 - - uuid@10.0.0: {} - - uuid@11.1.0: {} - - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.3) - optionalDependencies: - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color - - typescript - - vite@7.3.1(@types/node@22.19.11)(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) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.1 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.19.11 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.31.1 - tsx: 4.21.0 - yaml: 2.8.2 - - vite@7.3.1(@types/node@25.3.0)(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) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.1 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.3.0 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.31.1 - tsx: 4.21.0 - yaml: 2.8.2 - - vitest@4.0.18(@types/node@22.19.11)(@vitest/ui@4.0.18)(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@22.19.11)(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 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.19.11 - '@vitest/ui': 4.0.18(vitest@4.0.18) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - vitest@4.0.18(@types/node@25.3.0)(@vitest/ui@4.0.18)(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.0)(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 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.3.0 - '@vitest/ui': 4.0.18(vitest@4.0.18) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - webpack-virtual-modules@0.6.2: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wrappy@1.0.2: {} - - ws@8.19.0: {} - - xtend@4.0.2: {} - - yallist@3.1.1: {} - - yaml@2.8.2: {} - - yauzl@3.2.0: - dependencies: - buffer-crc32: 0.2.13 - pend: 1.2.0 - - zod@3.25.76: {} - - zod@4.2.1: {} - - zod@4.3.6: {} - - zustand@5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): - optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.4 - use-sync-external-store: 1.6.0(react@19.2.4) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index eb3b003..dee51e9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,2 @@ packages: - - apps/* - - packages/* - -onlyBuiltDependencies: - - '@swc/core' - - esbuild - - better-sqlite3 + - "packages/*" diff --git a/scripts/new-module.sh b/scripts/new-module.sh index 868b5f7..0cf7317 100755 --- a/scripts/new-module.sh +++ b/scripts/new-module.sh @@ -190,7 +190,7 @@ success "index.ts" # ── __tests__/handlers.test.ts ───────────────────────────────────────────────── cat > "${MODULE_DIR}/__tests__/handlers.test.ts" << EOF -import { describe, it } from "vitest"; +import { describe, it } from "@std/testing/bdd"; // Integration tests for the ${MODULE} handlers. // Import the router directly and use Hono's testClient to invoke routes @@ -202,9 +202,9 @@ describe("${MODULE} handlers", () => { EOF success "__tests__/handlers.test.ts" -# ── Auto-format with Biome ───────────────────────────────────────────────────── -if command -v pnpm >/dev/null 2>&1 && [[ -f "biome.json" ]]; then - pnpm exec biome check --write "${MODULE_DIR}" >/dev/null 2>&1 && success "Biome formatting applied" || warn "Biome check had warnings (non-fatal)" +# ── Auto-format with Deno ───────────────────────────────────────────────────── +if command -v deno >/dev/null 2>&1; then + deno fmt "${MODULE_DIR}" >/dev/null 2>&1 && success "Deno formatting applied" || warn "Deno fmt had warnings (non-fatal)" fi # ── Next steps ───────────────────────────────────────────────────────────────── @@ -225,6 +225,6 @@ echo " export const publicRoutes = [health, ${MODULE}];" echo "" echo -e "${BOLD}Then:${RESET}" echo " • Add your DB schema and table to packages/db/src/schema/" -echo " • Run pnpm db:generate && pnpm db:migrate" +echo " • Run deno task db:generate && deno task db:migrate" echo " • Flesh out ${MODULE}.repository.ts with real Drizzle queries" echo " • Add use-cases to usecases/${MODULE}.usecases.ts as logic grows" diff --git a/scripts/setup.sh b/scripts/setup.sh index b24336c..c0dccee 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -18,19 +18,19 @@ step() { echo -e "\n${BOLD}$*${RESET}"; } # ── Prerequisites ─────────────────────────────────────────────────────────────── step "Checking prerequisites..." -# Node.js — require ≥20 -if ! command -v node >/dev/null 2>&1; then - error "Node.js not found. Install v20+ from https://nodejs.org" +# Deno — require ≥2 +if ! command -v deno >/dev/null 2>&1; then + error "Deno not found. Install v2+ from https://deno.com" exit 1 fi -NODE_MAJOR=$(node -e "process.stdout.write(String(process.versions.node.split('.')[0]))") -if [[ "$NODE_MAJOR" -lt 20 ]]; then - error "Node.js v${NODE_MAJOR} found — v20 or higher required." +DENO_MAJOR=$(deno --version | head -1 | grep -oP '\d+' | head -1) +if [[ "$DENO_MAJOR" -lt 2 ]]; then + error "Deno v${DENO_MAJOR} found — v2 or higher required." exit 1 fi -success "Node.js $(node --version)" +success "Deno $(deno --version | head -1)" -# pnpm +# pnpm (still needed for frontend deps) if ! command -v pnpm >/dev/null 2>&1; then error "pnpm not found. Install: npm i -g pnpm" exit 1 @@ -106,9 +106,8 @@ else fi # ── Build packages ───────────────────────────────────────────────────────────── -step "Building shared packages..." -pnpm build:packages -success "Packages built" +step "Verifying packages..." +info "Shared packages are imported via Deno's import maps — no build step needed." # ── Done ──────────────────────────────────────────────────────────────────────── echo "" @@ -116,10 +115,12 @@ echo -e "${GREEN}${BOLD}Setup complete!${RESET}" echo "" echo "Next steps:" echo " 1. Edit .env — set DATABASE_URL (and REDIS_URL if needed)" -echo " 2. Run migrations: pnpm db:migrate" -echo " 3. Start dev: pnpm dev" +echo " 2. Run migrations: deno task db:migrate" +echo " 3. Start backend: deno task dev" +echo " 4. Start frontend: deno task dev:frontend (in a separate terminal)" echo "" echo "Other useful commands:" -echo " pnpm build Build all apps" -echo " pnpm test Run all tests" -echo " pnpm db:studio Open Drizzle Studio" +echo " deno task dev:frontend Start frontend (Vite)" +echo " deno test -A Run all tests" +echo " deno lint Lint backend" +echo " deno task db:studio Open Drizzle Studio" diff --git a/tsconfig.base.json b/tsconfig.base.json deleted file mode 100644 index 070cfaf..0000000 --- a/tsconfig.base.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "node16", - "lib": ["ESNext"], - "strict": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "incremental": true, - "composite": true, - "noUncheckedIndexedAccess": true, - "useUnknownInCatchVariables": true - }, - "exclude": ["node_modules", "dist"] -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 96e7adc..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "module": "Node16", - "moduleResolution": "Node16" - }, - "references": [ - { "path": "./packages/db" }, - { "path": "./packages/shared" }, - { "path": "./packages/email-templates" } - ] -} From 09f020870f64a736f0bfba949cd58d44ce54801d Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 07:26:20 +0000 Subject: [PATCH 02/27] fix(frontend): point TanStack Router at apps/frontend/src/routes --- apps/frontend/vite.config.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index c9816cc..66bf915 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -3,10 +3,15 @@ import tailwindcss from "@tailwindcss/vite"; import tanstackRouter from "@tanstack/router-plugin/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; -import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [tanstackRouter(), react(), tailwindcss(), tsconfigPaths()], + plugins: [ + tanstackRouter({ + routesDirectory: path.resolve(__dirname, "./src/routes"), + }), + react(), + tailwindcss(), + ], resolve: { alias: { "@": path.resolve(__dirname, "./src"), From e3bc8ec23f88862585fc254b3814ecb47eddd1e1 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 07:30:27 +0000 Subject: [PATCH 03/27] refactor(frontend): adopt Vite 8 oxc, native tsconfig paths, set root --- apps/frontend/package.json | 2 +- apps/frontend/vite.config.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 95002a1..37bcfaa 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -30,7 +30,7 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.17", - "vite-tsconfig-paths": "^5.1.4", + "zod": "^4.2.1", "zustand": "^5.0.8" }, diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index 66bf915..46a2d29 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -5,6 +5,7 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; export default defineConfig({ + root: __dirname, plugins: [ tanstackRouter({ routesDirectory: path.resolve(__dirname, "./src/routes"), @@ -12,7 +13,9 @@ export default defineConfig({ react(), tailwindcss(), ], + oxc: {}, resolve: { + tsconfigPaths: true, alias: { "@": path.resolve(__dirname, "./src"), }, From 06a5d785c950483b8dca1489ae2ba95ba3343e64 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 08:47:51 +0000 Subject: [PATCH 04/27] chore(infra): restore Deno workspace, add frontend Docker/Caddy, fix CORS and error boundary - Restore root workspace with all 5 members (frontend, backend, 3 packages) - Remove nodeModulesDir from backend (only valid at root per Deno docs) - Remove explicit @repo/* import maps (resolve via workspace now) - Add frontend Dockerfile (Deno builder + Caddy runner with SPA + /api/* proxy) - Add docker-compose.yml with frontend (port 80) and backend (port 9999) - Fix CORS: authClient and api default to same-origin via Vite proxy - Add ErrorFallback and 404 components to root route - Update backend Dockerfile to Deno 2.7.14 Debian with workspace member stubs - Remove .dockerignore frontend exclusion - Lower min password length to 8 - Strip .ts extensions from backend auth imports --- .dockerignore | 3 - apps/backend/Dockerfile | 9 +- apps/backend/deno.json | 191 +- apps/backend/src/lib/auth.ts | 8 +- apps/frontend/Caddyfile | 6 + apps/frontend/Dockerfile | 20 + apps/frontend/deno.json | 23 + apps/frontend/deno.lock | 3546 +++++++++++++++++ apps/frontend/package.json | 10 +- .../src/components/error-fallback.tsx | 42 + apps/frontend/src/lib/api.ts | 2 +- apps/frontend/src/lib/auth-client.ts | 2 +- apps/frontend/src/routes/__root.tsx | 26 +- deno.json | 54 +- deno.lock | 1123 +++++- docker-compose.yml | 62 +- src/routeTree.gen.ts | 113 + 17 files changed, 4956 insertions(+), 284 deletions(-) create mode 100644 apps/frontend/Caddyfile create mode 100644 apps/frontend/Dockerfile create mode 100644 apps/frontend/deno.json create mode 100644 apps/frontend/deno.lock create mode 100644 apps/frontend/src/components/error-fallback.tsx create mode 100644 src/routeTree.gen.ts diff --git a/.dockerignore b/.dockerignore index b6bca62..8b7caad 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,9 +6,6 @@ node_modules/ **/__tests__/ **/*.test.ts -# Frontend (not needed for backend builds) -apps/frontend/ - # Build artifacts dist/ **/dist/ diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile index 1ead970..7a9198d 100644 --- a/apps/backend/Dockerfile +++ b/apps/backend/Dockerfile @@ -1,24 +1,25 @@ # ─── Build ───────────────────────────────────────────────────────────── -FROM denoland/deno:alpine-2.1.4 AS builder +FROM denoland/deno:debian-2.7.14 AS builder WORKDIR /app # Layer 1: Configuration (changes infrequently — preserves dep cache) COPY deno.json deno.lock ./ -COPY apps/backend/deno.json apps/backend/deno.lock* ./apps/backend/ +COPY apps/backend/deno.json ./apps/backend/ +COPY apps/frontend/deno.json ./apps/frontend/ COPY packages/shared/deno.json ./packages/shared/ COPY packages/db/deno.json ./packages/db/ COPY packages/email-templates/deno.json ./packages/email-templates/ -RUN deno cache apps/backend/src/index.ts # Layer 2: Shared packages (change less often than app code) COPY packages/shared/ ./packages/shared/ COPY packages/db/ ./packages/db/ COPY packages/email-templates/ ./packages/email-templates/ -RUN deno cache apps/backend/src/index.ts # Layer 3: Application source COPY apps/backend/ ./apps/backend/ + +# Cache dependencies (uses lockfile + import maps for npm resolution) RUN deno cache apps/backend/src/index.ts # Compile to standalone binary (includes Deno runtime + all deps) diff --git a/apps/backend/deno.json b/apps/backend/deno.json index 625610f..03d51ac 100644 --- a/apps/backend/deno.json +++ b/apps/backend/deno.json @@ -1,97 +1,98 @@ { - "name": "backend", - "version": "0.1.0", - "exports": "./src/index.ts", - "compilerOptions": { - "paths": { - "@/*": ["./src/*"] - }, - "module": "NodeNext", - "moduleResolution": "NodeNext", - "skipLibCheck": true - }, - "tasks": { - "dev": "deno run --watch --env-file=.env -A src/index.ts", - "start": "deno run --env-file=.env -A src/index.ts", - "worker": "deno run --env-file=.env -A src/jobs/worker.ts", - "test": "deno test --env-file=.env -A", - "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", - "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", - "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" - }, - "imports": { - "@/": "./src/", - "@/app": "./src/app.ts", - "@/db": "./src/db/index.ts", - "@/env": "./src/env.ts", - "@/lib/auth": "./src/lib/auth.ts", - "@/lib/create-app": "./src/lib/create-app.ts", - "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", - "@/lib/types": "./src/lib/types.ts", - "@/lib/redis": "./src/lib/redis.ts", - "@/lib/error": "./src/lib/error.ts", - "@/lib/infra": "./src/lib/infra.ts", - "@/lib/cache": "./src/lib/cache.ts", - "@/lib/storage": "./src/lib/storage.ts", - "@/lib/rate-limit": "./src/lib/rate-limit.ts", - "@/lib/ws": "./src/lib/ws.ts", - "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", - "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", - "@/middlewares/auth": "./src/middlewares/auth.ts", - "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", - "@/modules/health": "./src/modules/health/index.ts", - "@/modules/health/handlers": "./src/modules/health/handlers.ts", - "@/modules/health/routes": "./src/modules/health/routes.ts", - "@/modules/users": "./src/modules/users/index.ts", - "@/modules/users/handlers": "./src/modules/users/handlers.ts", - "@/modules/users/routes": "./src/modules/users/routes.ts", - "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", - "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", - "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", - "@/jobs/index": "./src/jobs/index.ts", - "@/jobs/worker": "./src/jobs/worker.ts", - "hono": "npm:hono", - "hono/cors": "npm:hono/cors", - "hono/dev": "npm:hono/dev", - "hono/ws": "npm:hono/ws", - "@hono/zod-openapi": "npm:@hono/zod-openapi", - "@hono/swagger-ui": "npm:@hono/swagger-ui", - "@hono/zod-validator": "npm:@hono/zod-validator", - "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", - "better-auth": "npm:better-auth", - "better-auth/adapters": "npm:better-auth/adapters", - "better-auth/plugins": "npm:better-auth/plugins", - "better-auth/plugins/two-factor": "npm:better-auth/plugins/two-factor", - "ioredis": "npm:ioredis", - "bullmq": "npm:bullmq", - "pino": "npm:pino", - "pino-pretty": "npm:pino-pretty", - "hono-pino": "npm:hono-pino", - "stoker": "npm:stoker", - "stoker/middlewares": "npm:stoker/middlewares", - "stoker/openapi": "npm:stoker/openapi", - "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", - "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", - "zod": "npm:zod", - "drizzle-kit": "npm:drizzle-kit", - "drizzle-orm": "npm:drizzle-orm", - "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", - "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", - "drizzle-orm/postgres-js/migrator": "npm:drizzle-orm/postgres-js/migrator", - "postgres": "npm:postgres", - "drizzle-zod": "npm:drizzle-zod", - "@node-rs/argon2": "npm:@node-rs/argon2", - "resend": "npm:resend", - "dotenv": "npm:dotenv", - "dotenv-expand": "npm:dotenv-expand", - "@axiomhq/pino": "npm:@axiomhq/pino" - }, - "lint": { - "rules": { - "exclude": ["no-explicit-any", "no-non-null-assertion"] - } - }, - "test": { - "include": ["src/**/*.test.ts"] - } + "name": "backend", + "version": "0.1.0", + "exports": "./src/index.ts", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + }, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "skipLibCheck": true + }, + "tasks": { + "dev": "deno run --watch --env-file=.env -A src/index.ts", + "start": "deno run --env-file=.env -A src/index.ts", + "worker": "deno run --env-file=.env -A src/jobs/worker.ts", + "test": "deno test --env-file=.env -A", + "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", + "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", + "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" + }, + "imports": { + "@/": "./src/", + "@/app": "./src/app.ts", + "@/db": "./src/db/index.ts", + "@/env": "./src/env.ts", + "@/lib/auth": "./src/lib/auth.ts", + "@/lib/create-app": "./src/lib/create-app.ts", + "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", + "@/lib/types": "./src/lib/types.ts", + "@/lib/redis": "./src/lib/redis.ts", + "@/lib/error": "./src/lib/error.ts", + "@/lib/infra": "./src/lib/infra.ts", + "@/lib/cache": "./src/lib/cache.ts", + "@/lib/storage": "./src/lib/storage.ts", + "@/lib/rate-limit": "./src/lib/rate-limit.ts", + "@/lib/ws": "./src/lib/ws.ts", + "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", + "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", + "@/middlewares/auth": "./src/middlewares/auth.ts", + "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", + "@/modules/health": "./src/modules/health/index.ts", + "@/modules/health/handlers": "./src/modules/health/handlers.ts", + "@/modules/health/routes": "./src/modules/health/routes.ts", + "@/modules/users": "./src/modules/users/index.ts", + "@/modules/users/handlers": "./src/modules/users/handlers.ts", + "@/modules/users/routes": "./src/modules/users/routes.ts", + "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", + "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", + "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", + "@/jobs/index": "./src/jobs/index.ts", + "@/jobs/worker": "./src/jobs/worker.ts", + "@/lib/email": "./src/lib/email.ts", + "hono": "npm:hono", + "hono/cors": "npm:hono/cors", + "hono/dev": "npm:hono/dev", + "hono/ws": "npm:hono/ws", + "@hono/zod-openapi": "npm:@hono/zod-openapi", + "@hono/swagger-ui": "npm:@hono/swagger-ui", + "@hono/zod-validator": "npm:@hono/zod-validator", + "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", + "better-auth": "npm:better-auth", + "better-auth/adapters": "npm:better-auth/adapters", + "better-auth/plugins": "npm:better-auth/plugins", + "better-auth/plugins/two-factor": "npm:better-auth/plugins/two-factor", + "ioredis": "npm:ioredis", + "bullmq": "npm:bullmq", + "pino": "npm:pino", + "pino-pretty": "npm:pino-pretty", + "hono-pino": "npm:hono-pino", + "stoker": "npm:stoker", + "stoker/middlewares": "npm:stoker/middlewares", + "stoker/openapi": "npm:stoker/openapi", + "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", + "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", + "zod": "npm:zod", + "drizzle-kit": "npm:drizzle-kit", + "drizzle-orm": "npm:drizzle-orm", + "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", + "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", + "drizzle-orm/postgres-js/migrator": "npm:drizzle-orm/postgres-js/migrator", + "postgres": "npm:postgres", + "drizzle-zod": "npm:drizzle-zod", + "@node-rs/argon2": "npm:@node-rs/argon2", + "resend": "npm:resend", + "dotenv": "npm:dotenv", + "dotenv-expand": "npm:dotenv-expand", + "@axiomhq/pino": "npm:@axiomhq/pino" + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any", "no-non-null-assertion"] + } + }, + "test": { + "include": ["src/**/*.test.ts"] + } } diff --git a/apps/backend/src/lib/auth.ts b/apps/backend/src/lib/auth.ts index da4b994..750d321 100644 --- a/apps/backend/src/lib/auth.ts +++ b/apps/backend/src/lib/auth.ts @@ -3,9 +3,9 @@ import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { openAPI } from "better-auth/plugins"; import { twoFactor } from "better-auth/plugins/two-factor"; -import { db, schema } from "@/db/index.ts"; -import { sendEmail } from "@/lib/email.ts"; -import { redis } from "@/lib/redis.ts"; +import { db, schema } from "@/db"; +import { sendEmail } from "@/lib/email"; +import { redis } from "@/lib/redis"; import { passwordResetEmail, welcomeEmail } from "@repo/email-templates"; import type { User as DbUser } from "@repo/db/schema"; import env from "@/env.ts"; @@ -86,7 +86,7 @@ export const auth = betterAuth({ emailAndPassword: { enabled: true, autoSignIn: true, - minPasswordLength: 12, + minPasswordLength: 8, maxPasswordLength: 256, revokeSessionsOnPasswordReset: true, sendResetPassword: async ({ user, url }) => { diff --git a/apps/frontend/Caddyfile b/apps/frontend/Caddyfile new file mode 100644 index 0000000..64bd2c5 --- /dev/null +++ b/apps/frontend/Caddyfile @@ -0,0 +1,6 @@ +:80 { + root * /srv + file_server + + reverse_proxy /api/* backend:9999 +} diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile new file mode 100644 index 0000000..3f6aa50 --- /dev/null +++ b/apps/frontend/Dockerfile @@ -0,0 +1,20 @@ +# ─── Build ───────────────────────────────────────────────────────────── +FROM denoland/deno:debian-2.7.14 AS builder + +WORKDIR /app + +# Layer 1: Config files (changes infrequently — preserves dep cache) +COPY apps/frontend/deno.json apps/frontend/package.json ./ +RUN deno install + +# Layer 2: Source +COPY apps/frontend/ ./ +RUN deno task build + +# ─── Run (Caddy serves SPA + proxies /api/* to backend) ────────────── +FROM caddy:alpine + +COPY --from=builder /app/dist/ /srv/ +COPY apps/frontend/Caddyfile /etc/caddy/Caddyfile + +EXPOSE 80 diff --git a/apps/frontend/deno.json b/apps/frontend/deno.json new file mode 100644 index 0000000..d5e53cd --- /dev/null +++ b/apps/frontend/deno.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "react", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["vite/client"] + }, + "imports": { + "@/": "./src/" + }, + "exclude": ["node_modules", "dist", ".tanstack"], + "tasks": { + "dev": "deno run -A npm:vite dev --port 3000", + "build": "deno run -A npm:vite build", + "preview": "deno run -A npm:vite preview", + "typecheck": "tsc --noEmit" + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any", "no-non-null-assertion"] + } + } +} diff --git a/apps/frontend/deno.lock b/apps/frontend/deno.lock new file mode 100644 index 0000000..5a7e5fa --- /dev/null +++ b/apps/frontend/deno.lock @@ -0,0 +1,3546 @@ +{ + "version": "5", + "specifiers": { + "npm:@base-ui/react@^1.2.0": "1.4.1_@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@better-fetch/fetch@^1.1.18": "1.1.21", + "npm:@tailwindcss/vite@^4.1.17": "4.3.0_vite@7.3.3", + "npm:@tanstack/react-form@1": "1.31.0_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@tanstack/react-query@^5.90.7": "5.100.9_react@19.2.6", + "npm:@tanstack/react-router-devtools@^1.134.13": "1.166.13_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_@tanstack+router-core@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6_csstype@3.2.3", + "npm:@tanstack/react-router@^1.134.13": "1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@tanstack/router-plugin@^1.134.14": "1.167.35_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_vite@7.3.3_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@types/react-dom@^19.1.9": "19.2.3_@types+react@19.2.14", + "npm:@types/react@^19.1.16": "19.2.14", + "npm:@vitejs/plugin-react@^5.0.4": "5.2.0_vite@7.3.3", + "npm:class-variance-authority@~0.7.1": "0.7.1", + "npm:clsx@^2.1.1": "2.1.1", + "npm:lucide-react@0.553": "0.553.0_react@19.2.6", + "npm:react-dom@^19.1.1": "19.2.6_react@19.2.6", + "npm:react@^19.1.1": "19.2.6", + "npm:sonner@^2.0.7": "2.0.7_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:tailwind-merge@^3.3.1": "3.5.0", + "npm:tailwindcss@^4.1.17": "4.3.0", + "npm:typescript@~5.9.3": "5.9.3", + "npm:vite@^7.1.7": "7.3.3", + "npm:zustand@^5.0.8": "5.0.13_@types+react@19.2.14_react@19.2.6" + }, + "npm": { + "@asteasolutions/zod-to-openapi@8.5.0_zod@4.4.3": { + "integrity": "sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q==", + "dependencies": [ + "openapi3-ts", + "zod@4.4.3" + ] + }, + "@aws-crypto/crc32@5.2.0": { + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": [ + "@aws-crypto/util", + "@aws-sdk/types", + "tslib" + ] + }, + "@aws-crypto/crc32c@5.2.0": { + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dependencies": [ + "@aws-crypto/util", + "@aws-sdk/types", + "tslib" + ] + }, + "@aws-crypto/sha1-browser@5.2.0": { + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": [ + "@aws-crypto/supports-web-crypto", + "@aws-crypto/util", + "@aws-sdk/types", + "@aws-sdk/util-locate-window", + "@smithy/util-utf8@2.3.0", + "tslib" + ] + }, + "@aws-crypto/sha256-browser@5.2.0": { + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": [ + "@aws-crypto/sha256-js", + "@aws-crypto/supports-web-crypto", + "@aws-crypto/util", + "@aws-sdk/types", + "@aws-sdk/util-locate-window", + "@smithy/util-utf8@2.3.0", + "tslib" + ] + }, + "@aws-crypto/sha256-js@5.2.0": { + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": [ + "@aws-crypto/util", + "@aws-sdk/types", + "tslib" + ] + }, + "@aws-crypto/supports-web-crypto@5.2.0": { + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": [ + "tslib" + ] + }, + "@aws-crypto/util@5.2.0": { + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/util-utf8@2.3.0", + "tslib" + ] + }, + "@aws-sdk/client-s3@3.1045.0": { + "integrity": "sha512-fsuO3Y6t+3Ro9Bsg41DKj4Sfy53CGSrhnMldNplWmG8Tx0UbYk+YDa4RD1hVlJpERw4JBmPkl0+J9qlxMh1pcA==", + "dependencies": [ + "@aws-crypto/sha1-browser", + "@aws-crypto/sha256-browser", + "@aws-crypto/sha256-js", + "@aws-sdk/core", + "@aws-sdk/credential-provider-node", + "@aws-sdk/middleware-bucket-endpoint", + "@aws-sdk/middleware-expect-continue", + "@aws-sdk/middleware-flexible-checksums", + "@aws-sdk/middleware-host-header", + "@aws-sdk/middleware-location-constraint", + "@aws-sdk/middleware-logger", + "@aws-sdk/middleware-recursion-detection", + "@aws-sdk/middleware-sdk-s3", + "@aws-sdk/middleware-ssec", + "@aws-sdk/middleware-user-agent", + "@aws-sdk/region-config-resolver", + "@aws-sdk/signature-v4-multi-region", + "@aws-sdk/types", + "@aws-sdk/util-endpoints", + "@aws-sdk/util-user-agent-browser", + "@aws-sdk/util-user-agent-node", + "@smithy/config-resolver", + "@smithy/core", + "@smithy/eventstream-serde-browser", + "@smithy/eventstream-serde-config-resolver", + "@smithy/eventstream-serde-node", + "@smithy/fetch-http-handler", + "@smithy/hash-blob-browser", + "@smithy/hash-node", + "@smithy/hash-stream-node", + "@smithy/invalid-dependency", + "@smithy/md5-js", + "@smithy/middleware-content-length", + "@smithy/middleware-endpoint", + "@smithy/middleware-retry", + "@smithy/middleware-serde", + "@smithy/middleware-stack", + "@smithy/node-config-provider", + "@smithy/node-http-handler", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-body-length-node", + "@smithy/util-defaults-mode-browser", + "@smithy/util-defaults-mode-node", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "@smithy/util-waiter", + "tslib" + ] + }, + "@aws-sdk/core@3.974.8": { + "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "dependencies": [ + "@aws-sdk/types", + "@aws-sdk/xml-builder", + "@smithy/core", + "@smithy/node-config-provider", + "@smithy/property-provider", + "@smithy/protocol-http", + "@smithy/signature-v4", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-base64", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/crc64-nvme@3.972.7": { + "integrity": "sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-env@3.972.34": { + "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-http@3.972.36": { + "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@smithy/fetch-http-handler", + "@smithy/node-http-handler", + "@smithy/property-provider", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-stream", + "tslib" + ] + }, + "@aws-sdk/credential-provider-ini@3.972.38": { + "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/credential-provider-env", + "@aws-sdk/credential-provider-http", + "@aws-sdk/credential-provider-login", + "@aws-sdk/credential-provider-process", + "@aws-sdk/credential-provider-sso", + "@aws-sdk/credential-provider-web-identity", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/credential-provider-imds", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-login@3.972.38": { + "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/protocol-http", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-node@3.972.39": { + "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", + "dependencies": [ + "@aws-sdk/credential-provider-env", + "@aws-sdk/credential-provider-http", + "@aws-sdk/credential-provider-ini", + "@aws-sdk/credential-provider-process", + "@aws-sdk/credential-provider-sso", + "@aws-sdk/credential-provider-web-identity", + "@aws-sdk/types", + "@smithy/credential-provider-imds", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-process@3.972.34": { + "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-sso@3.972.38": { + "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/token-providers", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/credential-provider-web-identity@3.972.38": { + "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-bucket-endpoint@3.972.10": { + "integrity": "sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==", + "dependencies": [ + "@aws-sdk/types", + "@aws-sdk/util-arn-parser", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-config-provider", + "tslib" + ] + }, + "@aws-sdk/middleware-expect-continue@3.972.10": { + "integrity": "sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-flexible-checksums@3.974.16": { + "integrity": "sha512-6ru8doI0/XzszqLIPXf0E/V7HhAw1Pu94010XCKYtBUfD0LxF0BuOzrUf8OQGR6j2o6wgKTHUniOmndQycHwCA==", + "dependencies": [ + "@aws-crypto/crc32", + "@aws-crypto/crc32c", + "@aws-crypto/util", + "@aws-sdk/core", + "@aws-sdk/crc64-nvme", + "@aws-sdk/types", + "@smithy/is-array-buffer@4.2.2", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-middleware", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/middleware-host-header@3.972.10": { + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-location-constraint@3.972.10": { + "integrity": "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-logger@3.972.10": { + "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-recursion-detection@3.972.11": { + "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", + "dependencies": [ + "@aws-sdk/types", + "@aws/lambda-invoke-store", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-sdk-s3@3.972.37": { + "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@aws-sdk/util-arn-parser", + "@smithy/core", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/signature-v4", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-config-provider", + "@smithy/util-middleware", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/middleware-ssec@3.972.10": { + "integrity": "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/middleware-user-agent@3.972.38": { + "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/types", + "@aws-sdk/util-endpoints", + "@smithy/core", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-retry", + "tslib" + ] + }, + "@aws-sdk/nested-clients@3.997.6": { + "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "dependencies": [ + "@aws-crypto/sha256-browser", + "@aws-crypto/sha256-js", + "@aws-sdk/core", + "@aws-sdk/middleware-host-header", + "@aws-sdk/middleware-logger", + "@aws-sdk/middleware-recursion-detection", + "@aws-sdk/middleware-user-agent", + "@aws-sdk/region-config-resolver", + "@aws-sdk/signature-v4-multi-region", + "@aws-sdk/types", + "@aws-sdk/util-endpoints", + "@aws-sdk/util-user-agent-browser", + "@aws-sdk/util-user-agent-node", + "@smithy/config-resolver", + "@smithy/core", + "@smithy/fetch-http-handler", + "@smithy/hash-node", + "@smithy/invalid-dependency", + "@smithy/middleware-content-length", + "@smithy/middleware-endpoint", + "@smithy/middleware-retry", + "@smithy/middleware-serde", + "@smithy/middleware-stack", + "@smithy/node-config-provider", + "@smithy/node-http-handler", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-body-length-node", + "@smithy/util-defaults-mode-browser", + "@smithy/util-defaults-mode-node", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@aws-sdk/region-config-resolver@3.972.13": { + "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/config-resolver", + "@smithy/node-config-provider", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/s3-request-presigner@3.1045.0": { + "integrity": "sha512-VDRF8GIuUPX+K4DUYrvcODj/h54LOmdJ7DhpLQ0wrYrdxzIiJEpi0n9jZ1bbjT2UxhwTbOorse5EGo+gnOK2aA==", + "dependencies": [ + "@aws-sdk/signature-v4-multi-region", + "@aws-sdk/types", + "@aws-sdk/util-format-url", + "@smithy/middleware-endpoint", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/signature-v4-multi-region@3.996.25": { + "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "dependencies": [ + "@aws-sdk/middleware-sdk-s3", + "@aws-sdk/types", + "@smithy/protocol-http", + "@smithy/signature-v4", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/token-providers@3.1041.0": { + "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "dependencies": [ + "@aws-sdk/core", + "@aws-sdk/nested-clients", + "@aws-sdk/types", + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/types@3.973.8": { + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/util-arn-parser@3.972.3": { + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "dependencies": [ + "tslib" + ] + }, + "@aws-sdk/util-endpoints@3.996.8": { + "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-endpoints", + "tslib" + ] + }, + "@aws-sdk/util-format-url@3.972.10": { + "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/querystring-builder", + "@smithy/types", + "tslib" + ] + }, + "@aws-sdk/util-locate-window@3.965.5": { + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dependencies": [ + "tslib" + ] + }, + "@aws-sdk/util-user-agent-browser@3.972.10": { + "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "dependencies": [ + "@aws-sdk/types", + "@smithy/types", + "bowser", + "tslib" + ] + }, + "@aws-sdk/util-user-agent-node@3.973.24": { + "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", + "dependencies": [ + "@aws-sdk/middleware-user-agent", + "@aws-sdk/types", + "@smithy/node-config-provider", + "@smithy/types", + "@smithy/util-config-provider", + "tslib" + ] + }, + "@aws-sdk/xml-builder@3.972.22": { + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "dependencies": [ + "@nodable/entities", + "@smithy/types", + "fast-xml-parser", + "tslib" + ] + }, + "@aws/lambda-invoke-store@0.2.4": { + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==" + }, + "@axiomhq/js@1.6.1": { + "integrity": "sha512-iNWOnGvP+R2lIWYk9jkfvRI/rfhOR9hlWJOBJhMKwZ9mfpE0KdnFnG2XPLoMcrbyrOBKj6Jw0hpIH5GhPucLog==", + "dependencies": [ + "fetch-retry" + ] + }, + "@axiomhq/pino@1.6.1": { + "integrity": "sha512-T5mdwsrbPOkPu7dkBU/HnQvO8EoiVEO4HpqvGliDEgYv2bTVwE/ELiHep02UMVze/Gv3DEgQ/GQSIjbOqTAzYw==", + "dependencies": [ + "@axiomhq/js", + "pino-abstract-transport@1.2.0" + ] + }, + "@babel/code-frame@7.29.0": { + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dependencies": [ + "@babel/helper-validator-identifier", + "js-tokens", + "picocolors" + ] + }, + "@babel/compat-data@7.29.3": { + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==" + }, + "@babel/core@7.29.0": { + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-compilation-targets", + "@babel/helper-module-transforms", + "@babel/helpers", + "@babel/parser", + "@babel/template", + "@babel/traverse", + "@babel/types", + "@jridgewell/remapping", + "convert-source-map", + "debug", + "gensync", + "json5", + "semver@6.3.1" + ] + }, + "@babel/generator@7.29.1": { + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dependencies": [ + "@babel/parser", + "@babel/types", + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping", + "jsesc" + ] + }, + "@babel/helper-compilation-targets@7.28.6": { + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dependencies": [ + "@babel/compat-data", + "@babel/helper-validator-option", + "browserslist", + "lru-cache", + "semver@6.3.1" + ] + }, + "@babel/helper-globals@7.28.0": { + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" + }, + "@babel/helper-module-imports@7.28.6": { + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dependencies": [ + "@babel/traverse", + "@babel/types" + ] + }, + "@babel/helper-module-transforms@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dependencies": [ + "@babel/core", + "@babel/helper-module-imports", + "@babel/helper-validator-identifier", + "@babel/traverse" + ] + }, + "@babel/helper-plugin-utils@7.28.6": { + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==" + }, + "@babel/helper-string-parser@7.27.1": { + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier@7.28.5": { + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/helper-validator-option@7.27.1": { + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==" + }, + "@babel/helpers@7.29.2": { + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dependencies": [ + "@babel/template", + "@babel/types" + ] + }, + "@babel/parser@7.29.3": { + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dependencies": [ + "@babel/types" + ], + "bin": true + }, + "@babel/plugin-syntax-jsx@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-syntax-typescript@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-transform-react-jsx-self@7.27.1_@babel+core@7.29.0": { + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-transform-react-jsx-source@7.27.1_@babel+core@7.29.0": { + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/runtime@7.29.2": { + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==" + }, + "@babel/template@7.28.6": { + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dependencies": [ + "@babel/code-frame", + "@babel/parser", + "@babel/types" + ] + }, + "@babel/traverse@7.29.0": { + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-globals", + "@babel/parser", + "@babel/template", + "@babel/types", + "debug" + ] + }, + "@babel/types@7.29.0": { + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dependencies": [ + "@babel/helper-string-parser", + "@babel/helper-validator-identifier" + ] + }, + "@base-ui/react@1.4.1_@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==", + "dependencies": [ + "@babel/runtime", + "@base-ui/utils", + "@floating-ui/react-dom", + "@floating-ui/utils", + "@types/react", + "react", + "react-dom", + "use-sync-external-store" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@base-ui/utils@0.2.8_@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==", + "dependencies": [ + "@babel/runtime", + "@floating-ui/utils", + "@types/react", + "react", + "react-dom", + "reselect", + "use-sync-external-store" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@better-auth/core@1.6.10_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-13h/rfSGMLl7zwyOb1BSlxAQZs2nQqn/xFI/bxB7zQuS95hVgTmNbKqhHtJYkNDtuJCcjEf1sNtLBHkvaPT/vw==", + "dependencies": [ + "@better-auth/utils", + "@better-fetch/fetch", + "@opentelemetry/semantic-conventions", + "@standard-schema/spec", + "better-call", + "jose", + "kysely", + "nanostores", + "zod@4.4.3" + ] + }, + "@better-auth/drizzle-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0_postgres@3.4.9": { + "integrity": "sha512-Ax0Jlpvuu35P3U6FtUGfkLAUmBwYIF+JwwtHt+jBlOEQNIiBhbLHh8ArQxrKJFRTDYv/XoSsrvJ5OluPsUFuuQ==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils", + "drizzle-orm" + ], + "optionalPeers": [ + "drizzle-orm" + ] + }, + "@better-auth/kysely-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_kysely@0.28.17_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_nanostores@1.3.0": { + "integrity": "sha512-Mp27qHgnvNCkkVEMRwhtMVpiiVFBnww0V+bunRWNU8fkxsm6H+GIBihUQOnkSCnIjV7f2HNjrf5DeYI2IhDePQ==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils", + "kysely" + ], + "optionalPeers": [ + "kysely" + ] + }, + "@better-auth/memory-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-zXk7GXnOpafAjCJ3+boh8hTEmUozGCLQpCl4plH9sAix6UlMdpYmdDTEGd+I8zungiEK+jzw4oevy7IirzKrrw==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils" + ] + }, + "@better-auth/mongo-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-EkbK3j8qwE9STteWoUh7vkve7n7/jSlkI0e9onAwr3YuE+an8scG7BgTHoDXku2qPlVa+KFPmcWc1FX6K/N6xA==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils" + ] + }, + "@better-auth/prisma-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-p/eJXl/RtHLt/A75chX/P55gjMzo5tWSJyfAyICts1miGHFUsu6D2TmKSzF7KNtjucjjzRKmtFl6BwMOxXXf2Q==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils" + ] + }, + "@better-auth/telemetry@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { + "integrity": "sha512-7lcx4btKGe4tP7Y1Nk6MWQuaiKI+qOk08B4vZFJeNKxRXyCNjVmdck78NyOTEj3iaVxN69MiXDoBZ4fEdvVbBw==", + "dependencies": [ + "@better-auth/core", + "@better-auth/utils", + "@better-fetch/fetch" + ] + }, + "@better-auth/utils@0.4.0": { + "integrity": "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==", + "dependencies": [ + "@noble/hashes" + ] + }, + "@better-fetch/fetch@1.1.21": { + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==" + }, + "@biomejs/biome@2.3.7": { + "integrity": "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==", + "optionalDependencies": [ + "@biomejs/cli-darwin-arm64", + "@biomejs/cli-darwin-x64", + "@biomejs/cli-linux-arm64", + "@biomejs/cli-linux-arm64-musl", + "@biomejs/cli-linux-x64", + "@biomejs/cli-linux-x64-musl", + "@biomejs/cli-win32-arm64", + "@biomejs/cli-win32-x64" + ], + "bin": true + }, + "@biomejs/cli-darwin-arm64@2.3.7": { + "integrity": "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@biomejs/cli-darwin-x64@2.3.7": { + "integrity": "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@biomejs/cli-linux-arm64-musl@2.3.7": { + "integrity": "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@biomejs/cli-linux-arm64@2.3.7": { + "integrity": "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@biomejs/cli-linux-x64-musl@2.3.7": { + "integrity": "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@biomejs/cli-linux-x64@2.3.7": { + "integrity": "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@biomejs/cli-win32-arm64@2.3.7": { + "integrity": "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@biomejs/cli-win32-x64@2.3.7": { + "integrity": "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@drizzle-team/brocli@0.10.2": { + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==" + }, + "@emnapi/core@1.10.0": { + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dependencies": [ + "@emnapi/wasi-threads", + "tslib" + ] + }, + "@emnapi/runtime@1.10.0": { + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/wasi-threads@1.2.1": { + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dependencies": [ + "tslib" + ] + }, + "@esbuild-kit/core-utils@3.3.2": { + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "dependencies": [ + "esbuild@0.18.20", + "source-map-support" + ], + "deprecated": true + }, + "@esbuild-kit/esm-loader@2.6.5": { + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "dependencies": [ + "@esbuild-kit/core-utils", + "get-tsconfig" + ], + "deprecated": true + }, + "@esbuild/aix-ppc64@0.25.12": { + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/aix-ppc64@0.27.7": { + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.18.20": { + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm64@0.25.12": { + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm64@0.27.7": { + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.18.20": { + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-arm@0.25.12": { + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-arm@0.27.7": { + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.18.20": { + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/android-x64@0.25.12": { + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/android-x64@0.27.7": { + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.18.20": { + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-arm64@0.25.12": { + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-arm64@0.27.7": { + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.18.20": { + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/darwin-x64@0.25.12": { + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/darwin-x64@0.27.7": { + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.18.20": { + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-arm64@0.25.12": { + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-arm64@0.27.7": { + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.18.20": { + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-x64@0.25.12": { + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-x64@0.27.7": { + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.18.20": { + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm64@0.25.12": { + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm64@0.27.7": { + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.18.20": { + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-arm@0.25.12": { + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-arm@0.27.7": { + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.18.20": { + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-ia32@0.25.12": { + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-ia32@0.27.7": { + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.18.20": { + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-loong64@0.25.12": { + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-loong64@0.27.7": { + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.18.20": { + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-mips64el@0.25.12": { + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-mips64el@0.27.7": { + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.18.20": { + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-ppc64@0.25.12": { + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-ppc64@0.27.7": { + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.18.20": { + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-riscv64@0.25.12": { + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-riscv64@0.27.7": { + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.18.20": { + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-s390x@0.25.12": { + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-s390x@0.27.7": { + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.18.20": { + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/linux-x64@0.25.12": { + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/linux-x64@0.27.7": { + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.25.12": { + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-arm64@0.27.7": { + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.18.20": { + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-x64@0.25.12": { + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-x64@0.27.7": { + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.25.12": { + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-arm64@0.27.7": { + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.18.20": { + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-x64@0.25.12": { + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-x64@0.27.7": { + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.25.12": { + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/openharmony-arm64@0.27.7": { + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.18.20": { + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/sunos-x64@0.25.12": { + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/sunos-x64@0.27.7": { + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.18.20": { + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-arm64@0.25.12": { + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-arm64@0.27.7": { + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.18.20": { + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-ia32@0.25.12": { + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-ia32@0.27.7": { + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.18.20": { + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@esbuild/win32-x64@0.25.12": { + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@esbuild/win32-x64@0.27.7": { + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@floating-ui/core@1.7.5": { + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "dependencies": [ + "@floating-ui/utils" + ] + }, + "@floating-ui/dom@1.7.6": { + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "dependencies": [ + "@floating-ui/core", + "@floating-ui/utils" + ] + }, + "@floating-ui/react-dom@2.1.8_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "dependencies": [ + "@floating-ui/dom", + "react", + "react-dom" + ] + }, + "@floating-ui/utils@0.2.11": { + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==" + }, + "@hono/swagger-ui@0.6.1_hono@4.12.18": { + "integrity": "sha512-sJTvldu1GPeEPfyeLG7gRj+W4vEuD+JDi+JjJ3TJs/DvMUtBLs0KJO5yokGegWWdy5qrbdnQGekbhgNRmPmYKQ==", + "dependencies": [ + "hono" + ] + }, + "@hono/zod-openapi@1.4.0_hono@4.12.18_zod@4.4.3": { + "integrity": "sha512-AFchqR1N/NxfI4hUOSGI2/g8zLROxA1OE7Oh5JJFlTaGxhrdRyH+93gd0tIBpb0z8s9r8hUoNnaOBfHbdb4NMw==", + "dependencies": [ + "@asteasolutions/zod-to-openapi", + "@hono/zod-validator", + "hono", + "openapi3-ts", + "zod@4.4.3" + ] + }, + "@hono/zod-validator@0.8.0_hono@4.12.18_zod@4.4.3": { + "integrity": "sha512-5uS4S1/LKtZQYvD4BtpPUFkOv8d1wNxHHrChm26buMiEYc1FrHWvDUaKVBwkiVtvSExHSpLGDvcnpI2Copyj9w==", + "dependencies": [ + "hono", + "zod@4.4.3" + ] + }, + "@ioredis/commands@1.5.1": { + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==" + }, + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/remapping@2.3.5": { + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/sourcemap-codec@1.5.5": { + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": { + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": { + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": { + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": { + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": { + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": { + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@napi-rs/wasm-runtime@0.2.12": { + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util" + ] + }, + "@noble/ciphers@2.2.0": { + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==" + }, + "@noble/hashes@2.2.0": { + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==" + }, + "@nodable/entities@2.1.0": { + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==" + }, + "@node-rs/argon2-android-arm-eabi@2.0.2": { + "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@node-rs/argon2-android-arm64@2.0.2": { + "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-darwin-arm64@2.0.2": { + "integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-darwin-x64@2.0.2": { + "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@node-rs/argon2-freebsd-x64@2.0.2": { + "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@node-rs/argon2-linux-arm-gnueabihf@2.0.2": { + "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@node-rs/argon2-linux-arm64-gnu@2.0.2": { + "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-linux-arm64-musl@2.0.2": { + "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-linux-x64-gnu@2.0.2": { + "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@node-rs/argon2-linux-x64-musl@2.0.2": { + "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@node-rs/argon2-wasm32-wasi@2.0.2": { + "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", + "dependencies": [ + "@napi-rs/wasm-runtime" + ], + "cpu": ["wasm32"] + }, + "@node-rs/argon2-win32-arm64-msvc@2.0.2": { + "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@node-rs/argon2-win32-ia32-msvc@2.0.2": { + "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@node-rs/argon2-win32-x64-msvc@2.0.2": { + "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@node-rs/argon2@2.0.2": { + "integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==", + "optionalDependencies": [ + "@node-rs/argon2-android-arm-eabi", + "@node-rs/argon2-android-arm64", + "@node-rs/argon2-darwin-arm64", + "@node-rs/argon2-darwin-x64", + "@node-rs/argon2-freebsd-x64", + "@node-rs/argon2-linux-arm-gnueabihf", + "@node-rs/argon2-linux-arm64-gnu", + "@node-rs/argon2-linux-arm64-musl", + "@node-rs/argon2-linux-x64-gnu", + "@node-rs/argon2-linux-x64-musl", + "@node-rs/argon2-wasm32-wasi", + "@node-rs/argon2-win32-arm64-msvc", + "@node-rs/argon2-win32-ia32-msvc", + "@node-rs/argon2-win32-x64-msvc" + ] + }, + "@opentelemetry/semantic-conventions@1.40.0": { + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==" + }, + "@pinojs/redact@0.4.0": { + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "@rolldown/pluginutils@1.0.0-rc.3": { + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==" + }, + "@rollup/rollup-android-arm-eabi@4.60.3": { + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@rollup/rollup-android-arm64@4.60.3": { + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-arm64@4.60.3": { + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-x64@4.60.3": { + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rollup/rollup-freebsd-arm64@4.60.3": { + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@rollup/rollup-freebsd-x64@4.60.3": { + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-arm-gnueabihf@4.60.3": { + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm-musleabihf@4.60.3": { + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm64-gnu@4.60.3": { + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-arm64-musl@4.60.3": { + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-loong64-gnu@4.60.3": { + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-loong64-musl@4.60.3": { + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.60.3": { + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-ppc64-musl@4.60.3": { + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.60.3": { + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-riscv64-musl@4.60.3": { + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-s390x-gnu@4.60.3": { + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rollup/rollup-linux-x64-gnu@4.60.3": { + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-x64-musl@4.60.3": { + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-openbsd-x64@4.60.3": { + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-openharmony-arm64@4.60.3": { + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-arm64-msvc@4.60.3": { + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-ia32-msvc@4.60.3": { + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@rollup/rollup-win32-x64-gnu@4.60.3": { + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rollup/rollup-win32-x64-msvc@4.60.3": { + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@scalar/client-side-rendering@0.1.7": { + "integrity": "sha512-IDzjKF93jrOljlvKBsLHXT1FPWgz56jFrMPC+iLihREp1qH8wF92mG8Zpakw8cURkEuw5WijRk0xNBP2moGyuw==", + "dependencies": [ + "@scalar/types" + ] + }, + "@scalar/helpers@0.6.0": { + "integrity": "sha512-pfSamAgBxqFeE8IpEG6uGkHlnPhY1CLeOTttV9+vKQbrBk5b7vvyTsUXv0Hz4kNU1TFrxcTTPE+Akn5S+jlTtQ==" + }, + "@scalar/hono-api-reference@0.10.14_hono@4.12.18": { + "integrity": "sha512-LCIT4ul3c4MyD7shhxsWcvvOABt0fEHNQID2n+2TPeItc/MR2qCjjp/QfqD+JoQ7zbc0nnzh1kwRR06MVBmnUA==", + "dependencies": [ + "@scalar/client-side-rendering", + "hono" + ] + }, + "@scalar/types@0.9.6": { + "integrity": "sha512-UaCQQcscFTJdxZREE8KhUdSJgaDlc44TZbmWcZffs4m1hzqOvEI7lEBS13iBpLq7/cxUXFgyJdecywvNqJ0PkA==", + "dependencies": [ + "@scalar/helpers", + "nanoid@5.1.11", + "type-fest", + "zod@4.4.3" + ] + }, + "@smithy/chunked-blob-reader-native@4.2.3": { + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "dependencies": [ + "@smithy/util-base64", + "tslib" + ] + }, + "@smithy/chunked-blob-reader@5.2.2": { + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/config-resolver@4.4.17": { + "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", + "dependencies": [ + "@smithy/node-config-provider", + "@smithy/types", + "@smithy/util-config-provider", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "tslib" + ] + }, + "@smithy/core@3.23.17": { + "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-middleware", + "@smithy/util-stream", + "@smithy/util-utf8@4.2.2", + "@smithy/uuid", + "tslib" + ] + }, + "@smithy/credential-provider-imds@4.2.14": { + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "dependencies": [ + "@smithy/node-config-provider", + "@smithy/property-provider", + "@smithy/types", + "@smithy/url-parser", + "tslib" + ] + }, + "@smithy/eventstream-codec@4.2.14": { + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "dependencies": [ + "@aws-crypto/crc32", + "@smithy/types", + "@smithy/util-hex-encoding", + "tslib" + ] + }, + "@smithy/eventstream-serde-browser@4.2.14": { + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", + "dependencies": [ + "@smithy/eventstream-serde-universal", + "@smithy/types", + "tslib" + ] + }, + "@smithy/eventstream-serde-config-resolver@4.3.14": { + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/eventstream-serde-node@4.2.14": { + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", + "dependencies": [ + "@smithy/eventstream-serde-universal", + "@smithy/types", + "tslib" + ] + }, + "@smithy/eventstream-serde-universal@4.2.14": { + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", + "dependencies": [ + "@smithy/eventstream-codec", + "@smithy/types", + "tslib" + ] + }, + "@smithy/fetch-http-handler@5.3.17": { + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/querystring-builder", + "@smithy/types", + "@smithy/util-base64", + "tslib" + ] + }, + "@smithy/hash-blob-browser@4.2.15": { + "integrity": "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA==", + "dependencies": [ + "@smithy/chunked-blob-reader", + "@smithy/chunked-blob-reader-native", + "@smithy/types", + "tslib" + ] + }, + "@smithy/hash-node@4.2.14": { + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", + "dependencies": [ + "@smithy/types", + "@smithy/util-buffer-from@4.2.2", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/hash-stream-node@4.2.14": { + "integrity": "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ==", + "dependencies": [ + "@smithy/types", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/invalid-dependency@4.2.14": { + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/is-array-buffer@2.2.0": { + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/is-array-buffer@4.2.2": { + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/md5-js@4.2.14": { + "integrity": "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA==", + "dependencies": [ + "@smithy/types", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/middleware-content-length@4.2.14": { + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@smithy/middleware-endpoint@4.4.32": { + "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "dependencies": [ + "@smithy/core", + "@smithy/middleware-serde", + "@smithy/node-config-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-middleware", + "tslib" + ] + }, + "@smithy/middleware-retry@4.5.7": { + "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "dependencies": [ + "@smithy/core", + "@smithy/node-config-provider", + "@smithy/protocol-http", + "@smithy/service-error-classification", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/uuid", + "tslib" + ] + }, + "@smithy/middleware-serde@4.2.20": { + "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", + "dependencies": [ + "@smithy/core", + "@smithy/protocol-http", + "@smithy/types", + "tslib" + ] + }, + "@smithy/middleware-stack@4.2.14": { + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/node-config-provider@4.3.14": { + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", + "dependencies": [ + "@smithy/property-provider", + "@smithy/shared-ini-file-loader", + "@smithy/types", + "tslib" + ] + }, + "@smithy/node-http-handler@4.6.1": { + "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", + "dependencies": [ + "@smithy/protocol-http", + "@smithy/querystring-builder", + "@smithy/types", + "tslib" + ] + }, + "@smithy/property-provider@4.2.14": { + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/protocol-http@5.3.14": { + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/querystring-builder@4.2.14": { + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", + "dependencies": [ + "@smithy/types", + "@smithy/util-uri-escape", + "tslib" + ] + }, + "@smithy/querystring-parser@4.2.14": { + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/service-error-classification@4.3.1": { + "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", + "dependencies": [ + "@smithy/types" + ] + }, + "@smithy/shared-ini-file-loader@4.4.9": { + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/signature-v4@5.3.14": { + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", + "dependencies": [ + "@smithy/is-array-buffer@4.2.2", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-hex-encoding", + "@smithy/util-middleware", + "@smithy/util-uri-escape", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/smithy-client@4.12.13": { + "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", + "dependencies": [ + "@smithy/core", + "@smithy/middleware-endpoint", + "@smithy/middleware-stack", + "@smithy/protocol-http", + "@smithy/types", + "@smithy/util-stream", + "tslib" + ] + }, + "@smithy/types@4.14.1": { + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/url-parser@4.2.14": { + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", + "dependencies": [ + "@smithy/querystring-parser", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-base64@4.3.2": { + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dependencies": [ + "@smithy/util-buffer-from@4.2.2", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/util-body-length-browser@4.2.2": { + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-body-length-node@4.2.3": { + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-buffer-from@2.2.0": { + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": [ + "@smithy/is-array-buffer@2.2.0", + "tslib" + ] + }, + "@smithy/util-buffer-from@4.2.2": { + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dependencies": [ + "@smithy/is-array-buffer@4.2.2", + "tslib" + ] + }, + "@smithy/util-config-provider@4.2.2": { + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-defaults-mode-browser@4.3.49": { + "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", + "dependencies": [ + "@smithy/property-provider", + "@smithy/smithy-client", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-defaults-mode-node@4.2.54": { + "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", + "dependencies": [ + "@smithy/config-resolver", + "@smithy/credential-provider-imds", + "@smithy/node-config-provider", + "@smithy/property-provider", + "@smithy/smithy-client", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-endpoints@3.4.2": { + "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", + "dependencies": [ + "@smithy/node-config-provider", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-hex-encoding@4.2.2": { + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-middleware@4.2.14": { + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-retry@4.3.8": { + "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", + "dependencies": [ + "@smithy/service-error-classification", + "@smithy/types", + "tslib" + ] + }, + "@smithy/util-stream@4.5.25": { + "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", + "dependencies": [ + "@smithy/fetch-http-handler", + "@smithy/node-http-handler", + "@smithy/types", + "@smithy/util-base64", + "@smithy/util-buffer-from@4.2.2", + "@smithy/util-hex-encoding", + "@smithy/util-utf8@4.2.2", + "tslib" + ] + }, + "@smithy/util-uri-escape@4.2.2": { + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dependencies": [ + "tslib" + ] + }, + "@smithy/util-utf8@2.3.0": { + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": [ + "@smithy/util-buffer-from@2.2.0", + "tslib" + ] + }, + "@smithy/util-utf8@4.2.2": { + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dependencies": [ + "@smithy/util-buffer-from@4.2.2", + "tslib" + ] + }, + "@smithy/util-waiter@4.3.0": { + "integrity": "sha512-JyjYmLAfS+pdxF92o4yLgEoy0zhayKTw73FU1aofLWwLcJw7iSqIY2exGmMTrl/lmZugP5p/zxdFSippJDfKWA==", + "dependencies": [ + "@smithy/types", + "tslib" + ] + }, + "@smithy/uuid@1.1.2": { + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dependencies": [ + "tslib" + ] + }, + "@stablelib/base64@1.0.1": { + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==" + }, + "@standard-schema/spec@1.1.0": { + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" + }, + "@tailwindcss/node@4.3.0": { + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dependencies": [ + "@jridgewell/remapping", + "enhanced-resolve", + "jiti", + "lightningcss", + "magic-string", + "source-map-js", + "tailwindcss" + ] + }, + "@tailwindcss/oxide-android-arm64@4.3.0": { + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-arm64@4.3.0": { + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-x64@4.3.0": { + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-freebsd-x64@4.3.0": { + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0": { + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@tailwindcss/oxide-linux-arm64-gnu@4.3.0": { + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-arm64-musl@4.3.0": { + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-x64-gnu@4.3.0": { + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-x64-musl@4.3.0": { + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-wasm32-wasi@4.3.0": { + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "cpu": ["wasm32"] + }, + "@tailwindcss/oxide-win32-arm64-msvc@4.3.0": { + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-win32-x64-msvc@4.3.0": { + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide@4.3.0": { + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "optionalDependencies": [ + "@tailwindcss/oxide-android-arm64", + "@tailwindcss/oxide-darwin-arm64", + "@tailwindcss/oxide-darwin-x64", + "@tailwindcss/oxide-freebsd-x64", + "@tailwindcss/oxide-linux-arm-gnueabihf", + "@tailwindcss/oxide-linux-arm64-gnu", + "@tailwindcss/oxide-linux-arm64-musl", + "@tailwindcss/oxide-linux-x64-gnu", + "@tailwindcss/oxide-linux-x64-musl", + "@tailwindcss/oxide-wasm32-wasi", + "@tailwindcss/oxide-win32-arm64-msvc", + "@tailwindcss/oxide-win32-x64-msvc" + ] + }, + "@tailwindcss/vite@4.3.0_vite@7.3.3": { + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dependencies": [ + "@tailwindcss/node", + "@tailwindcss/oxide", + "tailwindcss", + "vite" + ] + }, + "@tanstack/devtools-event-client@0.4.3": { + "integrity": "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==", + "bin": true + }, + "@tanstack/form-core@1.31.0": { + "integrity": "sha512-t5G/LnrM/U10mQgzYis/WvHc6yTDj7jyF5cYf6GjVJpEbn0et0wDUQtafiOXY8hiXJ9D1HUqBi0u76ZX05uiHw==", + "dependencies": [ + "@tanstack/devtools-event-client", + "@tanstack/pacer-lite", + "@tanstack/store" + ] + }, + "@tanstack/history@1.161.6": { + "integrity": "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==" + }, + "@tanstack/pacer-lite@0.1.1": { + "integrity": "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==" + }, + "@tanstack/query-core@5.100.9": { + "integrity": "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ==" + }, + "@tanstack/react-form@1.31.0_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-31hoeKlk45jCAnry85bOiL89FwySL9HtAhfWLHmCJ5WcvBlHJpoqnMvl6Cy8DM+7ey2eWHKiPiEL29BkG5o12w==", + "dependencies": [ + "@tanstack/form-core", + "@tanstack/react-store", + "react" + ] + }, + "@tanstack/react-query@5.100.9_react@19.2.6": { + "integrity": "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A==", + "dependencies": [ + "@tanstack/query-core", + "react" + ] + }, + "@tanstack/react-router-devtools@1.166.13_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_@tanstack+router-core@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6_csstype@3.2.3": { + "integrity": "sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA==", + "dependencies": [ + "@tanstack/react-router", + "@tanstack/router-core", + "@tanstack/router-devtools-core", + "react", + "react-dom" + ], + "optionalPeers": [ + "@tanstack/router-core" + ] + }, + "@tanstack/react-router@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-OJM7Kguc7ERnweaNRWsyWgIKcl3z23rD1B4jaxjzd9RGdnzpt2HfrWa9rggbT0Hfzhfo4D2ZmsfoTme035tniQ==", + "dependencies": [ + "@tanstack/history", + "@tanstack/react-store", + "@tanstack/router-core", + "isbot", + "react", + "react-dom" + ] + }, + "@tanstack/react-store@0.9.3_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", + "dependencies": [ + "@tanstack/store", + "react", + "react-dom", + "use-sync-external-store" + ] + }, + "@tanstack/router-core@1.169.2": { + "integrity": "sha512-5sm0DJF1A7Mz+9gy4Gz/lLovNailK3yot4vYvz9MkBUPw26uLnhQiR8hSCYxucjE0wD6Mdlc5l+Z0/XTlZ7xHw==", + "dependencies": [ + "@tanstack/history", + "cookie-es", + "seroval", + "seroval-plugins" + ] + }, + "@tanstack/router-devtools-core@1.167.3_@tanstack+router-core@1.169.2_csstype@3.2.3": { + "integrity": "sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg==", + "dependencies": [ + "@tanstack/router-core", + "clsx", + "csstype", + "goober" + ], + "optionalPeers": [ + "csstype" + ] + }, + "@tanstack/router-generator@1.166.42": { + "integrity": "sha512-2qBWC0t78r6b3vI+AbnvCZcFAvbYBDlLuWZrTjQbcjUmwG3qyeQp983tJyDuj9wb5//adG1tgAGXZkJ3aDwdBg==", + "dependencies": [ + "@babel/types", + "@tanstack/router-core", + "@tanstack/router-utils", + "@tanstack/virtual-file-routes", + "jiti", + "magic-string", + "prettier", + "zod@3.25.76" + ] + }, + "@tanstack/router-plugin@1.167.35_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_vite@7.3.3_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-UAScU5VAzLYVY4FML/Cbc5S5TucT4I8Ata05yozGOe4ZfepTKRffA5xWLtD2N+ov5svdv0KTX/kqlZnYPe28mA==", + "dependencies": [ + "@babel/core", + "@babel/plugin-syntax-jsx", + "@babel/plugin-syntax-typescript", + "@babel/template", + "@babel/traverse", + "@babel/types", + "@tanstack/react-router", + "@tanstack/router-core", + "@tanstack/router-generator", + "@tanstack/router-utils", + "@tanstack/virtual-file-routes", + "chokidar", + "unplugin", + "vite", + "zod@3.25.76" + ], + "optionalPeers": [ + "@tanstack/react-router", + "vite" + ] + }, + "@tanstack/router-utils@1.161.8": { + "integrity": "sha512-xyiLWEKjfBAVhauDSSjXxyf7s8elU6SM+V050sbkofvGmIIvkwPFtDsX7Gvwh14kBd6iCwAT+RiPvXTxAptY0Q==", + "dependencies": [ + "@babel/core", + "@babel/generator", + "@babel/parser", + "@babel/types", + "ansis", + "babel-dead-code-elimination", + "diff", + "pathe", + "tinyglobby" + ] + }, + "@tanstack/store@0.9.3": { + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==" + }, + "@tanstack/virtual-file-routes@1.161.7": { + "integrity": "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==", + "bin": true + }, + "@tybys/wasm-util@0.10.2": { + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dependencies": [ + "tslib" + ] + }, + "@types/babel__core@7.20.5": { + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": [ + "@babel/parser", + "@babel/types", + "@types/babel__generator", + "@types/babel__template", + "@types/babel__traverse" + ] + }, + "@types/babel__generator@7.27.0": { + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dependencies": [ + "@babel/types" + ] + }, + "@types/babel__template@7.4.4": { + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": [ + "@babel/parser", + "@babel/types" + ] + }, + "@types/babel__traverse@7.28.0": { + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dependencies": [ + "@babel/types" + ] + }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/react-dom@19.2.3_@types+react@19.2.14": { + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dependencies": [ + "@types/react" + ] + }, + "@types/react@19.2.14": { + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dependencies": [ + "csstype" + ] + }, + "@vitejs/plugin-react@5.2.0_vite@7.3.3": { + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dependencies": [ + "@babel/core", + "@babel/plugin-transform-react-jsx-self", + "@babel/plugin-transform-react-jsx-source", + "@rolldown/pluginutils", + "@types/babel__core", + "react-refresh", + "vite" + ] + }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, + "ansis@4.2.0": { + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==" + }, + "anymatch@3.1.3": { + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": [ + "normalize-path", + "picomatch@2.3.2" + ] + }, + "atomic-sleep@1.0.0": { + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, + "babel-dead-code-elimination@1.0.12": { + "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", + "dependencies": [ + "@babel/core", + "@babel/parser", + "@babel/traverse", + "@babel/types" + ] + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "baseline-browser-mapping@2.10.29": { + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "bin": true + }, + "better-auth@1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_react@19.2.6_react-dom@19.2.6__react@19.2.6_postgres@3.4.9": { + "integrity": "sha512-gzYaywJuhAkv9bTuFj1k6zaSKEAcabxAzYsBj0kXSMaQJVE9uS/qp2592IZmuvtMHO1ohLOP92jDPV6xVsZSoQ==", + "dependencies": [ + "@better-auth/core", + "@better-auth/drizzle-adapter", + "@better-auth/kysely-adapter", + "@better-auth/memory-adapter", + "@better-auth/mongo-adapter", + "@better-auth/prisma-adapter", + "@better-auth/telemetry", + "@better-auth/utils", + "@better-fetch/fetch", + "@noble/ciphers", + "@noble/hashes", + "better-call", + "defu", + "drizzle-kit", + "drizzle-orm", + "jose", + "kysely", + "nanostores", + "react", + "react-dom", + "zod@4.4.3" + ], + "optionalPeers": [ + "drizzle-kit", + "drizzle-orm", + "react", + "react-dom" + ] + }, + "better-call@1.3.5_zod@4.4.3": { + "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", + "dependencies": [ + "@better-auth/utils", + "@better-fetch/fetch", + "rou3", + "set-cookie-parser", + "zod@4.4.3" + ], + "optionalPeers": [ + "zod@4.4.3" + ] + }, + "binary-extensions@2.3.0": { + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" + }, + "bowser@2.14.1": { + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "browserslist@4.28.2": { + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dependencies": [ + "baseline-browser-mapping", + "caniuse-lite", + "electron-to-chromium", + "node-releases", + "update-browserslist-db" + ], + "bin": true + }, + "buffer-from@1.1.2": { + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, + "bullmq@5.76.6": { + "integrity": "sha512-vlmL3B3NVMRy6se3c7jPHn1Nhqxrg7+wlv1t3XAQFBYZNJDMLP0OO5x2AX5ca7DAuS1SU/C+VfYi+NHVoFK1QQ==", + "dependencies": [ + "cron-parser", + "ioredis", + "msgpackr", + "node-abort-controller", + "semver@7.7.4", + "tslib" + ] + }, + "caniuse-lite@1.0.30001792": { + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==" + }, + "chokidar@3.6.0": { + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": [ + "anymatch", + "braces", + "glob-parent", + "is-binary-path", + "is-glob", + "normalize-path", + "readdirp" + ], + "optionalDependencies": [ + "fsevents" + ] + }, + "class-variance-authority@0.7.1": { + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": [ + "clsx" + ] + }, + "clsx@2.1.1": { + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, + "cluster-key-slot@1.1.2": { + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" + }, + "colorette@2.0.20": { + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "convert-source-map@2.0.0": { + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "cookie-es@3.1.1": { + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==" + }, + "cron-parser@4.9.0": { + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": [ + "luxon" + ] + }, + "csstype@3.2.3": { + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "dateformat@4.6.3": { + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "defu@6.1.7": { + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==" + }, + "denque@2.1.0": { + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "detect-libc@2.1.2": { + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + }, + "diff@8.0.4": { + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==" + }, + "dotenv-expand@13.0.0": { + "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", + "dependencies": [ + "dotenv" + ] + }, + "dotenv@17.4.2": { + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==" + }, + "drizzle-kit@0.31.10": { + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "dependencies": [ + "@drizzle-team/brocli", + "@esbuild-kit/esm-loader", + "esbuild@0.25.12", + "tsx" + ], + "bin": true + }, + "drizzle-orm@0.45.2_kysely@0.28.17_postgres@3.4.9": { + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", + "dependencies": [ + "kysely", + "postgres" + ], + "optionalPeers": [ + "kysely", + "postgres" + ] + }, + "drizzle-zod@0.8.3_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_zod@4.4.3_kysely@0.28.17_postgres@3.4.9": { + "integrity": "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==", + "dependencies": [ + "drizzle-orm", + "zod@4.4.3" + ] + }, + "electron-to-chromium@1.5.353": { + "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==" + }, + "end-of-stream@1.4.5": { + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": [ + "once" + ] + }, + "enhanced-resolve@5.21.2": { + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", + "dependencies": [ + "graceful-fs", + "tapable" + ] + }, + "esbuild@0.18.20": { + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "optionalDependencies": [ + "@esbuild/android-arm@0.18.20", + "@esbuild/android-arm64@0.18.20", + "@esbuild/android-x64@0.18.20", + "@esbuild/darwin-arm64@0.18.20", + "@esbuild/darwin-x64@0.18.20", + "@esbuild/freebsd-arm64@0.18.20", + "@esbuild/freebsd-x64@0.18.20", + "@esbuild/linux-arm@0.18.20", + "@esbuild/linux-arm64@0.18.20", + "@esbuild/linux-ia32@0.18.20", + "@esbuild/linux-loong64@0.18.20", + "@esbuild/linux-mips64el@0.18.20", + "@esbuild/linux-ppc64@0.18.20", + "@esbuild/linux-riscv64@0.18.20", + "@esbuild/linux-s390x@0.18.20", + "@esbuild/linux-x64@0.18.20", + "@esbuild/netbsd-x64@0.18.20", + "@esbuild/openbsd-x64@0.18.20", + "@esbuild/sunos-x64@0.18.20", + "@esbuild/win32-arm64@0.18.20", + "@esbuild/win32-ia32@0.18.20", + "@esbuild/win32-x64@0.18.20" + ], + "scripts": true, + "bin": true + }, + "esbuild@0.25.12": { + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "optionalDependencies": [ + "@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" + ], + "scripts": true, + "bin": true + }, + "esbuild@0.27.7": { + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "optionalDependencies": [ + "@esbuild/aix-ppc64@0.27.7", + "@esbuild/android-arm@0.27.7", + "@esbuild/android-arm64@0.27.7", + "@esbuild/android-x64@0.27.7", + "@esbuild/darwin-arm64@0.27.7", + "@esbuild/darwin-x64@0.27.7", + "@esbuild/freebsd-arm64@0.27.7", + "@esbuild/freebsd-x64@0.27.7", + "@esbuild/linux-arm@0.27.7", + "@esbuild/linux-arm64@0.27.7", + "@esbuild/linux-ia32@0.27.7", + "@esbuild/linux-loong64@0.27.7", + "@esbuild/linux-mips64el@0.27.7", + "@esbuild/linux-ppc64@0.27.7", + "@esbuild/linux-riscv64@0.27.7", + "@esbuild/linux-s390x@0.27.7", + "@esbuild/linux-x64@0.27.7", + "@esbuild/netbsd-arm64@0.27.7", + "@esbuild/netbsd-x64@0.27.7", + "@esbuild/openbsd-arm64@0.27.7", + "@esbuild/openbsd-x64@0.27.7", + "@esbuild/openharmony-arm64@0.27.7", + "@esbuild/sunos-x64@0.27.7", + "@esbuild/win32-arm64@0.27.7", + "@esbuild/win32-ia32@0.27.7", + "@esbuild/win32-x64@0.27.7" + ], + "scripts": true, + "bin": true + }, + "escalade@3.2.0": { + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events@3.3.0": { + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "fast-copy@4.0.3": { + "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==" + }, + "fast-safe-stringify@2.1.1": { + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "fast-sha256@1.3.0": { + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==" + }, + "fast-xml-builder@1.2.0": { + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "dependencies": [ + "path-expression-matcher", + "xml-naming" + ] + }, + "fast-xml-parser@5.7.2": { + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "dependencies": [ + "@nodable/entities", + "fast-xml-builder", + "path-expression-matcher", + "strnum" + ], + "bin": true + }, + "fdir@6.5.0_picomatch@4.0.4": { + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dependencies": [ + "picomatch@4.0.4" + ], + "optionalPeers": [ + "picomatch@4.0.4" + ] + }, + "fetch-retry@6.0.0": { + "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==" + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true + }, + "gensync@1.0.0-beta.2": { + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-tsconfig@4.14.0": { + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dependencies": [ + "resolve-pkg-maps" + ] + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "goober@2.1.18_csstype@3.2.3": { + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "dependencies": [ + "csstype" + ] + }, + "graceful-fs@4.2.11": { + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "help-me@5.0.0": { + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "hono-pino@0.10.3_hono@4.12.18_pino@10.3.1": { + "integrity": "sha512-n0RNPIFOoq25Fg8b4D5gus4sVqI0z+8I17ibl96+p43d07UnZ0EMM/It0qSgfc7UtaC+XP5FkFmRHwBp6owsNA==", + "dependencies": [ + "defu", + "hono", + "pino" + ] + }, + "hono@4.12.18": { + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==" + }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ioredis@5.10.1": { + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "dependencies": [ + "@ioredis/commands", + "cluster-key-slot", + "debug", + "denque", + "lodash.defaults", + "lodash.isarguments", + "redis-errors", + "redis-parser", + "standard-as-callback" + ] + }, + "is-binary-path@2.1.0": { + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": [ + "binary-extensions" + ] + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "isbot@5.1.40": { + "integrity": "sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==" + }, + "jiti@2.7.0": { + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "bin": true + }, + "jose@6.2.3": { + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" + }, + "joycon@3.1.1": { + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" + }, + "js-tokens@4.0.0": { + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsesc@3.1.0": { + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": true + }, + "json5@2.2.3": { + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": true + }, + "kysely@0.28.17": { + "integrity": "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==" + }, + "lightningcss-android-arm64@1.32.0": { + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-arm64@1.32.0": { + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-x64@1.32.0": { + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "lightningcss-freebsd-x64@1.32.0": { + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "lightningcss-linux-arm-gnueabihf@1.32.0": { + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "lightningcss-linux-arm64-gnu@1.32.0": { + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-arm64-musl@1.32.0": { + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-x64-gnu@1.32.0": { + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-linux-x64-musl@1.32.0": { + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-win32-arm64-msvc@1.32.0": { + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "lightningcss-win32-x64-msvc@1.32.0": { + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "os": ["win32"], + "cpu": ["x64"] + }, + "lightningcss@1.32.0": { + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dependencies": [ + "detect-libc" + ], + "optionalDependencies": [ + "lightningcss-android-arm64", + "lightningcss-darwin-arm64", + "lightningcss-darwin-x64", + "lightningcss-freebsd-x64", + "lightningcss-linux-arm-gnueabihf", + "lightningcss-linux-arm64-gnu", + "lightningcss-linux-arm64-musl", + "lightningcss-linux-x64-gnu", + "lightningcss-linux-x64-musl", + "lightningcss-win32-arm64-msvc", + "lightningcss-win32-x64-msvc" + ] + }, + "lodash.defaults@4.2.0": { + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "lodash.isarguments@3.1.0": { + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "lru-cache@5.1.1": { + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": [ + "yallist" + ] + }, + "lucide-react@0.553.0_react@19.2.6": { + "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==", + "dependencies": [ + "react" + ] + }, + "luxon@3.7.2": { + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==" + }, + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "msgpackr-extract@3.0.3": { + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dependencies": [ + "node-gyp-build-optional-packages" + ], + "optionalDependencies": [ + "@msgpackr-extract/msgpackr-extract-darwin-arm64", + "@msgpackr-extract/msgpackr-extract-darwin-x64", + "@msgpackr-extract/msgpackr-extract-linux-arm", + "@msgpackr-extract/msgpackr-extract-linux-arm64", + "@msgpackr-extract/msgpackr-extract-linux-x64", + "@msgpackr-extract/msgpackr-extract-win32-x64" + ], + "scripts": true, + "bin": true + }, + "msgpackr@2.0.1": { + "integrity": "sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==", + "optionalDependencies": [ + "msgpackr-extract" + ] + }, + "nanoid@3.3.12": { + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "bin": true + }, + "nanoid@5.1.11": { + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "bin": true + }, + "nanostores@1.3.0": { + "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==" + }, + "node-abort-controller@3.1.1": { + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node-gyp-build-optional-packages@5.2.2": { + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dependencies": [ + "detect-libc" + ], + "bin": true + }, + "node-releases@2.0.38": { + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==" + }, + "normalize-path@3.0.0": { + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "on-exit-leak-free@2.1.2": { + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" + }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, + "openapi3-ts@4.5.0": { + "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", + "dependencies": [ + "yaml" + ] + }, + "path-expression-matcher@1.5.0": { + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==" + }, + "pathe@2.0.3": { + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch@2.3.2": { + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" + }, + "picomatch@4.0.4": { + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" + }, + "pino-abstract-transport@1.2.0": { + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": [ + "readable-stream", + "split2" + ] + }, + "pino-abstract-transport@3.0.0": { + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dependencies": [ + "split2" + ] + }, + "pino-pretty@13.1.3": { + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "dependencies": [ + "colorette", + "dateformat", + "fast-copy", + "fast-safe-stringify", + "help-me", + "joycon", + "minimist", + "on-exit-leak-free", + "pino-abstract-transport@3.0.0", + "pump", + "secure-json-parse", + "sonic-boom", + "strip-json-comments" + ], + "bin": true + }, + "pino-std-serializers@7.1.0": { + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==" + }, + "pino@10.3.1": { + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dependencies": [ + "@pinojs/redact", + "atomic-sleep", + "on-exit-leak-free", + "pino-abstract-transport@3.0.0", + "pino-std-serializers", + "process-warning", + "quick-format-unescaped", + "real-require", + "safe-stable-stringify", + "sonic-boom", + "thread-stream" + ], + "bin": true + }, + "postal-mime@2.7.4": { + "integrity": "sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==" + }, + "postcss@8.5.14": { + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dependencies": [ + "nanoid@3.3.12", + "picocolors", + "source-map-js" + ] + }, + "postgres@3.4.9": { + "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==" + }, + "prettier@3.8.3": { + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "bin": true + }, + "process-warning@5.0.0": { + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" + }, + "process@0.11.10": { + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "pump@3.0.4": { + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dependencies": [ + "end-of-stream", + "once" + ] + }, + "quick-format-unescaped@4.0.4": { + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "react-dom@19.2.6_react@19.2.6": { + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "dependencies": [ + "react", + "scheduler" + ] + }, + "react-refresh@0.18.0": { + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==" + }, + "react@19.2.6": { + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==" + }, + "readable-stream@4.7.0": { + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": [ + "abort-controller", + "buffer", + "events", + "process", + "string_decoder" + ] + }, + "readdirp@3.6.0": { + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": [ + "picomatch@2.3.2" + ] + }, + "real-require@0.2.0": { + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" + }, + "redis-errors@1.2.0": { + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser@3.0.0": { + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": [ + "redis-errors" + ] + }, + "reselect@5.1.1": { + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, + "resend@6.12.3": { + "integrity": "sha512-FkEi6YPnVL96/LvH8+QP7NaeaBy5brYXwlRqUCqZZeNL0/iyKij18IPmyPXYauT/2ODn1JG04qKz+qlJfzqzTw==", + "dependencies": [ + "postal-mime", + "svix" + ] + }, + "resolve-pkg-maps@1.0.0": { + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" + }, + "rollup@4.60.3": { + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loong64-gnu", + "@rollup/rollup-linux-loong64-musl", + "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-ppc64-musl", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-riscv64-musl", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-openbsd-x64", + "@rollup/rollup-openharmony-arm64", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-gnu", + "@rollup/rollup-win32-x64-msvc", + "fsevents" + ], + "bin": true + }, + "rou3@0.7.12": { + "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==" + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-stable-stringify@2.5.0": { + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, + "scheduler@0.27.0": { + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, + "secure-json-parse@4.1.0": { + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==" + }, + "semver@6.3.1": { + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": true + }, + "semver@7.7.4": { + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "bin": true + }, + "seroval-plugins@1.5.4_seroval@1.5.4": { + "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", + "dependencies": [ + "seroval" + ] + }, + "seroval@1.5.4": { + "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==" + }, + "set-cookie-parser@3.1.0": { + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==" + }, + "sonic-boom@4.2.1": { + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dependencies": [ + "atomic-sleep" + ] + }, + "sonner@2.0.7_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "dependencies": [ + "react", + "react-dom" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "source-map-support@0.5.21": { + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": [ + "buffer-from", + "source-map" + ] + }, + "source-map@0.6.1": { + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, + "standard-as-callback@2.1.0": { + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "standardwebhooks@1.0.0": { + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "dependencies": [ + "@stablelib/base64", + "fast-sha256" + ] + }, + "stoker@2.0.1_@hono+zod-openapi@1.4.0__hono@4.12.18__zod@4.4.3_hono@4.12.18_zod@4.4.3": { + "integrity": "sha512-liSQNnJmn8fWSEan7sVaFe6iSHuN3X02fDGLS6snwW+FUuKi5HmKUHm3P+Kzr5xiDPqRpmSTtmGEBbSL9H2zkQ==", + "dependencies": [ + "@hono/zod-openapi", + "hono" + ], + "optionalPeers": [ + "@hono/zod-openapi" + ] + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": [ + "safe-buffer" + ] + }, + "strip-json-comments@5.0.3": { + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==" + }, + "strnum@2.3.0": { + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==" + }, + "svix@1.92.2": { + "integrity": "sha512-ZmuA3UVvlnF9EgxlzmPtF7CKjQb64Z6OFlyfdDfU0sdcC7dJa+3aOYX5B9mA+RS6ch1AxBa4UP/l6KmqfGtWBQ==", + "dependencies": [ + "standardwebhooks" + ] + }, + "tagged-tag@1.0.0": { + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" + }, + "tailwind-merge@3.5.0": { + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==" + }, + "tailwindcss@4.3.0": { + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==" + }, + "tapable@2.3.3": { + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==" + }, + "thread-stream@4.0.0": { + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "dependencies": [ + "real-require" + ] + }, + "tinyglobby@0.2.16": { + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dependencies": [ + "fdir", + "picomatch@4.0.4" + ] + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] + }, + "tslib@2.8.1": { + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "tsx@4.21.0": { + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dependencies": [ + "esbuild@0.27.7", + "get-tsconfig" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "type-fest@5.6.0": { + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "dependencies": [ + "tagged-tag" + ] + }, + "typescript@5.9.3": { + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "bin": true + }, + "unplugin@3.0.0": { + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "dependencies": [ + "@jridgewell/remapping", + "picomatch@4.0.4", + "webpack-virtual-modules" + ] + }, + "update-browserslist-db@1.2.3_browserslist@4.28.2": { + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dependencies": [ + "browserslist", + "escalade", + "picocolors" + ], + "bin": true + }, + "use-sync-external-store@1.6.0_react@19.2.6": { + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dependencies": [ + "react" + ] + }, + "vite@7.3.3": { + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "dependencies": [ + "esbuild@0.27.7", + "fdir", + "picomatch@4.0.4", + "postcss", + "rollup", + "tinyglobby" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "webpack-virtual-modules@0.6.2": { + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" + }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "xml-naming@0.1.0": { + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==" + }, + "yallist@3.1.1": { + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yaml@2.8.4": { + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "bin": true + }, + "zod@3.25.76": { + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" + }, + "zod@4.4.3": { + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + }, + "zustand@5.0.13_@types+react@19.2.14_react@19.2.6": { + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react", + "react" + ] + } + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@base-ui/react@^1.2.0", + "npm:@better-fetch/fetch@^1.1.18", + "npm:@tailwindcss/vite@^4.1.17", + "npm:@tanstack/react-form@1", + "npm:@tanstack/react-query@^5.90.7", + "npm:@tanstack/react-router-devtools@^1.134.13", + "npm:@tanstack/react-router@^1.134.13", + "npm:@tanstack/router-plugin@^1.134.14", + "npm:@types/react-dom@^19.1.9", + "npm:@types/react@^19.1.16", + "npm:@vitejs/plugin-react@^5.0.4", + "npm:better-auth@^1.3.34", + "npm:class-variance-authority@~0.7.1", + "npm:clsx@^2.1.1", + "npm:lucide-react@0.553", + "npm:react-dom@^19.1.1", + "npm:react@^19.1.1", + "npm:sonner@^2.0.7", + "npm:tailwind-merge@^3.3.1", + "npm:tailwindcss@^4.1.17", + "npm:typescript@~5.9.3", + "npm:vite@^7.1.7", + "npm:zod@^4.2.1", + "npm:zustand@^5.0.8" + ] + } + } +} diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 37bcfaa..50dc028 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -4,18 +4,14 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite dev", - "build": "pnpm -w build:packages && vite build && tsc --noEmit", + "dev": "vite dev --port 3000", + "build": "vite build", "typecheck": "tsc --noEmit", - "lint": "biome check .", - "start": "vite", "preview": "vite preview" }, "dependencies": { "@base-ui/react": "^1.2.0", "@better-fetch/fetch": "^1.1.18", - "@repo/db": "workspace:*", - "@repo/shared": "workspace:*", "@tailwindcss/vite": "^4.1.17", "@tanstack/react-form": "^1.0.0", "@tanstack/react-query": "^5.90.7", @@ -30,7 +26,6 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.17", - "zod": "^4.2.1", "zustand": "^5.0.8" }, @@ -39,7 +34,6 @@ "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", - "tw-animate-css": "^1.4.0", "typescript": "~5.9.3", "vite": "^7.1.7" } diff --git a/apps/frontend/src/components/error-fallback.tsx b/apps/frontend/src/components/error-fallback.tsx new file mode 100644 index 0000000..d5fadcc --- /dev/null +++ b/apps/frontend/src/components/error-fallback.tsx @@ -0,0 +1,42 @@ +import { useRouter } from "@tanstack/react-router"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +type ErrorFallbackProps = { + error: Error; +}; + +function ErrorFallback({ error }: ErrorFallbackProps) { + const router = useRouter(); + + return ( +
+ + + Something went wrong + + +

+ {error.message || "An unexpected error occurred"} +

+ {import.meta.env.DEV && error.stack && ( +
+							{error.stack}
+						
+ )} +
+ + + +
+
+ ); +} + +export { ErrorFallback }; diff --git a/apps/frontend/src/lib/api.ts b/apps/frontend/src/lib/api.ts index e497453..bc23915 100644 --- a/apps/frontend/src/lib/api.ts +++ b/apps/frontend/src/lib/api.ts @@ -1,4 +1,4 @@ -const BASE_URL = import.meta.env.VITE_API_URL ?? "http://localhost:9999"; +const BASE_URL = import.meta.env.VITE_API_URL ?? ""; // ─── Typed API error ────────────────────────────────────────────────────────── // Thrown for any non-2xx response so callers can distinguish HTTP failures diff --git a/apps/frontend/src/lib/auth-client.ts b/apps/frontend/src/lib/auth-client.ts index 3256f1b..a3ba556 100644 --- a/apps/frontend/src/lib/auth-client.ts +++ b/apps/frontend/src/lib/auth-client.ts @@ -1,7 +1,7 @@ import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ - baseURL: import.meta.env.VITE_API_URL || "http://localhost:9999", + baseURL: import.meta.env.VITE_API_URL, }); export const { signIn, signUp, signOut, useSession } = authClient; diff --git a/apps/frontend/src/routes/__root.tsx b/apps/frontend/src/routes/__root.tsx index e8e61ad..d42bffc 100644 --- a/apps/frontend/src/routes/__root.tsx +++ b/apps/frontend/src/routes/__root.tsx @@ -1,7 +1,12 @@ import type { QueryClient } from "@tanstack/react-query"; -import { createRootRouteWithContext, Outlet } from "@tanstack/react-router"; +import { + createRootRouteWithContext, + Link, + Outlet, +} from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { Toaster } from "sonner"; +import { ErrorFallback } from "@/components/error-fallback"; import { sessionQueryOptions } from "@/services/auth"; interface RouterContext { @@ -9,15 +14,26 @@ interface RouterContext { } export const Route = createRootRouteWithContext()({ - // Prefetch the session once at the root of the route tree. - // Every child route receives context.session — no per-page auth fetch. - // After any auth mutation (signIn, signOut, signUp), invalidate - // sessionQueryOptions.queryKey and navigate; the router re-runs this. beforeLoad: async ({ context }) => { const session = await context.queryClient.ensureQueryData(sessionQueryOptions); return { session }; }, + errorComponent: ErrorFallback, + notFoundComponent: () => ( +
+
+

404

+

Page not found

+ + Go home + +
+
+ ), component: RootComponent, }); diff --git a/deno.json b/deno.json index f22b58d..671d860 100644 --- a/deno.json +++ b/deno.json @@ -1,29 +1,29 @@ { - "workspace": [ - "apps/backend", - "packages/shared", - "packages/db", - "packages/email-templates" - ], - "tasks": { - "dev": "deno task --cwd=apps/backend dev", - "dev:backend": "deno task --cwd=apps/backend dev", - "dev:frontend": "deno run -A npm:vite dev --config apps/frontend/vite.config.ts", - "start": "deno task --cwd=apps/backend start", - "worker": "deno task --cwd=apps/backend worker", - "test": "deno task --cwd=apps/backend test", - "lint": "deno lint", - "fmt": "deno fmt", - "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", - "db:migrate": "deno task --cwd=apps/backend db:migrate", - "db:studio": "deno task --cwd=apps/backend db:studio", - "db:generate": "deno task --cwd=apps/backend db:generate" - }, - "imports": { - "@std/expect": "jsr:@std/expect@^1.0.19", - "@std/testing/bdd": "jsr:@std/testing@^1.0.18/bdd" - }, - "exclude": ["apps/frontend"], - "nodeModulesDir": "auto", - "sloppyImports": true + "workspace": [ + "apps/backend", + "apps/frontend", + "packages/shared", + "packages/db", + "packages/email-templates" + ], + "tasks": { + "dev": "deno task --cwd=apps/backend dev", + "dev:backend": "deno task --cwd=apps/backend dev", + "dev:frontend": "deno task --cwd=apps/frontend dev", + "start": "deno task --cwd=apps/backend start", + "worker": "deno task --cwd=apps/backend worker", + "test": "deno task --cwd=apps/backend test", + "lint": "deno lint", + "fmt": "deno fmt", + "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", + "db:migrate": "deno task --cwd=apps/backend db:migrate", + "db:studio": "deno task --cwd=apps/backend db:studio", + "db:generate": "deno task --cwd=apps/backend db:generate" + }, + "imports": { + "@std/expect": "jsr:@std/expect@^1.0.19", + "@std/testing/bdd": "jsr:@std/testing@^1.0.18/bdd" + }, + "nodeModulesDir": "auto", + "sloppyImports": true } diff --git a/deno.lock b/deno.lock index 59b5b02..3465f7c 100644 --- a/deno.lock +++ b/deno.lock @@ -10,14 +10,28 @@ "npm:@aws-sdk/client-s3@*": "3.1045.0", "npm:@aws-sdk/s3-request-presigner@*": "3.1045.0", "npm:@axiomhq/pino@*": "1.6.1", + "npm:@base-ui/react@^1.2.0": "1.4.1_@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@better-fetch/fetch@^1.1.18": "1.1.21", "npm:@biomejs/biome@2.3.7": "2.3.7", "npm:@hono/swagger-ui@*": "0.6.1_hono@4.12.18", "npm:@hono/zod-openapi@*": "1.4.0_hono@4.12.18_zod@4.4.3", "npm:@hono/zod-validator@*": "0.8.0_hono@4.12.18_zod@4.4.3", "npm:@node-rs/argon2@*": "2.0.2", "npm:@scalar/hono-api-reference@*": "0.10.14_hono@4.12.18", - "npm:better-auth@*": "1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9", + "npm:@tailwindcss/vite@^4.1.17": "4.3.0_vite@7.3.3", + "npm:@tanstack/react-form@1": "1.31.0_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@tanstack/react-query@^5.90.7": "5.100.9_react@19.2.6", + "npm:@tanstack/react-router-devtools@^1.134.13": "1.166.13_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_@tanstack+router-core@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6_csstype@3.2.3", + "npm:@tanstack/react-router@^1.134.13": "1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@tanstack/router-plugin@^1.134.14": "1.167.35_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_vite@7.3.3_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@types/react-dom@^19.1.9": "19.2.3_@types+react@19.2.14", + "npm:@types/react@^19.1.16": "19.2.14", + "npm:@vitejs/plugin-react@^5.0.4": "5.2.0_vite@7.3.3", + "npm:better-auth@*": "1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:better-auth@^1.3.34": "1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_react@19.2.6_react-dom@19.2.6__react@19.2.6", "npm:bullmq@*": "5.76.6", + "npm:class-variance-authority@~0.7.1": "0.7.1", + "npm:clsx@^2.1.1": "2.1.1", "npm:dotenv-expand@*": "13.0.0", "npm:dotenv@*": "17.4.2", "npm:drizzle-kit@*": "0.31.10", @@ -26,13 +40,23 @@ "npm:hono-pino@*": "0.10.3_hono@4.12.18_pino@10.3.1", "npm:hono@*": "4.12.18", "npm:ioredis@*": "5.10.1", + "npm:lucide-react@0.553": "0.553.0_react@19.2.6", "npm:pino-pretty@*": "13.1.3", "npm:pino@*": "10.3.1", "npm:postgres@*": "3.4.9", + "npm:react-dom@^19.1.1": "19.2.6_react@19.2.6", + "npm:react@^19.1.1": "19.2.6", "npm:resend@*": "6.12.3", + "npm:sonner@^2.0.7": "2.0.7_react@19.2.6_react-dom@19.2.6__react@19.2.6", "npm:stoker@*": "2.0.1_@hono+zod-openapi@1.4.0__hono@4.12.18__zod@4.4.3_hono@4.12.18_zod@4.4.3", - "npm:vite@*": "8.0.11", - "npm:zod@*": "4.4.3" + "npm:tailwind-merge@^3.3.1": "3.5.0", + "npm:tailwindcss@^4.1.17": "4.3.0", + "npm:typescript@~5.9.3": "5.9.3", + "npm:vite@*": "7.3.3", + "npm:vite@^7.1.7": "7.3.3", + "npm:zod@*": "4.4.3", + "npm:zod@^4.2.1": "4.4.3", + "npm:zustand@^5.0.8": "5.0.13_@types+react@19.2.14_react@19.2.6" }, "jsr": { "@std/assert@1.0.19": { @@ -71,7 +95,7 @@ "integrity": "sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q==", "dependencies": [ "openapi3-ts", - "zod" + "zod@4.4.3" ] }, "@aws-crypto/crc32@5.2.0": { @@ -619,6 +643,191 @@ "pino-abstract-transport@1.2.0" ] }, + "@babel/code-frame@7.29.0": { + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dependencies": [ + "@babel/helper-validator-identifier", + "js-tokens", + "picocolors" + ] + }, + "@babel/compat-data@7.29.3": { + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==" + }, + "@babel/core@7.29.0": { + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-compilation-targets", + "@babel/helper-module-transforms", + "@babel/helpers", + "@babel/parser", + "@babel/template", + "@babel/traverse", + "@babel/types", + "@jridgewell/remapping", + "convert-source-map", + "debug", + "gensync", + "json5", + "semver@6.3.1" + ] + }, + "@babel/generator@7.29.1": { + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dependencies": [ + "@babel/parser", + "@babel/types", + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping", + "jsesc" + ] + }, + "@babel/helper-compilation-targets@7.28.6": { + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dependencies": [ + "@babel/compat-data", + "@babel/helper-validator-option", + "browserslist", + "lru-cache", + "semver@6.3.1" + ] + }, + "@babel/helper-globals@7.28.0": { + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" + }, + "@babel/helper-module-imports@7.28.6": { + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dependencies": [ + "@babel/traverse", + "@babel/types" + ] + }, + "@babel/helper-module-transforms@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dependencies": [ + "@babel/core", + "@babel/helper-module-imports", + "@babel/helper-validator-identifier", + "@babel/traverse" + ] + }, + "@babel/helper-plugin-utils@7.28.6": { + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==" + }, + "@babel/helper-string-parser@7.27.1": { + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier@7.28.5": { + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/helper-validator-option@7.27.1": { + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==" + }, + "@babel/helpers@7.29.2": { + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dependencies": [ + "@babel/template", + "@babel/types" + ] + }, + "@babel/parser@7.29.3": { + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dependencies": [ + "@babel/types" + ], + "bin": true + }, + "@babel/plugin-syntax-jsx@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-syntax-typescript@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-transform-react-jsx-self@7.27.1_@babel+core@7.29.0": { + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-transform-react-jsx-source@7.27.1_@babel+core@7.29.0": { + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/runtime@7.29.2": { + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==" + }, + "@babel/template@7.28.6": { + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dependencies": [ + "@babel/code-frame", + "@babel/parser", + "@babel/types" + ] + }, + "@babel/traverse@7.29.0": { + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-globals", + "@babel/parser", + "@babel/template", + "@babel/types", + "debug" + ] + }, + "@babel/types@7.29.0": { + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dependencies": [ + "@babel/helper-string-parser", + "@babel/helper-validator-identifier" + ] + }, + "@base-ui/react@1.4.1_@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==", + "dependencies": [ + "@babel/runtime", + "@base-ui/utils", + "@floating-ui/react-dom", + "@floating-ui/utils", + "@types/react", + "react", + "react-dom", + "use-sync-external-store" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@base-ui/utils@0.2.8_@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==", + "dependencies": [ + "@babel/runtime", + "@floating-ui/utils", + "@types/react", + "react", + "react-dom", + "reselect", + "use-sync-external-store" + ], + "optionalPeers": [ + "@types/react" + ] + }, "@better-auth/core@1.6.10_@better-auth+utils@0.4.0_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0": { "integrity": "sha512-13h/rfSGMLl7zwyOb1BSlxAQZs2nQqn/xFI/bxB7zQuS95hVgTmNbKqhHtJYkNDtuJCcjEf1sNtLBHkvaPT/vw==", "dependencies": [ @@ -630,7 +839,7 @@ "jose", "kysely", "nanostores", - "zod" + "zod@4.4.3" ] }, "@better-auth/drizzle-adapter@1.6.10_@better-auth+core@1.6.10__@better-auth+utils@0.4.0__@better-fetch+fetch@1.1.21__better-call@1.3.5___zod@4.4.3__jose@6.2.3__kysely@0.28.17__nanostores@1.3.0_@better-auth+utils@0.4.0_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_@better-fetch+fetch@1.1.21_better-call@1.3.5__zod@4.4.3_jose@6.2.3_kysely@0.28.17_nanostores@1.3.0_postgres@3.4.9": { @@ -1155,6 +1364,30 @@ "os": ["win32"], "cpu": ["x64"] }, + "@floating-ui/core@1.7.5": { + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "dependencies": [ + "@floating-ui/utils" + ] + }, + "@floating-ui/dom@1.7.6": { + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "dependencies": [ + "@floating-ui/core", + "@floating-ui/utils" + ] + }, + "@floating-ui/react-dom@2.1.8_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "dependencies": [ + "@floating-ui/dom", + "react", + "react-dom" + ] + }, + "@floating-ui/utils@0.2.11": { + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==" + }, "@hono/swagger-ui@0.6.1_hono@4.12.18": { "integrity": "sha512-sJTvldu1GPeEPfyeLG7gRj+W4vEuD+JDi+JjJ3TJs/DvMUtBLs0KJO5yokGegWWdy5qrbdnQGekbhgNRmPmYKQ==", "dependencies": [ @@ -1168,19 +1401,46 @@ "@hono/zod-validator", "hono", "openapi3-ts", - "zod" + "zod@4.4.3" ] }, "@hono/zod-validator@0.8.0_hono@4.12.18_zod@4.4.3": { "integrity": "sha512-5uS4S1/LKtZQYvD4BtpPUFkOv8d1wNxHHrChm26buMiEYc1FrHWvDUaKVBwkiVtvSExHSpLGDvcnpI2Copyj9w==", "dependencies": [ "hono", - "zod" + "zod@4.4.3" ] }, "@ioredis/commands@1.5.1": { "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==" }, + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/remapping@2.3.5": { + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/sourcemap-codec@1.5.5": { + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": { "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", "os": ["darwin"], @@ -1219,14 +1479,6 @@ "@tybys/wasm-util" ] }, - "@napi-rs/wasm-runtime@1.1.4_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0": { - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dependencies": [ - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util" - ] - }, "@noble/ciphers@2.2.0": { "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==" }, @@ -1289,7 +1541,7 @@ "@node-rs/argon2-wasm32-wasi@2.0.2": { "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", "dependencies": [ - "@napi-rs/wasm-runtime@0.2.12" + "@napi-rs/wasm-runtime" ], "cpu": ["wasm32"] }, @@ -1330,93 +1582,136 @@ "@opentelemetry/semantic-conventions@1.40.0": { "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==" }, - "@oxc-project/types@0.128.0": { - "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==" - }, "@pinojs/redact@0.4.0": { "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" }, - "@rolldown/binding-android-arm64@1.0.0-rc.18": { - "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "@rolldown/pluginutils@1.0.0-rc.3": { + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==" + }, + "@rollup/rollup-android-arm-eabi@4.60.3": { + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@rollup/rollup-android-arm64@4.60.3": { + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", "os": ["android"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-arm64@1.0.0-rc.18": { - "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "@rollup/rollup-darwin-arm64@4.60.3": { + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-x64@1.0.0-rc.18": { - "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "@rollup/rollup-darwin-x64@4.60.3": { + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", "os": ["darwin"], "cpu": ["x64"] }, - "@rolldown/binding-freebsd-x64@1.0.0-rc.18": { - "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "@rollup/rollup-freebsd-arm64@4.60.3": { + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@rollup/rollup-freebsd-x64@4.60.3": { + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18": { - "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "@rollup/rollup-linux-arm-gnueabihf@4.60.3": { + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm-musleabihf@4.60.3": { + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", "os": ["linux"], "cpu": ["arm"] }, - "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18": { - "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "@rollup/rollup-linux-arm64-gnu@4.60.3": { + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-arm64-musl@1.0.0-rc.18": { - "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "@rollup/rollup-linux-arm64-musl@4.60.3": { + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18": { - "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "@rollup/rollup-linux-loong64-gnu@4.60.3": { + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-loong64-musl@4.60.3": { + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.60.3": { + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18": { - "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "@rollup/rollup-linux-ppc64-musl@4.60.3": { + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.60.3": { + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-riscv64-musl@4.60.3": { + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-s390x-gnu@4.60.3": { + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", "os": ["linux"], "cpu": ["s390x"] }, - "@rolldown/binding-linux-x64-gnu@1.0.0-rc.18": { - "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "@rollup/rollup-linux-x64-gnu@4.60.3": { + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-linux-x64-musl@1.0.0-rc.18": { - "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "@rollup/rollup-linux-x64-musl@4.60.3": { + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-openharmony-arm64@1.0.0-rc.18": { - "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "@rollup/rollup-openbsd-x64@4.60.3": { + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-openharmony-arm64@4.60.3": { + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rolldown/binding-wasm32-wasi@1.0.0-rc.18": { - "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", - "dependencies": [ - "@emnapi/core", - "@emnapi/runtime", - "@napi-rs/wasm-runtime@1.1.4_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0" - ], - "cpu": ["wasm32"] - }, - "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18": { - "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "@rollup/rollup-win32-arm64-msvc@4.60.3": { + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", "os": ["win32"], "cpu": ["arm64"] }, - "@rolldown/binding-win32-x64-msvc@1.0.0-rc.18": { - "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "@rollup/rollup-win32-ia32-msvc@4.60.3": { + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@rollup/rollup-win32-x64-gnu@4.60.3": { + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", "os": ["win32"], "cpu": ["x64"] }, - "@rolldown/pluginutils@1.0.0-rc.18": { - "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==" + "@rollup/rollup-win32-x64-msvc@4.60.3": { + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "os": ["win32"], + "cpu": ["x64"] }, "@scalar/client-side-rendering@0.1.7": { "integrity": "sha512-IDzjKF93jrOljlvKBsLHXT1FPWgz56jFrMPC+iLihREp1qH8wF92mG8Zpakw8cURkEuw5WijRk0xNBP2moGyuw==", @@ -1440,7 +1735,7 @@ "@scalar/helpers", "nanoid@5.1.11", "type-fest", - "zod" + "zod@4.4.3" ] }, "@smithy/chunked-blob-reader-native@4.2.3": { @@ -1888,25 +2183,349 @@ "@standard-schema/spec@1.1.0": { "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" }, + "@tailwindcss/node@4.3.0": { + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dependencies": [ + "@jridgewell/remapping", + "enhanced-resolve", + "jiti", + "lightningcss", + "magic-string", + "source-map-js", + "tailwindcss" + ] + }, + "@tailwindcss/oxide-android-arm64@4.3.0": { + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-arm64@4.3.0": { + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-x64@4.3.0": { + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-freebsd-x64@4.3.0": { + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0": { + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@tailwindcss/oxide-linux-arm64-gnu@4.3.0": { + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-arm64-musl@4.3.0": { + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-x64-gnu@4.3.0": { + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-x64-musl@4.3.0": { + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-wasm32-wasi@4.3.0": { + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "cpu": ["wasm32"] + }, + "@tailwindcss/oxide-win32-arm64-msvc@4.3.0": { + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-win32-x64-msvc@4.3.0": { + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide@4.3.0": { + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "optionalDependencies": [ + "@tailwindcss/oxide-android-arm64", + "@tailwindcss/oxide-darwin-arm64", + "@tailwindcss/oxide-darwin-x64", + "@tailwindcss/oxide-freebsd-x64", + "@tailwindcss/oxide-linux-arm-gnueabihf", + "@tailwindcss/oxide-linux-arm64-gnu", + "@tailwindcss/oxide-linux-arm64-musl", + "@tailwindcss/oxide-linux-x64-gnu", + "@tailwindcss/oxide-linux-x64-musl", + "@tailwindcss/oxide-wasm32-wasi", + "@tailwindcss/oxide-win32-arm64-msvc", + "@tailwindcss/oxide-win32-x64-msvc" + ] + }, + "@tailwindcss/vite@4.3.0_vite@7.3.3": { + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dependencies": [ + "@tailwindcss/node", + "@tailwindcss/oxide", + "tailwindcss", + "vite" + ] + }, + "@tanstack/devtools-event-client@0.4.3": { + "integrity": "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==", + "bin": true + }, + "@tanstack/form-core@1.31.0": { + "integrity": "sha512-t5G/LnrM/U10mQgzYis/WvHc6yTDj7jyF5cYf6GjVJpEbn0et0wDUQtafiOXY8hiXJ9D1HUqBi0u76ZX05uiHw==", + "dependencies": [ + "@tanstack/devtools-event-client", + "@tanstack/pacer-lite", + "@tanstack/store" + ] + }, + "@tanstack/history@1.161.6": { + "integrity": "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==" + }, + "@tanstack/pacer-lite@0.1.1": { + "integrity": "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==" + }, + "@tanstack/query-core@5.100.9": { + "integrity": "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ==" + }, + "@tanstack/react-form@1.31.0_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-31hoeKlk45jCAnry85bOiL89FwySL9HtAhfWLHmCJ5WcvBlHJpoqnMvl6Cy8DM+7ey2eWHKiPiEL29BkG5o12w==", + "dependencies": [ + "@tanstack/form-core", + "@tanstack/react-store", + "react" + ] + }, + "@tanstack/react-query@5.100.9_react@19.2.6": { + "integrity": "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A==", + "dependencies": [ + "@tanstack/query-core", + "react" + ] + }, + "@tanstack/react-router-devtools@1.166.13_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_@tanstack+router-core@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6_csstype@3.2.3": { + "integrity": "sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA==", + "dependencies": [ + "@tanstack/react-router", + "@tanstack/router-core", + "@tanstack/router-devtools-core", + "react", + "react-dom" + ], + "optionalPeers": [ + "@tanstack/router-core" + ] + }, + "@tanstack/react-router@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-OJM7Kguc7ERnweaNRWsyWgIKcl3z23rD1B4jaxjzd9RGdnzpt2HfrWa9rggbT0Hfzhfo4D2ZmsfoTme035tniQ==", + "dependencies": [ + "@tanstack/history", + "@tanstack/react-store", + "@tanstack/router-core", + "isbot", + "react", + "react-dom" + ] + }, + "@tanstack/react-store@0.9.3_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", + "dependencies": [ + "@tanstack/store", + "react", + "react-dom", + "use-sync-external-store" + ] + }, + "@tanstack/router-core@1.169.2": { + "integrity": "sha512-5sm0DJF1A7Mz+9gy4Gz/lLovNailK3yot4vYvz9MkBUPw26uLnhQiR8hSCYxucjE0wD6Mdlc5l+Z0/XTlZ7xHw==", + "dependencies": [ + "@tanstack/history", + "cookie-es", + "seroval", + "seroval-plugins" + ] + }, + "@tanstack/router-devtools-core@1.167.3_@tanstack+router-core@1.169.2_csstype@3.2.3": { + "integrity": "sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg==", + "dependencies": [ + "@tanstack/router-core", + "clsx", + "csstype", + "goober" + ], + "optionalPeers": [ + "csstype" + ] + }, + "@tanstack/router-generator@1.166.42": { + "integrity": "sha512-2qBWC0t78r6b3vI+AbnvCZcFAvbYBDlLuWZrTjQbcjUmwG3qyeQp983tJyDuj9wb5//adG1tgAGXZkJ3aDwdBg==", + "dependencies": [ + "@babel/types", + "@tanstack/router-core", + "@tanstack/router-utils", + "@tanstack/virtual-file-routes", + "jiti", + "magic-string", + "prettier", + "zod@3.25.76" + ] + }, + "@tanstack/router-plugin@1.167.35_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_vite@7.3.3_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-UAScU5VAzLYVY4FML/Cbc5S5TucT4I8Ata05yozGOe4ZfepTKRffA5xWLtD2N+ov5svdv0KTX/kqlZnYPe28mA==", + "dependencies": [ + "@babel/core", + "@babel/plugin-syntax-jsx", + "@babel/plugin-syntax-typescript", + "@babel/template", + "@babel/traverse", + "@babel/types", + "@tanstack/react-router", + "@tanstack/router-core", + "@tanstack/router-generator", + "@tanstack/router-utils", + "@tanstack/virtual-file-routes", + "chokidar", + "unplugin", + "vite", + "zod@3.25.76" + ], + "optionalPeers": [ + "@tanstack/react-router", + "vite" + ] + }, + "@tanstack/router-utils@1.161.8": { + "integrity": "sha512-xyiLWEKjfBAVhauDSSjXxyf7s8elU6SM+V050sbkofvGmIIvkwPFtDsX7Gvwh14kBd6iCwAT+RiPvXTxAptY0Q==", + "dependencies": [ + "@babel/core", + "@babel/generator", + "@babel/parser", + "@babel/types", + "ansis", + "babel-dead-code-elimination", + "diff", + "pathe", + "tinyglobby" + ] + }, + "@tanstack/store@0.9.3": { + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==" + }, + "@tanstack/virtual-file-routes@1.161.7": { + "integrity": "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==", + "bin": true + }, "@tybys/wasm-util@0.10.2": { "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dependencies": [ "tslib" ] }, + "@types/babel__core@7.20.5": { + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": [ + "@babel/parser", + "@babel/types", + "@types/babel__generator", + "@types/babel__template", + "@types/babel__traverse" + ] + }, + "@types/babel__generator@7.27.0": { + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dependencies": [ + "@babel/types" + ] + }, + "@types/babel__template@7.4.4": { + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": [ + "@babel/parser", + "@babel/types" + ] + }, + "@types/babel__traverse@7.28.0": { + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dependencies": [ + "@babel/types" + ] + }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/react-dom@19.2.3_@types+react@19.2.14": { + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dependencies": [ + "@types/react" + ] + }, + "@types/react@19.2.14": { + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dependencies": [ + "csstype" + ] + }, + "@vitejs/plugin-react@5.2.0_vite@7.3.3": { + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dependencies": [ + "@babel/core", + "@babel/plugin-transform-react-jsx-self", + "@babel/plugin-transform-react-jsx-source", + "@rolldown/pluginutils", + "@types/babel__core", + "react-refresh", + "vite" + ] + }, "abort-controller@3.0.0": { "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dependencies": [ "event-target-shim" ] }, + "ansis@4.2.0": { + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==" + }, + "anymatch@3.1.3": { + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": [ + "normalize-path", + "picomatch@2.3.2" + ] + }, "atomic-sleep@1.0.0": { "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "babel-dead-code-elimination@1.0.12": { + "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", + "dependencies": [ + "@babel/core", + "@babel/parser", + "@babel/traverse", + "@babel/types" + ] + }, "base64-js@1.5.1": { "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, - "better-auth@1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9": { + "baseline-browser-mapping@2.10.29": { + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "bin": true + }, + "better-auth@1.6.10_drizzle-kit@0.31.10_drizzle-orm@0.45.2__kysely@0.28.17__postgres@3.4.9_react@19.2.6_react-dom@19.2.6__react@19.2.6": { "integrity": "sha512-gzYaywJuhAkv9bTuFj1k6zaSKEAcabxAzYsBj0kXSMaQJVE9uS/qp2592IZmuvtMHO1ohLOP92jDPV6xVsZSoQ==", "dependencies": [ "@better-auth/core", @@ -1927,11 +2546,15 @@ "jose", "kysely", "nanostores", - "zod" + "react", + "react-dom", + "zod@4.4.3" ], "optionalPeers": [ "drizzle-kit", - "drizzle-orm" + "drizzle-orm", + "react", + "react-dom" ] }, "better-call@1.3.5_zod@4.4.3": { @@ -1941,15 +2564,35 @@ "@better-fetch/fetch", "rou3", "set-cookie-parser", - "zod" + "zod@4.4.3" ], "optionalPeers": [ - "zod" + "zod@4.4.3" ] }, + "binary-extensions@2.3.0": { + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" + }, "bowser@2.14.1": { "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "browserslist@4.28.2": { + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dependencies": [ + "baseline-browser-mapping", + "caniuse-lite", + "electron-to-chromium", + "node-releases", + "update-browserslist-db" + ], + "bin": true + }, "buffer-from@1.1.2": { "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, @@ -1967,22 +2610,58 @@ "ioredis", "msgpackr", "node-abort-controller", - "semver", + "semver@7.7.4", "tslib" ] }, + "caniuse-lite@1.0.30001792": { + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==" + }, + "chokidar@3.6.0": { + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": [ + "anymatch", + "braces", + "glob-parent", + "is-binary-path", + "is-glob", + "normalize-path", + "readdirp" + ], + "optionalDependencies": [ + "fsevents" + ] + }, + "class-variance-authority@0.7.1": { + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": [ + "clsx" + ] + }, + "clsx@2.1.1": { + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "cluster-key-slot@1.1.2": { "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" }, "colorette@2.0.20": { "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "convert-source-map@2.0.0": { + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "cookie-es@3.1.1": { + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==" + }, "cron-parser@4.9.0": { "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", "dependencies": [ "luxon" ] }, + "csstype@3.2.3": { + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, "dateformat@4.6.3": { "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" }, @@ -2001,6 +2680,9 @@ "detect-libc@2.1.2": { "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" }, + "diff@8.0.4": { + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==" + }, "dotenv-expand@13.0.0": { "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", "dependencies": [ @@ -2035,15 +2717,25 @@ "integrity": "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==", "dependencies": [ "drizzle-orm", - "zod" + "zod@4.4.3" ] }, + "electron-to-chromium@1.5.353": { + "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==" + }, "end-of-stream@1.4.5": { "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dependencies": [ "once" ] }, + "enhanced-resolve@5.21.2": { + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", + "dependencies": [ + "graceful-fs", + "tapable" + ] + }, "esbuild@0.18.20": { "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "optionalDependencies": [ @@ -2139,6 +2831,9 @@ "scripts": true, "bin": true }, + "escalade@3.2.0": { + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, "event-target-shim@5.0.1": { "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, @@ -2174,26 +2869,50 @@ "fdir@6.5.0_picomatch@4.0.4": { "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dependencies": [ - "picomatch" + "picomatch@4.0.4" ], "optionalPeers": [ - "picomatch" + "picomatch@4.0.4" ] }, "fetch-retry@6.0.0": { "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==" }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, "fsevents@2.3.3": { "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "os": ["darwin"], "scripts": true }, + "gensync@1.0.0-beta.2": { + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, "get-tsconfig@4.14.0": { "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dependencies": [ "resolve-pkg-maps" ] }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "goober@2.1.18_csstype@3.2.3": { + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "dependencies": [ + "csstype" + ] + }, + "graceful-fs@4.2.11": { + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "help-me@5.0.0": { "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" }, @@ -2225,12 +2944,48 @@ "standard-as-callback" ] }, + "is-binary-path@2.1.0": { + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": [ + "binary-extensions" + ] + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "isbot@5.1.40": { + "integrity": "sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==" + }, + "jiti@2.7.0": { + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "bin": true + }, "jose@6.2.3": { "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "joycon@3.1.1": { "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" }, + "js-tokens@4.0.0": { + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsesc@3.1.0": { + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": true + }, + "json5@2.2.3": { + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": true + }, "kysely@0.28.17": { "integrity": "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==" }, @@ -2314,9 +3069,27 @@ "lodash.isarguments@3.1.0": { "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, + "lru-cache@5.1.1": { + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": [ + "yallist" + ] + }, + "lucide-react@0.553.0_react@19.2.6": { + "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==", + "dependencies": [ + "react" + ] + }, "luxon@3.7.2": { "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==" }, + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, "minimist@1.2.8": { "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, @@ -2366,6 +3139,12 @@ ], "bin": true }, + "node-releases@2.0.38": { + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==" + }, + "normalize-path@3.0.0": { + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, "on-exit-leak-free@2.1.2": { "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" }, @@ -2384,9 +3163,15 @@ "path-expression-matcher@1.5.0": { "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==" }, + "pathe@2.0.3": { + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, + "picomatch@2.3.2": { + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" + }, "picomatch@4.0.4": { "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, @@ -2456,6 +3241,10 @@ "postgres@3.4.9": { "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==" }, + "prettier@3.8.3": { + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "bin": true + }, "process-warning@5.0.0": { "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" }, @@ -2472,6 +3261,19 @@ "quick-format-unescaped@4.0.4": { "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, + "react-dom@19.2.6_react@19.2.6": { + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "dependencies": [ + "react", + "scheduler" + ] + }, + "react-refresh@0.18.0": { + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==" + }, + "react@19.2.6": { + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==" + }, "readable-stream@4.7.0": { "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dependencies": [ @@ -2482,6 +3284,12 @@ "string_decoder" ] }, + "readdirp@3.6.0": { + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": [ + "picomatch@2.3.2" + ] + }, "real-require@0.2.0": { "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" }, @@ -2494,6 +3302,9 @@ "redis-errors" ] }, + "reselect@5.1.1": { + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "resend@6.12.3": { "integrity": "sha512-FkEi6YPnVL96/LvH8+QP7NaeaBy5brYXwlRqUCqZZeNL0/iyKij18IPmyPXYauT/2ODn1JG04qKz+qlJfzqzTw==", "dependencies": [ @@ -2504,28 +3315,38 @@ "resolve-pkg-maps@1.0.0": { "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" }, - "rolldown@1.0.0-rc.18": { - "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "rollup@4.60.3": { + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", "dependencies": [ - "@oxc-project/types", - "@rolldown/pluginutils" + "@types/estree" ], "optionalDependencies": [ - "@rolldown/binding-android-arm64", - "@rolldown/binding-darwin-arm64", - "@rolldown/binding-darwin-x64", - "@rolldown/binding-freebsd-x64", - "@rolldown/binding-linux-arm-gnueabihf", - "@rolldown/binding-linux-arm64-gnu", - "@rolldown/binding-linux-arm64-musl", - "@rolldown/binding-linux-ppc64-gnu", - "@rolldown/binding-linux-s390x-gnu", - "@rolldown/binding-linux-x64-gnu", - "@rolldown/binding-linux-x64-musl", - "@rolldown/binding-openharmony-arm64", - "@rolldown/binding-wasm32-wasi", - "@rolldown/binding-win32-arm64-msvc", - "@rolldown/binding-win32-x64-msvc" + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loong64-gnu", + "@rollup/rollup-linux-loong64-musl", + "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-ppc64-musl", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-riscv64-musl", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-openbsd-x64", + "@rollup/rollup-openharmony-arm64", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-gnu", + "@rollup/rollup-win32-x64-msvc", + "fsevents" ], "bin": true }, @@ -2538,13 +3359,29 @@ "safe-stable-stringify@2.5.0": { "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" }, + "scheduler@0.27.0": { + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, "secure-json-parse@4.1.0": { "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==" }, + "semver@6.3.1": { + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": true + }, "semver@7.7.4": { "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "bin": true }, + "seroval-plugins@1.5.4_seroval@1.5.4": { + "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", + "dependencies": [ + "seroval" + ] + }, + "seroval@1.5.4": { + "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==" + }, "set-cookie-parser@3.1.0": { "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==" }, @@ -2554,6 +3391,13 @@ "atomic-sleep" ] }, + "sonner@2.0.7_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "dependencies": [ + "react", + "react-dom" + ] + }, "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, @@ -2611,6 +3455,15 @@ "tagged-tag@1.0.0": { "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" }, + "tailwind-merge@3.5.0": { + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==" + }, + "tailwindcss@4.3.0": { + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==" + }, + "tapable@2.3.3": { + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==" + }, "thread-stream@4.0.0": { "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", "dependencies": [ @@ -2621,7 +3474,13 @@ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dependencies": [ "fdir", - "picomatch" + "picomatch@4.0.4" + ] + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" ] }, "tslib@2.8.1": { @@ -2644,13 +3503,41 @@ "tagged-tag" ] }, - "vite@8.0.11": { - "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "typescript@5.9.3": { + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "bin": true + }, + "unplugin@3.0.0": { + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", "dependencies": [ - "lightningcss", - "picomatch", + "@jridgewell/remapping", + "picomatch@4.0.4", + "webpack-virtual-modules" + ] + }, + "update-browserslist-db@1.2.3_browserslist@4.28.2": { + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dependencies": [ + "browserslist", + "escalade", + "picocolors" + ], + "bin": true + }, + "use-sync-external-store@1.6.0_react@19.2.6": { + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dependencies": [ + "react" + ] + }, + "vite@7.3.3": { + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "dependencies": [ + "esbuild@0.27.7", + "fdir", + "picomatch@4.0.4", "postcss", - "rolldown", + "rollup", "tinyglobby" ], "optionalDependencies": [ @@ -2658,18 +3545,38 @@ ], "bin": true }, + "webpack-virtual-modules@0.6.2": { + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" + }, "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "xml-naming@0.1.0": { "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==" }, + "yallist@3.1.1": { + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, "yaml@2.8.4": { "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", "bin": true }, + "zod@3.25.76": { + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" + }, "zod@4.4.3": { "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + }, + "zustand@5.0.13_@types+react@19.2.14_react@19.2.6": { + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react", + "react" + ] } }, "workspace": { @@ -2711,6 +3618,36 @@ "npm:zod@*" ] }, + "apps/frontend": { + "packageJson": { + "dependencies": [ + "npm:@base-ui/react@^1.2.0", + "npm:@better-fetch/fetch@^1.1.18", + "npm:@tailwindcss/vite@^4.1.17", + "npm:@tanstack/react-form@1", + "npm:@tanstack/react-query@^5.90.7", + "npm:@tanstack/react-router-devtools@^1.134.13", + "npm:@tanstack/react-router@^1.134.13", + "npm:@tanstack/router-plugin@^1.134.14", + "npm:@types/react-dom@^19.1.9", + "npm:@types/react@^19.1.16", + "npm:@vitejs/plugin-react@^5.0.4", + "npm:better-auth@^1.3.34", + "npm:class-variance-authority@~0.7.1", + "npm:clsx@^2.1.1", + "npm:lucide-react@0.553", + "npm:react-dom@^19.1.1", + "npm:react@^19.1.1", + "npm:sonner@^2.0.7", + "npm:tailwind-merge@^3.3.1", + "npm:tailwindcss@^4.1.17", + "npm:typescript@~5.9.3", + "npm:vite@^7.1.7", + "npm:zod@^4.2.1", + "npm:zustand@^5.0.8" + ] + } + }, "packages/db": { "dependencies": [ "npm:dotenv@*", diff --git a/docker-compose.yml b/docker-compose.yml index b8ad6a5..023ef17 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,46 +1,22 @@ services: - db: - image: postgres:16-alpine + frontend: + build: + context: . + dockerfile: apps/frontend/Dockerfile ports: - - "5432:5432" - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: orcta_dev - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s - timeout: 5s - retries: 5 - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - volumes: - - redis_data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 5s - retries: 5 + - "80:80" + depends_on: + - backend - # Optional: MinIO for local S3-compatible storage - # minio: - # image: minio/minio - # ports: - # - "9000:9000" - # - "9001:9001" - # environment: - # MINIO_ROOT_USER: minioadmin - # MINIO_ROOT_PASSWORD: minioadmin - # command: server /data --console-address ":9001" - # volumes: - # - minio_data:/data - -volumes: - postgres_data: - redis_data: - # minio_data: + backend: + build: + context: . + dockerfile: apps/backend/Dockerfile + expose: + - "9999" + environment: + PORT: 9999 + DATABASE_URL: ${DATABASE_URL} + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost} + REDIS_URL: ${REDIS_URL:-} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts new file mode 100644 index 0000000..8fe9524 --- /dev/null +++ b/src/routeTree.gen.ts @@ -0,0 +1,113 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './../apps/frontend/src/routes/__root' +import { Route as RegisterRouteImport } from './../apps/frontend/src/routes/register' +import { Route as LoginRouteImport } from './../apps/frontend/src/routes/login' +import { Route as DashboardRouteImport } from './../apps/frontend/src/routes/dashboard' +import { Route as IndexRouteImport } from './../apps/frontend/src/routes/index' + +const RegisterRoute = RegisterRouteImport.update({ + id: '/register', + path: '/register', + getParentRoute: () => rootRouteImport, +} as any) +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => rootRouteImport, +} as any) +const DashboardRoute = DashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/dashboard': typeof DashboardRoute + '/login': typeof LoginRoute + '/register': typeof RegisterRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/dashboard': typeof DashboardRoute + '/login': typeof LoginRoute + '/register': typeof RegisterRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/dashboard': typeof DashboardRoute + '/login': typeof LoginRoute + '/register': typeof RegisterRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/dashboard' | '/login' | '/register' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/dashboard' | '/login' | '/register' + id: '__root__' | '/' | '/dashboard' | '/login' | '/register' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + DashboardRoute: typeof DashboardRoute + LoginRoute: typeof LoginRoute + RegisterRoute: typeof RegisterRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/register': { + id: '/register' + path: '/register' + fullPath: '/register' + preLoaderRoute: typeof RegisterRouteImport + parentRoute: typeof rootRouteImport + } + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport + parentRoute: typeof rootRouteImport + } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + DashboardRoute: DashboardRoute, + LoginRoute: LoginRoute, + RegisterRoute: RegisterRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() From eed9d5a15f58d7bf6b3b152674ce428d6f040832 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 08:52:33 +0000 Subject: [PATCH 05/27] docs: update workspace, deployment, and readme to reflect current architecture - Rewrite DENO_WORKSPACE_SCOPE.md from migration plan to current-state doc - Update README.md: frontend on :3000, Docker Compose layout, Deno-only prereq - Add Docker Compose full-stack option to DEPLOYMENT.md - Fix broken CLAUDE.md link in AGENTS.md to point to existing docs --- AGENTS.md | 5 +- README.md | 31 +-- docs/DENO_WORKSPACE_SCOPE.md | 359 ++++++++++------------------------- docs/DEPLOYMENT.md | 43 ++++- 4 files changed, 154 insertions(+), 284 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 58ada14..1d3874f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,8 +3,9 @@ Instructions for AI agents working in this codebase. Read this before touching anything. -Also read: [`CLAUDE.md`](CLAUDE.md) for commands and architecture, -[`docs/WRITING.md`](docs/WRITING.md) for documentation voice, +Also read: [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) for deployment, +[`docs/DENO_WORKSPACE_SCOPE.md`](docs/DENO_WORKSPACE_SCOPE.md) for workspace +architecture, [`docs/WRITING.md`](docs/WRITING.md) for documentation voice, [`docs/PHILOSOPHY.md`](docs/PHILOSOPHY.md) for the beliefs behind every decision. diff --git a/README.md b/README.md index 63540cf..ea2fb70 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A production-ready TypeScript monorepo. Ship fast, sleep well. ``` Backend runs on [localhost:9999](http://localhost:9999/docs). Frontend on -[localhost:5173](http://localhost:5173). +[localhost:3000](http://localhost:3000). ## What's Inside @@ -20,7 +20,7 @@ jobs, rate limiting This is a GitHub template. Click **Use this template** → **Create a new repository** on GitHub, then clone your new repo. -You need Deno 2+ (for the backend) and pnpm (for frontend dependencies). +You need Deno 2+ and pnpm. ```bash git clone https://github.com// my-app @@ -58,10 +58,10 @@ Run everything: ```bash deno task dev # Backend on :9999 -deno task dev:frontend # Frontend on :5173 +deno task dev:frontend # Frontend on :3000 ``` -Open [localhost:5173](http://localhost:5173). You're live. +Open [localhost:3000](http://localhost:3000). You're live. ## Daily Commands @@ -187,29 +187,30 @@ app.post("/api/auth/login", authRateLimit, loginHandler); ## Deploy -**Backend** → Docker on any VPS, or Railway/Render **Frontend** → Vercel (zero -config) **Database** → Supabase, Neon, or Railway +**Full stack** → `docker compose up -d` (Caddy + backend on one VPS) +**Frontend only** → Vercel (zero config) **Database** → Supabase, Neon, or +Railway See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for the full guide. ## Project Layout ```bash -apps/backend/src/ - modules/ ← Your features go here - lib/ ← Reusable utilities - jobs/ ← Background workers - middlewares/ ← Auth, etc. +apps/backend/ + Dockerfile ← Standalone backend image (Deno compile → Alpine) + src/ ← Modules, lib, jobs, middlewares -apps/frontend/src/ - routes/ ← Pages (file-based routing) - lib/ ← API client, helpers - components/ ← UI components +apps/frontend/ + Dockerfile ← Deno builder + Caddy runner image + Caddyfile ← SPA + /api/* reverse proxy + src/ ← Routes, lib, components packages/ db/ ← Database schemas shared/ ← Types shared everywhere email-templates/ ← Email builders + +docker-compose.yml ← Full stack (Caddy :80 + backend :9999 + Postgres + Redis) ``` ## Learn More diff --git a/docs/DENO_WORKSPACE_SCOPE.md b/docs/DENO_WORKSPACE_SCOPE.md index 42f8a9b..f140927 100644 --- a/docs/DENO_WORKSPACE_SCOPE.md +++ b/docs/DENO_WORKSPACE_SCOPE.md @@ -1,50 +1,65 @@ -# Deno Workspace Migration Scope +# Deno Workspace Architecture -Current architecture: flat root `deno.json` with a global import map that -resolves `@repo/*`, `@/*`, and all `npm:` dependencies. This works, but -per-package config is crowded into one file. +The monorepo uses Deno's native workspace system. Each package is +self-describing with its own `deno.json`, and bare specifiers like `@repo/shared` +resolve through workspace member names — no global import map entries needed. -Goal: use Deno's native workspace system so each package is self-describing. +--- + +## Why + +A flat root `deno.json` with all imports works, but doesn't scale. Every package +change touches the same file, per-package concerns are mingled, and the LSP +can't scope imports to the correct context. Workspaces give us: + +- **Self-describing packages** — each `deno.json` declares its own deps +- **Bare specifier resolution** — `@repo/shared` resolves from workspace member + `name`, not an import map entry +- **LSP accuracy** — per-package config means the editor resolves imports per + context +- **Progressive abstraction** — packages can escalate from no config to full + config as they grow --- ## Current State ``` -deno.json (root) ← 40+ import map entries, 10 tasks, lint/fmt/test config -├── apps/ -│ ├── backend/ ← 20 @/ imports + 10 @repo/ imports from root deno.json -│ └── frontend/ ← pnpm only, no @repo imports in source -├── packages/ -│ ├── shared/ ← package.json only, no deno.json; 3 source files, 2 test files -│ ├── db/ ← package.json only, no deno.json; schema dir, no tests -│ └── email-templates/ ← package.json only, no deno.json; 1 source, 1 test file -├── package.json ← pnpm root -└── pnpm-workspace.yaml ← MISSING (but frontend package.json has workspace:* deps) +deno.json (root) + workspace: [apps/backend, apps/frontend, packages/shared, packages/db, packages/email-templates] + tasks: dev, dev:frontend, dev:backend, start, worker, test, lint, fmt, check, db:* + imports: @std/expect, @std/testing/bdd (shared dev/test deps only) + nodeModulesDir: auto + sloppyImports: true + ├── apps/ + │ ├── backend/ deno.json — 40+ import map entries (npm: + @/* aliases) + │ └── frontend/ deno.json — @/ import map, vite tasks (consumer only, no name) + └── packages/ + ├── shared/ deno.json — name @repo/shared, pure TS, zero npm deps + ├── db/ deno.json — name @repo/db, exports ./ and ./schema + └── email-templates/ deno.json — name @repo/email-templates, zero npm deps ``` ### Dependency graph ``` apps/backend - └── @repo/shared (20 import sites across handlers, repos, use-cases) + └── @repo/shared (20+ import sites) └── @repo/db (7 import sites, mostly @repo/db/schema) - └── @repo/email-templates (not yet imported — future) + └── @repo/email-templates packages/db - └── drizzle-orm, postgres (npm: deps, currently in root import map) + └── drizzle-orm, postgres, drizzle-zod (npm: in its own imports) packages/shared — zero external deps, pure TypeScript packages/email-templates — zero external deps, pure TypeScript -apps/frontend - └── @repo/shared (package.json dep, NOT imported in source) - └── @repo/db (package.json dep, NOT imported in source) +apps/frontend — consumer only (Vite/browser), not imported by anything else ``` --- -## Target State +## Config Files ### Root `deno.json` @@ -52,16 +67,24 @@ apps/frontend { "workspace": [ "apps/backend", + "apps/frontend", "packages/shared", "packages/db", "packages/email-templates" ], "tasks": { "dev": "deno task --cwd=apps/backend dev", - "dev:frontend": "deno run -A npm:vite dev --config apps/frontend/vite.config.ts", - "check": "deno check", + "dev:backend": "deno task --cwd=apps/backend dev", + "dev:frontend": "deno task --cwd=apps/frontend dev", + "start": "deno task --cwd=apps/backend start", + "worker": "deno task --cwd=apps/backend worker", + "test": "deno task --cwd=apps/backend test", "lint": "deno lint", - "fmt": "deno fmt" + "fmt": "deno fmt", + "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", + "db:migrate": "deno task --cwd=apps/backend db:migrate", + "db:studio": "deno task --cwd=apps/backend db:studio", + "db:generate": "deno task --cwd=apps/backend db:generate" }, "imports": { "@std/expect": "jsr:@std/expect@^1.0.19", @@ -75,7 +98,21 @@ apps/frontend Root owns: workspace membership, shared dev/test deps, top-level convenience tasks. No npm runtime deps, no `@repo/*` entries, no `@/*` aliases. -### `packages/shared/deno.json` — NEW +### `apps/backend/deno.json` + +Owns: all npm runtime deps, all `@/*` path aliases, tasks for dev/test/DB, lint +and test config. + +No `@repo/*` entries — those resolve through workspace bare specifiers. No +`nodeModulesDir` — that's a root-level concern per Deno docs. + +### `apps/frontend/deno.json` + +Consumer member only. Has `@/*` import map for Vite/React imports. No `name` or +`exports` — nothing imports the frontend. Uses `deno run -A npm:vite` for dev +and build tasks. + +### `packages/shared/deno.json` ```json { @@ -87,10 +124,9 @@ tasks. No npm runtime deps, no `@repo/*` entries, no `@/*` aliases. ``` Zero external deps — pure TypeScript. The `name` field lets Deno resolve -`@repo/shared` as a bare specifier via workspace resolution, replacing the root -import map entry. +`@repo/shared` as a bare specifier via workspace resolution. -### `packages/db/deno.json` — NEW +### `packages/db/deno.json` ```json { @@ -104,7 +140,10 @@ import map entry. "drizzle-orm": "npm:drizzle-orm", "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", - "postgres": "npm:postgres" + "drizzle-zod": "npm:drizzle-zod", + "postgres": "npm:postgres", + "dotenv": "npm:dotenv", + "drizzle-kit": "npm:drizzle-kit" }, "exclude": ["node_modules"] } @@ -113,7 +152,7 @@ import map entry. The `exports` with sub-path `./schema` preserves `@repo/db/schema` imports without needing a root import map entry. -### `packages/email-templates/deno.json` — NEW +### `packages/email-templates/deno.json` ```json { @@ -124,252 +163,54 @@ without needing a root import map entry. } ``` -### `apps/backend/deno.json` — MOVE + EXPAND - -```json -{ - "tasks": { - "dev": "deno run --watch --env-file=.env -A src/index.ts", - "start": "deno run --env-file=.env -A src/index.ts", - "test": "deno test --env-file=.env -A", - "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", - "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", - "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" - }, - "imports": { - "@/": "./src/", - "@/app": "./src/app.ts", - "@/db": "./src/db/index.ts", - "@/env": "./src/env.ts", - "@/lib/auth": "./src/lib/auth.ts", - "@/lib/create-app": "./src/lib/create-app.ts", - "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", - "@/lib/types": "./src/lib/types.ts", - "@/lib/redis": "./src/lib/redis.ts", - "@/lib/error": "./src/lib/error.ts", - "@/lib/infra": "./src/lib/infra.ts", - "@/lib/cache": "./src/lib/cache.ts", - "@/lib/storage": "./src/lib/storage.ts", - "@/lib/rate-limit": "./src/lib/rate-limit.ts", - "@/lib/ws": "./src/lib/ws.ts", - "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", - "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", - "@/middlewares/auth": "./src/middlewares/auth.ts", - "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", - "@/modules/health": "./src/modules/health/index.ts", - "@/modules/health/handlers": "./src/modules/health/handlers.ts", - "@/modules/health/routes": "./src/modules/health/routes.ts", - "@/modules/users": "./src/modules/users/index.ts", - "@/modules/users/handlers": "./src/modules/users/handlers.ts", - "@/modules/users/routes": "./src/modules/users/routes.ts", - "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", - "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", - "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", - "@/jobs/index": "./src/jobs/index.ts", - "@/jobs/worker": "./src/jobs/worker.ts", - "hono": "npm:hono", - "hono/cors": "npm:hono/cors", - "hono/dev": "npm:hono/dev", - "hono/ws": "npm:hono/ws", - "@hono/zod-openapi": "npm:@hono/zod-openapi", - "@hono/swagger-ui": "npm:@hono/swagger-ui", - "@hono/zod-validator": "npm:@hono/zod-validator", - "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", - "better-auth": "npm:better-auth", - "better-auth/adapters": "npm:better-auth/adapters", - "better-auth/plugins": "npm:better-auth/plugins", - "ioredis": "npm:ioredis", - "bullmq": "npm:bullmq", - "pino": "npm:pino", - "pino-pretty": "npm:pino-pretty", - "hono-pino": "npm:hono-pino", - "stoker": "npm:stoker", - "stoker/middlewares": "npm:stoker/middlewares", - "stoker/openapi": "npm:stoker/openapi", - "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", - "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", - "zod": "npm:zod", - "drizzle-zod": "npm:drizzle-zod", - "resend": "npm:resend", - "dotenv": "npm:dotenv", - "dotenv-expand": "npm:dotenv-expand", - "@axiomhq/pino": "npm:@axiomhq/pino" - }, - "lint": { - "rules": { - "exclude": ["no-explicit-any", "no-non-null-assertion"] - } - }, - "test": { - "include": ["src/**/*.test.ts"] - } -} -``` - -No `@repo/*` entries — those resolve through workspace bare specifiers. No -`drizzle-kit` import — that's a CLI tool, used via `npx` or -`deno run -A npm:drizzle-kit`. - ---- - -## Steps - -### Step 1 — Add `deno.json` to each workspace member - -Create 4 files: - -- `packages/shared/deno.json` -- `packages/db/deno.json` -- `packages/email-templates/deno.json` -- `apps/backend/deno.json` (migrate from root) - -**Risk**: low. Adding config files is additive — nothing breaks yet. - -### Step 2 — Update root `deno.json` - -Add `"workspace"` field, strip `@repo/*` and `/*` entries from `imports`, move -lint/fmt/test config to member files, strip backend tasks. - -**Risk**: medium. If workspace resolution doesn't kick in correctly, -`deno check` and `deno test` will fail with module-not-found errors. - -### Step 3 — Verify `deno check` across all members - -```bash -deno check -``` - -This should type-check all workspace members. If a member has type errors (e.g. -`packages/db` importing `drizzle-orm` but not declaring it in its own -`imports`), fix per-member config. - -### Step 4 — Verify `deno test` across all members - -```bash -deno test -A -``` - -Tests in `apps/backend`, `packages/shared`, `packages/email-templates` should -all run with per-member test configs. - -### Step 5 — Clean up orphaned files - -- Remove `packages/shared/vitest.config.ts` (Deno doesn't use it) -- Remove `packages/email-templates/vitest.config.ts` -- Remove `apps/backend/vitest.config.ts` (if it exists) -- Remove `apps/backend/package.json` — it only says `"type": "module"` which - Deno doesn't need (Deno treats `.ts` as ESM by default, `.js` inherits from - nearest `package.json` — but there's no `package.json` with `"type": "module"` - for Deno paths anymore) - - **BUT**: keep it if `npm:@better-auth/cli generate` or `drizzle-kit` needs - it to detect ESM -- Remove `apps/backend/tsconfig.json`, `packages/*/tsconfig.json` (orphaned from - old TypeScript setup) - -### Step 6 — Recreate `pnpm-workspace.yaml` - -If it was deleted, recreate it. Without it, `pnpm install` can't resolve -`workspace:*` protocol in the frontend's `package.json`. - -```yaml -packages: - - "packages/*" -``` - -The frontend's `package.json` lists `@repo/shared` and `@repo/db` as workspace -dependencies but never imports them in source. Optionally remove those unused -deps from `apps/frontend/package.json` — simplifies the pnpm workspace and -eliminates the dependency entirely. - -### Step 7 — Update scripts - -- `scripts/new-module.sh`: the scaffolded `handlers.test.ts` already uses - `@std/testing/bdd`, so no change needed for tests. Still references - `biome.json` for formatting — keep since Biome is still used for frontend. -- `scripts/setup.sh`: already updated for Deno. - -### Step 8 — Update root `package.json` - -Remove `"engines": { "deno": ">=2.0.0" }` — Deno doesn't read `engines` from -`package.json`. Keep the `"packageManager"` field for pnpm. +Zero external deps. --- -## Edge cases & risks - -### 1. Module resolution order +## Module Resolution Order -Deno resolves bare specifiers in this order (workspace members → import map → -npm): +Deno resolves bare specifiers in this order: 1. Check if specifier matches a workspace member's `name` 2. Check the local `deno.json` `imports` 3. Check the root `deno.json` `imports` -So `apps/backend` can still use `@repo/shared` even if it's not in anyone's -`imports` — it resolves through step 1 (workspace member name). **This is the -core mechanism** that lets us remove `@repo/*` from the root import map. - -### 2. Duplicate npm import declarations - -Both `packages/db` and `apps/backend` import `drizzle-orm` in their own -`deno.json`. Deno should deduplicate these to a single npm install. Verify with -`deno info` after the migration. - -### 3. Backend `package.json` removal - -The backend `package.json` is minimal -(`{"name": "backend", "type": "module", "private": true}`). It exists solely for -the pnpm workspace (so `pnpm -r` finds it) and for `type: "module"` (so -Node-based tools like drizzle-kit detect ESM). - -**If we keep it**: no change needed. It's inert for Deno. **If we remove it**: -`npm:@better-auth/cli generate` might fail if it probes `type` from -`package.json`. The safe call is to keep it. +So `apps/backend` imports `@repo/shared` and it resolves through step 1 +(workspace member name). No import map entry needed. This is the core mechanism +that lets us keep the root `imports` clean. -### 4. `pnpm-workspace.yaml` status - -Currently missing. The frontend `package.json` lists -`"@repo/shared": "workspace:*"` and `"@repo/db": "workspace:*"` but never -imports them in source code. Two options: +--- -- **Keep deps + recreate yaml**: simplest, no code changes -- **Remove unused deps**: cleaner but requires verifying nothing at build time - depends on them (better-auth might resolve types through them) +## Edge Cases -### 5. `deno check` on member packages +### 1. All members must exist -`packages/shared` and `packages/email-templates` are pure TypeScript with zero -deps. `deno check` should pass instantly. +If root has a `"workspace"` array, ANY `deno.json` under the root directory MUST +be a workspace member. The `"exclude"` field only affects `deno fmt`/`deno +lint`/`deno test`, NOT workspace validation. This means the Dockerfile for the +backend must copy stub `deno.json` files for all members before `deno cache` +runs. -`packages/db` depends on `drizzle-orm` and `postgres`. If these npm packages -don't have Deno-compatible type declarations, `deno check` might fail. The -current root import map already has these entries, so they're already working — -moving them to `packages/db/deno.json` shouldn't change resolution. +### 2. `nodeModulesDir` placement -`apps/backend` has the most complex dep graph. Moving its imports out of root -scope and into its own `deno.json` should be transparent since workspace member -imports take priority. +Per Deno docs, `nodeModulesDir` is only valid at the workspace root level (OK in +root, rejected in member configs). It lives in root `deno.json` only. -### 6. LSP behavior +### 3. Duplicate npm import declarations -With per-package `deno.json` files, VS Code's Deno LSP should correctly resolve -imports within each workspace member using that member's config. This is the -main UX improvement over the flat approach. +Both `packages/db` and `apps/backend` import `drizzle-orm` in their own +`deno.json`. Deno deduplicates these to a single npm install. ---- +### 4. Frontend is a consumer-only member -## Summary +The frontend `deno.json` has no `name` or `exports`. It needs to be a workspace +member (to pass validation) but isn't imported by anything. The Docker build +copies its `deno.json` as a stub. -| Item | Effort | Risk | -| --------------------------------- | -------------- | -------------- | -| Create 4 `deno.json` files | Small | Low | -| Restructure root `deno.json` | Medium | Medium | -| Remove orphaned config files | Small | Low | -| Recreate `pnpm-workspace.yaml` | Trivial | Low | -| Verify `deno check` + `deno test` | Medium | Medium | -| **Total** | **~2-3 hours** | **Low-Medium** | +### 5. pnpm workspace -The migration is straightforward: add `deno.json` to each package, strip them -from the root, verify resolution. The risk is in edge cases (npm type -declarations, pnpm workspace sync, LSP cache invalidation). +The frontend's `package.json` still uses pnpm for dependency management (Vite, +React, etc.). The Deno workspace and pnpm workspace coexist — Deno handles the +backend and packages, pnpm handles the frontend's npm deps. The `package.json` +has been cleaned up: no `@repo/*` workspace deps (those are resolved via Deno +workspace now). diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index c59c0d5..4a66d94 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -4,12 +4,13 @@ Get your app live in 15 minutes. ## TL;DR -| Part | Where | Cost | -| -------- | --------------------------- | ------------------- | -| Backend | Railway, Render, or any VPS | $5-20/mo | -| Frontend | Vercel | Free | -| Database | Supabase, Neon, or Railway | Free tier available | -| Redis | Upstash or Railway | Free tier available | +| Part | Where | Cost | +| ---------- | --------------------------- | ------------------- | +| Full stack | Docker Compose on any VPS | $5-20/mo | +| Backend | Railway, Render, or any VPS | $5-20/mo | +| Frontend | Vercel, or Docker on VPS | Free | +| Database | Supabase, Neon, or Railway | Free tier available | +| Redis | Upstash or Railway | Free tier available | ## 1. Database @@ -50,7 +51,33 @@ FRONTEND_URL=https:// 1. Railway auto-deploys on push -### Option B: Any VPS (more control) +### Option B: Docker Compose (whole stack on one VPS) + +The simplest production setup: one subdomain, Caddy reverse proxy with +path-based routing. Caddy serves the SPA frontend at `/` and proxies `/api/*` to +the backend. + +```bash +# Install dependencies +curl -fsSL https://get.docker.com | sh + +# Clone +git clone app && cd app + +# Set secrets +echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)" >> .env +echo "DATABASE_URL=postgres://..." >> .env +echo "BETTER_AUTH_URL=https://yourdomain.com" >> .env +echo "FRONTEND_URL=https://yourdomain.com" >> .env + +# Start everything +docker compose up -d +``` + +See `docker-compose.yml` for the full service definition. The frontend runs on +port 80 (Caddy) and the backend on port 9999 (internal). + +### Option C: Any VPS (separate services) SSH into your server: @@ -58,7 +85,7 @@ SSH into your server: # Install dependencies curl -fsSL https://get.docker.com | sh -# Clone and build +# Clone and build backend git clone app && cd app docker build -t api -f apps/backend/Dockerfile . From 3d7e46b4e64fbec04a5e831182dd75f9e5676719 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 09:00:30 +0000 Subject: [PATCH 06/27] chore(scripts): fix new-module.sh structure, update pnpm workspace and cleanup root package.json - Flatten usecases file in new-module.sh (matches existing users module pattern) - Add apps/frontend to pnpm-workspace.yaml so pnpm installs frontend deps - Remove stale pnpm scripts and engines from root package.json - Update setup.sh copy to reference Deno workspace resolution --- package.json | 36 +++++++++++++++--------------------- pnpm-workspace.yaml | 1 + scripts/new-module.sh | 7 +++---- scripts/setup.sh | 2 +- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index a3a0dad..d864c13 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,17 @@ { - "name": "orcta-stack", - "version": "1.0.0", - "license": "MIT", - "private": true, - "type": "module", - "scripts": { - "setup": "./scripts/setup.sh", - "dev:frontend": "pnpm --filter frontend dev", - "build": "pnpm -r --filter ./apps/frontend build", - "lint": "deno lint", - "fmt": "deno fmt", - "clean": "rm -rf apps/*/dist packages/*/dist node_modules/.cache" - }, - "devDependencies": { - "@biomejs/biome": "2.3.7" - }, - "dependencies": {}, - "engines": { - "deno": ">=2.0.0" - }, - "packageManager": "pnpm@9.15.0" + "name": "orcta-stack", + "version": "1.0.0", + "license": "MIT", + "private": true, + "type": "module", + "scripts": { + "setup": "./scripts/setup.sh", + "lint": "deno lint", + "fmt": "deno fmt", + "clean": "rm -rf apps/frontend/dist apps/frontend/node_modules node_modules/.cache" + }, + "devDependencies": { + "@biomejs/biome": "2.3.7" + }, + "packageManager": "pnpm@9.15.0" } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dee51e9..808bfd2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - "packages/*" + - "apps/frontend" diff --git a/scripts/new-module.sh b/scripts/new-module.sh index 0cf7317..240dd58 100755 --- a/scripts/new-module.sh +++ b/scripts/new-module.sh @@ -43,7 +43,6 @@ PASCAL=$(echo "$MODULE" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(su echo -e "\n${BOLD}Scaffolding module: ${MODULE}${RESET} (tags: ${PASCAL})\n" # ── Directory structure ───────────────────────────────────────────────────────── -mkdir -p "${MODULE_DIR}/usecases" mkdir -p "${MODULE_DIR}/__tests__" success "Created directory structure" @@ -117,8 +116,8 @@ export async function findAll(): Promise< EOF success "${MODULE}.repository.ts" -# ── usecases/${MODULE}.usecases.ts ───────────────────────────────────────────── -cat > "${MODULE_DIR}/usecases/${MODULE}.usecases.ts" << EOF +# ── ${MODULE}.usecases.ts ────────────────────────────────────────────────────── +cat > "${MODULE_DIR}/${MODULE}.usecases.ts" << EOF // Use-cases: functional core. // // Pure functions that receive already-loaded domain values and apply business rules. @@ -227,4 +226,4 @@ echo -e "${BOLD}Then:${RESET}" echo " • Add your DB schema and table to packages/db/src/schema/" echo " • Run deno task db:generate && deno task db:migrate" echo " • Flesh out ${MODULE}.repository.ts with real Drizzle queries" -echo " • Add use-cases to usecases/${MODULE}.usecases.ts as logic grows" +echo " • Add business logic to ${MODULE}.usecases.ts as needed" diff --git a/scripts/setup.sh b/scripts/setup.sh index c0dccee..72daf6b 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -107,7 +107,7 @@ fi # ── Build packages ───────────────────────────────────────────────────────────── step "Verifying packages..." -info "Shared packages are imported via Deno's import maps — no build step needed." +info "Shared packages are resolved via Deno workspace — no build step needed." # ── Done ──────────────────────────────────────────────────────────────────────── echo "" From fe1f2e09917b53ac52273b8b2467e2e28c6fd674 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 09:28:11 +0000 Subject: [PATCH 07/27] ci: update GitHub Actions for workspace + frontend Docker deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix FRONTEND_URL port in CI (5173 → 3000) - Add frontend image build and push to deploy workflow - Add frontend service to docker-compose.prod.yml (Caddy on :80) - Remove accidentally tracked generated routeTree.gen.ts - Add routeTree.gen.ts to .gitignore --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy.yml | 80 +++++++++++++++---------- .gitignore | 5 +- docker-compose.prod.yml | 14 ++++- src/routeTree.gen.ts | 113 ----------------------------------- 5 files changed, 63 insertions(+), 151 deletions(-) delete mode 100644 src/routeTree.gen.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 382666e..4f032a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,4 +65,4 @@ jobs: BETTER_AUTH_SECRET: test-secret-for-ci-at-least-32-chars BETTER_AUTH_URL: http://localhost:9999 SERVER_URL: http://localhost:9999 - FRONTEND_URL: http://localhost:5173 + FRONTEND_URL: http://localhost:3000 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fd60a38..eaf422e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,19 +6,17 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: orctatech-engineering-team/orcta-backend + BACKEND_IMAGE: orctatech-engineering-team/orcta-backend + FRONTEND_IMAGE: orctatech-engineering-team/orcta-frontend jobs: - build-and-push: - name: Build & push Docker image + build-backend: + name: Build & push backend image runs-on: ubuntu-latest permissions: contents: read packages: write - outputs: - image_tag: ${{ steps.meta.outputs.version }} - steps: - uses: actions/checkout@v4 @@ -33,7 +31,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }} tags: | type=sha,prefix=,format=short type=raw,value=latest @@ -49,10 +47,45 @@ jobs: build-args: | SERVICE_VERSION=${{ github.sha }} + build-frontend: + name: Build & push frontend image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }} + tags: | + type=sha,prefix=,format=short + type=raw,value=latest + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: apps/frontend/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + deploy: name: Deploy to VPS runs-on: ubuntu-latest - needs: build-and-push + needs: [build-backend, build-frontend] environment: production steps: @@ -65,25 +98,15 @@ jobs: username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} script: | - # Fail fast: - # -e → exit on error - # -u → error on undefined variables - # -o pipefail → fail if any command in a pipeline fails set -euo pipefail - # Static deployment configuration REPO_NAME="orcta-stack" APP_DIR="/srv/apps/$REPO_NAME" - - # Branch that triggered the workflow (master in your case) BRANCH="${{ github.ref_name }}" - # Ensure application directory exists (idempotent) mkdir -p "$APP_DIR" cd "$APP_DIR" - # Sync repository state - # Clone only once; subsequent deploys pull latest changes if [ ! -d ".git" ]; then echo "Cloning repository..." git clone git@github.com:Orctatech-Engineering-Team/$REPO_NAME.git . @@ -94,35 +117,26 @@ jobs: git pull origin "$BRANCH" fi - # Hard stop if production environment file is missing - # Prevents accidental boot with empty credentials/secrets if [ ! -f ".env.production" ]; then echo "Error: .env.production file not found." exit 1 fi - # Authenticate with GitHub Container Registry (GHCR) - # Token is piped via stdin to avoid shell history leakage echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - # Pull latest backend image built by CI + # Pull latest images built by CI docker pull ghcr.io/orctatech-engineering-team/orcta-backend:latest + docker pull ghcr.io/orctatech-engineering-team/orcta-frontend:latest - # Start infrastructure dependencies first - # --env-file ensures Compose-time variable interpolation works + # Start infrastructure dependencies IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml up -d db redis - # Show container states (useful for debugging in CI logs) docker compose --env-file .env.production -f docker-compose.prod.yml ps - - # Run database migrations using the NEW backend image - # --rm prevents orphaned containers + # Run database migrations IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml run --rm backend deno run --allow-env --allow-net --allow-read --allow-sys apps/backend/src/db/migrate.ts - # Update backend container - # Only backend is recreated → DB/Redis remain untouched - IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml up -d backend + # Recreate backend and frontend + IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml up -d backend frontend - # Remove dangling/unused images to control disk usage docker image prune -f diff --git a/.gitignore b/.gitignore index 9199243..87e8cad 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,7 @@ coverage/ # Misc .cache/ -.tanstack/ \ No newline at end of file +.tanstack/ + +# Generated +**/routeTree.gen.ts \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 084fbf5..0e6ca6f 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,10 +1,18 @@ # Production docker-compose — used by the CD pipeline on the VPS. # Dev services (MinIO, exposed DB ports) are intentionally excluded. # -# The backend image is built by CI and pushed to GHCR. +# Images are built by CI and pushed to GHCR. # Required: .env.production on the VPS (never committed to git). services: + frontend: + image: ghcr.io/orctatech-engineering-team/orcta-frontend:${IMAGE_TAG:-latest} + restart: unless-stopped + ports: + - "80:80" + depends_on: + - backend + db: image: postgres:16-alpine restart: unless-stopped @@ -37,8 +45,8 @@ services: image: ghcr.io/orctatech-engineering-team/orcta-backend:${IMAGE_TAG:-latest} restart: unless-stopped env_file: .env.production - ports: - - "9292:9292" + expose: + - "9999" depends_on: db: condition: service_healthy diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts deleted file mode 100644 index 8fe9524..0000000 --- a/src/routeTree.gen.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint-disable */ - -// @ts-nocheck - -// noinspection JSUnusedGlobalSymbols - -// This file was automatically generated by TanStack Router. -// You should NOT make any changes in this file as it will be overwritten. -// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. - -import { Route as rootRouteImport } from './../apps/frontend/src/routes/__root' -import { Route as RegisterRouteImport } from './../apps/frontend/src/routes/register' -import { Route as LoginRouteImport } from './../apps/frontend/src/routes/login' -import { Route as DashboardRouteImport } from './../apps/frontend/src/routes/dashboard' -import { Route as IndexRouteImport } from './../apps/frontend/src/routes/index' - -const RegisterRoute = RegisterRouteImport.update({ - id: '/register', - path: '/register', - getParentRoute: () => rootRouteImport, -} as any) -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => rootRouteImport, -} as any) -const DashboardRoute = DashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) - -export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/dashboard': typeof DashboardRoute - '/login': typeof LoginRoute - '/register': typeof RegisterRoute -} -export interface FileRoutesByTo { - '/': typeof IndexRoute - '/dashboard': typeof DashboardRoute - '/login': typeof LoginRoute - '/register': typeof RegisterRoute -} -export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/dashboard': typeof DashboardRoute - '/login': typeof LoginRoute - '/register': typeof RegisterRoute -} -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/dashboard' | '/login' | '/register' - fileRoutesByTo: FileRoutesByTo - to: '/' | '/dashboard' | '/login' | '/register' - id: '__root__' | '/' | '/dashboard' | '/login' | '/register' - fileRoutesById: FileRoutesById -} -export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - DashboardRoute: typeof DashboardRoute - LoginRoute: typeof LoginRoute - RegisterRoute: typeof RegisterRoute -} - -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/register': { - id: '/register' - path: '/register' - fullPath: '/register' - preLoaderRoute: typeof RegisterRouteImport - parentRoute: typeof rootRouteImport - } - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRouteImport - } - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardRouteImport - parentRoute: typeof rootRouteImport - } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - } -} - -const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - DashboardRoute: DashboardRoute, - LoginRoute: LoginRoute, - RegisterRoute: RegisterRoute, -} -export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() From 12e3a17ba6a683fda74fc08d51fbb6e0d159c3f4 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Sun, 10 May 2026 09:33:51 +0000 Subject: [PATCH 08/27] docs: add GitHub pull request template Aligns with Orcta PR guidelines (Summary, Motivation, Changes Made, Testing, Screenshots, Pre-Submission Checklist, Technical Decisions, Related Work, Reviewer Notes) and the AGENTS.md PR format. --- .github/PULL_REQUEST_TEMPLATE.md | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7e92bce --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,49 @@ +## Summary + +Brief description of what this PR does in 2-3 sentences. Focus on the what, not +the how. + +## Motivation + +Why are we making this change? Link to the issue or ticket, or explain the user +need if no issue exists. + +## Changes Made + +- List the key changes in this PR. Helps reviewers understand scope at a glance. +- One bullet per logical change. + +## Testing Instructions + +Step-by-step guide for reviewers to verify the changes work as expected. + +```bash +# Include specific commands +``` + +## Screenshots or Evidence + +For UI changes: before and after screenshots. For backend: test output or API +responses. + +## Pre-Submission Checklist + +- [ ] All tests pass — `deno test -A` +- [ ] No type errors — `deno check` +- [ ] No lint warnings — `deno lint` +- [ ] Tests added or updated for new functionality +- [ ] Branch is rebased on latest `dev` +- [ ] Commit history tells a legible story + +## Technical Decisions + +Document trade-offs, alternative approaches considered, or technical debt +introduced. + +## Related Work + +Links to dependent PRs, related issues, or follow-up work. + +## Reviewer Notes + +Specific areas where you want focused feedback or context that helps reviewers. From c0acfd0b51b3386108637440f84fd19494ede8eb Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 11 May 2026 12:43:49 +0000 Subject: [PATCH 09/27] docs: strip example text from PR template --- .github/PULL_REQUEST_TEMPLATE.md | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7e92bce..be2bc8e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,31 +1,13 @@ ## Summary -Brief description of what this PR does in 2-3 sentences. Focus on the what, not -the how. - ## Motivation -Why are we making this change? Link to the issue or ticket, or explain the user -need if no issue exists. - ## Changes Made -- List the key changes in this PR. Helps reviewers understand scope at a glance. -- One bullet per logical change. - ## Testing Instructions -Step-by-step guide for reviewers to verify the changes work as expected. - -```bash -# Include specific commands -``` - ## Screenshots or Evidence -For UI changes: before and after screenshots. For backend: test output or API -responses. - ## Pre-Submission Checklist - [ ] All tests pass — `deno test -A` @@ -37,13 +19,6 @@ responses. ## Technical Decisions -Document trade-offs, alternative approaches considered, or technical debt -introduced. - ## Related Work -Links to dependent PRs, related issues, or follow-up work. - ## Reviewer Notes - -Specific areas where you want focused feedback or context that helps reviewers. From 8e39d02486e26725c5ae91e3f286e83db4702f5f Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 09:33:57 +0000 Subject: [PATCH 10/27] chore(deps): bump @std/testing to 1.0.19 --- deno.json | 2 +- deno.lock | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/deno.json b/deno.json index 671d860..0961750 100644 --- a/deno.json +++ b/deno.json @@ -22,7 +22,7 @@ }, "imports": { "@std/expect": "jsr:@std/expect@^1.0.19", - "@std/testing/bdd": "jsr:@std/testing@^1.0.18/bdd" + "@std/testing/bdd": "jsr:@std/testing@^1.0.19/bdd" }, "nodeModulesDir": "auto", "sloppyImports": true diff --git a/deno.lock b/deno.lock index 3465f7c..57983ac 100644 --- a/deno.lock +++ b/deno.lock @@ -3,10 +3,11 @@ "specifiers": { "jsr:@std/assert@^1.0.19": "1.0.19", "jsr:@std/expect@^1.0.19": "1.0.19", - "jsr:@std/internal@^1.0.12": "1.0.13", - "jsr:@std/internal@^1.0.13": "1.0.13", - "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/testing@^1.0.18": "1.0.18", + "jsr:@std/internal@^1.0.12": "1.0.14", + "jsr:@std/internal@^1.0.13": "1.0.14", + "jsr:@std/internal@^1.0.14": "1.0.14", + "jsr:@std/path@^1.1.4": "1.1.5", + "jsr:@std/testing@^1.0.19": "1.0.19", "npm:@aws-sdk/client-s3@*": "3.1045.0", "npm:@aws-sdk/s3-request-presigner@*": "3.1045.0", "npm:@axiomhq/pino@*": "1.6.1", @@ -73,20 +74,20 @@ "jsr:@std/path" ] }, - "@std/internal@1.0.13": { - "integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0" + "@std/internal@1.0.14": { + "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" }, - "@std/path@1.1.4": { - "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "@std/path@1.1.5": { + "integrity": "ccea00982ea28c36becaf6e62f855406c76a8c32d462f66f415bbb7d83a271bc", "dependencies": [ - "jsr:@std/internal@^1.0.12" + "jsr:@std/internal@^1.0.14" ] }, - "@std/testing@1.0.18": { - "integrity": "d3152f57b11666bf6358d0e127c7e3488e91178b0c2d8fbf0793e1c53cd13cb1", + "@std/testing@1.0.19": { + "integrity": "f4236172365b216728dc3cc8b5e80a9f4c33083d1e4ede7613d5b25b4014898e", "dependencies": [ "jsr:@std/assert", - "jsr:@std/internal@^1.0.13" + "jsr:@std/internal@^1.0.14" ] } }, @@ -3582,7 +3583,7 @@ "workspace": { "dependencies": [ "jsr:@std/expect@^1.0.19", - "jsr:@std/testing@^1.0.18" + "jsr:@std/testing@^1.0.19" ], "packageJson": { "dependencies": [ From 3e043e422d9e05d1041c08fdf7de1f82a644d8e5 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 09:35:05 +0000 Subject: [PATCH 11/27] fix(infra): add postgres and redis services to dev docker-compose.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README quickstart and scripts/setup.sh both assume `docker compose up -d` starts Postgres and Redis, but the compose file only defined frontend/backend build services — a fresh clone couldn't actually get a database running. Mirrors docker-compose.prod.yml's db/redis shape (image, healthcheck) with dev-appropriate host-exposed ports so `deno task db:migrate` and `deno task dev` running on the host can reach them at the .env.example defaults. --- docker-compose.yml | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 023ef17..9c87bb0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,44 @@ services: - "9999" environment: PORT: 9999 - DATABASE_URL: ${DATABASE_URL} + DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-orcta_dev} BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost} - REDIS_URL: ${REDIS_URL:-} + REDIS_URL: redis://redis:6379 + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + POSTGRES_DB: ${POSTGRES_DB:-orcta_dev} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-postgres}"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + postgres_data: + redis_data: From 03343b2d4ef995d1e96e918e47d61681afe5b007 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 09:46:36 +0000 Subject: [PATCH 12/27] feat(jobs): implement real email job, drop cleanup/sync stubs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cleanup/sync had no concrete meaning in a template (no domain to clean up or sync) and were pure TODO stubs — dropped rather than filled with speculative logic. email is now real: worker.ts looks up the template by name and sends it via the existing Resend wrapper. Routes better-auth's sendResetPassword/sendVerificationEmail through a new queueEmail helper (apps/backend/src/lib/email.ts) instead of calling sendEmail synchronously, so auth requests no longer block on the Resend API call. queueEmail queues through BullMQ when REDIS_URL is set and sends inline otherwise, keeping email and background jobs independently optional the way withCache already treats Redis for caching — addJob alone would have made email hard-require Redis, which isn't how either battery is documented. Updates docs/BATTERIES.md's Background Jobs and Email sections to match. --- apps/backend/src/jobs/index.ts | 40 +++++----- apps/backend/src/jobs/worker.ts | 25 ++----- apps/backend/src/lib/auth.ts | 19 ++--- apps/backend/src/lib/email.ts | 17 +++++ docs/BATTERIES.md | 127 +++++++++++++++++--------------- 5 files changed, 117 insertions(+), 111 deletions(-) diff --git a/apps/backend/src/jobs/index.ts b/apps/backend/src/jobs/index.ts index e7d731f..13b94cc 100644 --- a/apps/backend/src/jobs/index.ts +++ b/apps/backend/src/jobs/index.ts @@ -1,15 +1,29 @@ import { Queue } from "bullmq"; +import { passwordResetEmail, welcomeEmail } from "@repo/email-templates"; import { getRedis } from "@/lib/redis"; -// Define your job types -export type JobName = "email" | "cleanup" | "sync"; +export type EmailTemplateName = "welcome" | "passwordReset"; + +export type JobName = "email"; export interface JobData { - email: { to: string; template: string; data: Record }; - cleanup: { olderThanDays: number }; - sync: { userId: string }; + email: { + to: string; + template: EmailTemplateName; + props: { name: string; actionUrl?: string }; + }; } +// Shared template lookup — used by the worker to process queued jobs and by +// queueEmail's no-Redis fallback to send inline, so both paths build the same email. +export const emailTemplates: Record< + EmailTemplateName, + (props: { name: string; actionUrl?: string }) => ReturnType +> = { + welcome: welcomeEmail, + passwordReset: passwordResetEmail, +}; + // Create queues function createQueue(name: T) { // biome-ignore lint/suspicious/noExplicitAny: BullMQ accepts ioredis instances but types diverge @@ -18,34 +32,20 @@ function createQueue(name: T) { // Export queues (lazy initialization) let emailQueue: Queue | null = null; -let cleanupQueue: Queue | null = null; -let syncQueue: Queue | null = null; export function getEmailQueue(): Queue { if (!emailQueue) emailQueue = createQueue("email"); return emailQueue; } -export function getCleanupQueue(): Queue { - if (!cleanupQueue) cleanupQueue = createQueue("cleanup"); - return cleanupQueue; -} - -export function getSyncQueue(): Queue { - if (!syncQueue) syncQueue = createQueue("sync"); - return syncQueue; -} - // Helper to add jobs -export async function addJob( +export function addJob( name: T, data: JobData[T], options?: { delay?: number; priority?: number }, ) { const queueMap = { email: getEmailQueue(), - cleanup: getCleanupQueue(), - sync: getSyncQueue(), }; const queue = queueMap[name]; diff --git a/apps/backend/src/jobs/worker.ts b/apps/backend/src/jobs/worker.ts index 5468036..d1ffacb 100644 --- a/apps/backend/src/jobs/worker.ts +++ b/apps/backend/src/jobs/worker.ts @@ -1,29 +1,18 @@ import { type Job, Worker } from "bullmq"; import pino from "pino"; import { getRedis } from "@/lib/redis.ts"; -import type { JobData, JobName } from "./index.ts"; +import { sendEmail } from "@/lib/email.ts"; +import { emailTemplates, type JobData, type JobName } from "./index.ts"; const logger = pino({ name: "worker" }); const processors: { [K in JobName]: (job: Job) => Promise } = { async email(job) { - logger.info( - { to: job.data.to, template: job.data.template }, - "Processing email job", - ); - // TODO: Implement email sending - }, - async cleanup(job) { - logger.info( - { olderThanDays: job.data.olderThanDays }, - "Processing cleanup job", - ); - // TODO: Implement cleanup logic - }, - async sync(job) { - logger.info({ userId: job.data.userId }, "Processing sync job"); - // TODO: Implement sync logic + const { to, template, props } = job.data; + logger.info({ to, template }, "Processing email job"); + const { subject, html, text } = emailTemplates[template](props); + await sendEmail({ to, subject, html, text }); }, }; @@ -46,7 +35,7 @@ function startWorker(name: T) { return worker; } -const workers = (["email", "cleanup", "sync"] as JobName[]).map(startWorker); +const workers = (["email"] as JobName[]).map(startWorker); async function shutdown() { logger.info("Shutting down workers..."); diff --git a/apps/backend/src/lib/auth.ts b/apps/backend/src/lib/auth.ts index 750d321..037525f 100644 --- a/apps/backend/src/lib/auth.ts +++ b/apps/backend/src/lib/auth.ts @@ -4,9 +4,8 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { openAPI } from "better-auth/plugins"; import { twoFactor } from "better-auth/plugins/two-factor"; import { db, schema } from "@/db"; -import { sendEmail } from "@/lib/email"; +import { queueEmail } from "@/lib/email"; import { redis } from "@/lib/redis"; -import { passwordResetEmail, welcomeEmail } from "@repo/email-templates"; import type { User as DbUser } from "@repo/db/schema"; import env from "@/env.ts"; @@ -90,12 +89,10 @@ export const auth = betterAuth({ maxPasswordLength: 256, revokeSessionsOnPasswordReset: true, sendResetPassword: async ({ user, url }) => { - const template = passwordResetEmail({ name: user.name, actionUrl: url }); - await sendEmail({ + await queueEmail({ to: user.email, - subject: template.subject, - html: template.html, - text: template.text, + template: "passwordReset", + props: { name: user.name, actionUrl: url }, }); }, password: { @@ -106,12 +103,10 @@ export const auth = betterAuth({ }, emailVerification: { sendVerificationEmail: async ({ user, url }) => { - const template = welcomeEmail({ name: user.name, actionUrl: url }); - await sendEmail({ + await queueEmail({ to: user.email, - subject: template.subject, - html: template.html, - text: template.text, + template: "welcome", + props: { name: user.name, actionUrl: url }, }); }, sendOnSignUp: true, diff --git a/apps/backend/src/lib/email.ts b/apps/backend/src/lib/email.ts index bb0fb3f..bf1c714 100644 --- a/apps/backend/src/lib/email.ts +++ b/apps/backend/src/lib/email.ts @@ -1,5 +1,7 @@ import { Resend } from "resend"; import env from "@/env.ts"; +import { redis } from "@/lib/redis"; +import { addJob, emailTemplates, type JobData } from "@/jobs/index.ts"; const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null; @@ -28,3 +30,18 @@ export async function sendEmail(options: { console.error("[email] failed to send:", error); } } + +// Email and background jobs are independently optional batteries (RESEND_API_KEY +// vs REDIS_URL). Queue through BullMQ when Redis is configured; otherwise send +// inline so email keeps working without Redis, same as `sendEmail`'s own +// RESEND_API_KEY fallback. +export async function queueEmail(payload: JobData["email"]) { + if (redis) { + await addJob("email", payload); + return; + } + const { subject, html, text } = emailTemplates[payload.template]( + payload.props, + ); + await sendEmail({ to: payload.to, subject, html, text }); +} diff --git a/docs/BATTERIES.md b/docs/BATTERIES.md index 8772f35..5966543 100644 --- a/docs/BATTERIES.md +++ b/docs/BATTERIES.md @@ -263,9 +263,15 @@ ws.send(JSON.stringify({ type: "join", room: "chat-123" })); ## Background Jobs -Process work asynchronously with [BullMQ](https://docs.bullmq.io). +Process work asynchronously with [BullMQ](https://docs.bullmq.io). The one +built-in job type is `email` — it's what powers the [Email](#email) battery's +`queueEmail` helper, used by better-auth's sign-up and password-reset flows +(`apps/backend/src/lib/auth.ts`) so those requests don't block on an outbound +Resend API call. -**Requires env var:** `REDIS_URL` +**Requires env var:** `REDIS_URL` — without it, `queueEmail` degrades to +sending inline instead of queuing (see [Email](#email)); code that calls +`addJob` directly requires Redis. ### Setup @@ -278,32 +284,45 @@ REDIS_URL=redis://localhost:6379 Defined in `apps/backend/src/jobs/index.ts`: ```typescript -export type JobName = "email" | "cleanup" | "sync"; +export type EmailTemplateName = "welcome" | "passwordReset"; +export type JobName = "email"; export interface JobData { - email: { to: string; template: string; data: Record }; - cleanup: { olderThanDays: number }; - sync: { userId: string }; + email: { + to: string; + template: EmailTemplateName; + props: { name: string; actionUrl?: string }; + }; } ``` ### Queue a job +Prefer `queueEmail` (from `@/lib/email`) over `addJob` directly for emails — +it queues through Redis when available and falls back to sending inline when +it isn't, so the call site doesn't need to care: + ```typescript -import { addJob } from "@/jobs"; +import { queueEmail } from "@/lib/email"; -// Fire and forget -await addJob("email", { +await queueEmail({ to: "user@example.com", template: "welcome", - data: { name: "Alex" }, + props: { name: "Alex" }, }); +``` -// With options -await addJob("cleanup", { olderThanDays: 30 }, { - delay: 60_000, // wait 1 min before processing - priority: 10, // higher = processed first -}); +`addJob` is the lower-level primitive `queueEmail` and any future job types +build on — it always requires Redis: + +```typescript +import { addJob } from "@/jobs"; + +await addJob( + "email", + { to: "user@example.com", template: "welcome", props: { name: "Alex" } }, + { delay: 60_000, priority: 10 }, // optional: wait 1 min, higher priority = processed first +); ``` Jobs are automatically kept for the last 100 successes and 1 000 failures in @@ -311,24 +330,16 @@ Redis. ### Process jobs -Add your logic in `apps/backend/src/jobs/worker.ts` inside the `processors` -object: +`apps/backend/src/jobs/worker.ts`'s `processors.email` looks up the template +by name (via the `emailTemplates` map exported from `jobs/index.ts`) and sends +it: ```typescript -const processors = { +const processors: { [K in JobName]: (job: Job) => Promise } = { async email(job) { - const { to, template, data } = job.data; - await sendEmail(to, template, data); // wire up your email sender - }, - - async sync(job) { - const { userId } = job.data; - // fetch external data, update DB, etc. - }, - - async cleanup(job) { - const { olderThanDays } = job.data; - // delete old records + const { to, template, props } = job.data; + const { subject, html, text } = emailTemplates[template](props); + await sendEmail({ to, subject, html, text }); }, }; ``` @@ -349,11 +360,10 @@ server. 1. Add the name to the `JobName` union and its payload to `JobData` in `jobs/index.ts` -2. Create a queue getter following the existing pattern (`getSyncQueue` etc.) +2. Add a queue getter following the existing `getEmailQueue` pattern 3. Add the queue to the `queueMap` inside `addJob` 4. Add a processor in `worker.ts` -5. Add the job name to the workers array: - `(["email", "cleanup", "sync", "yourJob"] as JobName[])` +5. Add the job name to the workers array: `(["email", "yourJob"] as JobName[])` --- @@ -421,9 +431,14 @@ Every rate-limited response includes: ## Email -Send transactional emails with [Resend](https://resend.com). +Send transactional emails with [Resend](https://resend.com). Templates live +in `@repo/email-templates`; `apps/backend/src/lib/email.ts` wraps Resend +(`sendEmail`) and adds the [Background Jobs](#background-jobs)-aware +`queueEmail` — the one both better-auth hooks (`sendResetPassword`, +`sendVerificationEmail` in `apps/backend/src/lib/auth.ts`) actually call. -**Requires env var:** `RESEND_API_KEY` +**Requires env var:** `RESEND_API_KEY` — without it, `sendEmail` logs instead +of sending, so email works end-to-end in dev with no setup. ### Setup @@ -434,38 +449,28 @@ RESEND_API_KEY=re_xxxxx ### Usage ```typescript -import { Resend } from "resend"; -import { passwordResetEmail, welcomeEmail } from "@repo/email-templates"; - -const resend = new Resend(process.env.RESEND_API_KEY); +import { queueEmail } from "@/lib/email"; -// Welcome email -const welcome = welcomeEmail({ - name: "Alex", - actionUrl: "https://app.example.com/verify?token=xxx", -}); - -await resend.emails.send({ - from: "hello@yourdomain.com", +// Queues through Redis if REDIS_URL is set, otherwise sends inline +await queueEmail({ to: "alex@example.com", - subject: welcome.subject, - html: welcome.html, - text: welcome.text, + template: "welcome", // or "passwordReset" + props: { name: "Alex", actionUrl: "https://app.example.com/verify?token=xxx" }, }); +``` -// Password reset -const reset = passwordResetEmail({ - name: "Alex", - actionUrl: "https://app.example.com/reset?token=xxx", -}); +Need to send immediately, bypassing the queue entirely? Use `sendEmail` with a +template directly: -await resend.emails.send({ - from: "hello@yourdomain.com", - to: "alex@example.com", - subject: reset.subject, - html: reset.html, - text: reset.text, +```typescript +import { sendEmail } from "@/lib/email"; +import { welcomeEmail } from "@repo/email-templates"; + +const { subject, html, text } = welcomeEmail({ + name: "Alex", + actionUrl: "https://app.example.com/verify?token=xxx", }); +await sendEmail({ to: "alex@example.com", subject, html, text }); ``` ### Available templates From 354e0978971d5df80c81145f668696468405f9a5 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 09:50:15 +0000 Subject: [PATCH 13/27] chore: normalize deno.json formatting repo-wide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root, apps/backend, and apps/frontend deno.json used tabs and failed deno fmt --check; the three packages/*/deno.json already used 2-space and passed. Invisible until now because CI never ran deno fmt --check. Ran deno fmt on the three offenders so all six deno.json files agree. Biome's formatter (indentStyle: tab, applied repo-wide with no path scoping) was fighting Deno's fmt on these same files, so excludes **/deno.json and **/deno.lock via a biome.json override — Deno owns its own config format, Biome owns everything else. --- apps/backend/deno.json | 192 ++++++++++++++++++++-------------------- apps/frontend/deno.json | 42 ++++----- biome.json | 6 ++ deno.json | 54 +++++------ 4 files changed, 150 insertions(+), 144 deletions(-) diff --git a/apps/backend/deno.json b/apps/backend/deno.json index 03d51ac..aae5e68 100644 --- a/apps/backend/deno.json +++ b/apps/backend/deno.json @@ -1,98 +1,98 @@ { - "name": "backend", - "version": "0.1.0", - "exports": "./src/index.ts", - "compilerOptions": { - "paths": { - "@/*": ["./src/*"] - }, - "module": "NodeNext", - "moduleResolution": "NodeNext", - "skipLibCheck": true - }, - "tasks": { - "dev": "deno run --watch --env-file=.env -A src/index.ts", - "start": "deno run --env-file=.env -A src/index.ts", - "worker": "deno run --env-file=.env -A src/jobs/worker.ts", - "test": "deno test --env-file=.env -A", - "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", - "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", - "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" - }, - "imports": { - "@/": "./src/", - "@/app": "./src/app.ts", - "@/db": "./src/db/index.ts", - "@/env": "./src/env.ts", - "@/lib/auth": "./src/lib/auth.ts", - "@/lib/create-app": "./src/lib/create-app.ts", - "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", - "@/lib/types": "./src/lib/types.ts", - "@/lib/redis": "./src/lib/redis.ts", - "@/lib/error": "./src/lib/error.ts", - "@/lib/infra": "./src/lib/infra.ts", - "@/lib/cache": "./src/lib/cache.ts", - "@/lib/storage": "./src/lib/storage.ts", - "@/lib/rate-limit": "./src/lib/rate-limit.ts", - "@/lib/ws": "./src/lib/ws.ts", - "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", - "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", - "@/middlewares/auth": "./src/middlewares/auth.ts", - "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", - "@/modules/health": "./src/modules/health/index.ts", - "@/modules/health/handlers": "./src/modules/health/handlers.ts", - "@/modules/health/routes": "./src/modules/health/routes.ts", - "@/modules/users": "./src/modules/users/index.ts", - "@/modules/users/handlers": "./src/modules/users/handlers.ts", - "@/modules/users/routes": "./src/modules/users/routes.ts", - "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", - "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", - "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", - "@/jobs/index": "./src/jobs/index.ts", - "@/jobs/worker": "./src/jobs/worker.ts", - "@/lib/email": "./src/lib/email.ts", - "hono": "npm:hono", - "hono/cors": "npm:hono/cors", - "hono/dev": "npm:hono/dev", - "hono/ws": "npm:hono/ws", - "@hono/zod-openapi": "npm:@hono/zod-openapi", - "@hono/swagger-ui": "npm:@hono/swagger-ui", - "@hono/zod-validator": "npm:@hono/zod-validator", - "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", - "better-auth": "npm:better-auth", - "better-auth/adapters": "npm:better-auth/adapters", - "better-auth/plugins": "npm:better-auth/plugins", - "better-auth/plugins/two-factor": "npm:better-auth/plugins/two-factor", - "ioredis": "npm:ioredis", - "bullmq": "npm:bullmq", - "pino": "npm:pino", - "pino-pretty": "npm:pino-pretty", - "hono-pino": "npm:hono-pino", - "stoker": "npm:stoker", - "stoker/middlewares": "npm:stoker/middlewares", - "stoker/openapi": "npm:stoker/openapi", - "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", - "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", - "zod": "npm:zod", - "drizzle-kit": "npm:drizzle-kit", - "drizzle-orm": "npm:drizzle-orm", - "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", - "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", - "drizzle-orm/postgres-js/migrator": "npm:drizzle-orm/postgres-js/migrator", - "postgres": "npm:postgres", - "drizzle-zod": "npm:drizzle-zod", - "@node-rs/argon2": "npm:@node-rs/argon2", - "resend": "npm:resend", - "dotenv": "npm:dotenv", - "dotenv-expand": "npm:dotenv-expand", - "@axiomhq/pino": "npm:@axiomhq/pino" - }, - "lint": { - "rules": { - "exclude": ["no-explicit-any", "no-non-null-assertion"] - } - }, - "test": { - "include": ["src/**/*.test.ts"] - } + "name": "backend", + "version": "0.1.0", + "exports": "./src/index.ts", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + }, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "skipLibCheck": true + }, + "tasks": { + "dev": "deno run --watch --env-file=.env -A src/index.ts", + "start": "deno run --env-file=.env -A src/index.ts", + "worker": "deno run --env-file=.env -A src/jobs/worker.ts", + "test": "deno test --env-file=.env -A", + "db:migrate": "deno run --env-file=.env --allow-env --allow-net --allow-read --allow-sys src/db/migrate.ts", + "db:studio": "deno run --env-file=.env -A npm:drizzle-kit studio", + "db:generate": "deno run --env-file=.env -A npm:drizzle-kit generate" + }, + "imports": { + "@/": "./src/", + "@/app": "./src/app.ts", + "@/db": "./src/db/index.ts", + "@/env": "./src/env.ts", + "@/lib/auth": "./src/lib/auth.ts", + "@/lib/create-app": "./src/lib/create-app.ts", + "@/lib/configure-open-api": "./src/lib/configure-open-api.ts", + "@/lib/types": "./src/lib/types.ts", + "@/lib/redis": "./src/lib/redis.ts", + "@/lib/error": "./src/lib/error.ts", + "@/lib/infra": "./src/lib/infra.ts", + "@/lib/cache": "./src/lib/cache.ts", + "@/lib/storage": "./src/lib/storage.ts", + "@/lib/rate-limit": "./src/lib/rate-limit.ts", + "@/lib/ws": "./src/lib/ws.ts", + "@/lib/http-status-codes": "./src/lib/http-status-codes.ts", + "@/lib/http-status-phrases": "./src/lib/http-status-phrases.ts", + "@/middlewares/auth": "./src/middlewares/auth.ts", + "@/middlewares/wide-event": "./src/middlewares/wide-event.ts", + "@/modules/health": "./src/modules/health/index.ts", + "@/modules/health/handlers": "./src/modules/health/handlers.ts", + "@/modules/health/routes": "./src/modules/health/routes.ts", + "@/modules/users": "./src/modules/users/index.ts", + "@/modules/users/handlers": "./src/modules/users/handlers.ts", + "@/modules/users/routes": "./src/modules/users/routes.ts", + "@/modules/users/users.repository": "./src/modules/users/users.repository.ts", + "@/modules/users/users.errors": "./src/modules/users/users.errors.ts", + "@/modules/users/users.usecases": "./src/modules/users/users.usecases.ts", + "@/jobs/index": "./src/jobs/index.ts", + "@/jobs/worker": "./src/jobs/worker.ts", + "@/lib/email": "./src/lib/email.ts", + "hono": "npm:hono", + "hono/cors": "npm:hono/cors", + "hono/dev": "npm:hono/dev", + "hono/ws": "npm:hono/ws", + "@hono/zod-openapi": "npm:@hono/zod-openapi", + "@hono/swagger-ui": "npm:@hono/swagger-ui", + "@hono/zod-validator": "npm:@hono/zod-validator", + "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference", + "better-auth": "npm:better-auth", + "better-auth/adapters": "npm:better-auth/adapters", + "better-auth/plugins": "npm:better-auth/plugins", + "better-auth/plugins/two-factor": "npm:better-auth/plugins/two-factor", + "ioredis": "npm:ioredis", + "bullmq": "npm:bullmq", + "pino": "npm:pino", + "pino-pretty": "npm:pino-pretty", + "hono-pino": "npm:hono-pino", + "stoker": "npm:stoker", + "stoker/middlewares": "npm:stoker/middlewares", + "stoker/openapi": "npm:stoker/openapi", + "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3", + "@aws-sdk/s3-request-presigner": "npm:@aws-sdk/s3-request-presigner", + "zod": "npm:zod", + "drizzle-kit": "npm:drizzle-kit", + "drizzle-orm": "npm:drizzle-orm", + "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", + "drizzle-orm/postgres-js": "npm:drizzle-orm/postgres-js", + "drizzle-orm/postgres-js/migrator": "npm:drizzle-orm/postgres-js/migrator", + "postgres": "npm:postgres", + "drizzle-zod": "npm:drizzle-zod", + "@node-rs/argon2": "npm:@node-rs/argon2", + "resend": "npm:resend", + "dotenv": "npm:dotenv", + "dotenv-expand": "npm:dotenv-expand", + "@axiomhq/pino": "npm:@axiomhq/pino" + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any", "no-non-null-assertion"] + } + }, + "test": { + "include": ["src/**/*.test.ts"] + } } diff --git a/apps/frontend/deno.json b/apps/frontend/deno.json index d5e53cd..89eb75c 100644 --- a/apps/frontend/deno.json +++ b/apps/frontend/deno.json @@ -1,23 +1,23 @@ { - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "react", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "types": ["vite/client"] - }, - "imports": { - "@/": "./src/" - }, - "exclude": ["node_modules", "dist", ".tanstack"], - "tasks": { - "dev": "deno run -A npm:vite dev --port 3000", - "build": "deno run -A npm:vite build", - "preview": "deno run -A npm:vite preview", - "typecheck": "tsc --noEmit" - }, - "lint": { - "rules": { - "exclude": ["no-explicit-any", "no-non-null-assertion"] - } - } + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "react", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["vite/client"] + }, + "imports": { + "@/": "./src/" + }, + "exclude": ["node_modules", "dist", ".tanstack"], + "tasks": { + "dev": "deno run -A npm:vite dev --port 3000", + "build": "deno run -A npm:vite build", + "preview": "deno run -A npm:vite preview", + "typecheck": "tsc --noEmit" + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any", "no-non-null-assertion"] + } + } } diff --git a/biome.json b/biome.json index b9cb233..334e3d0 100644 --- a/biome.json +++ b/biome.json @@ -87,6 +87,12 @@ "linter": { "enabled": false }, "formatter": { "enabled": false }, "assist": { "enabled": false } + }, + { + "includes": ["**/deno.json", "**/deno.lock"], + "linter": { "enabled": false }, + "formatter": { "enabled": false }, + "assist": { "enabled": false } } ] } diff --git a/deno.json b/deno.json index 0961750..d4473b6 100644 --- a/deno.json +++ b/deno.json @@ -1,29 +1,29 @@ { - "workspace": [ - "apps/backend", - "apps/frontend", - "packages/shared", - "packages/db", - "packages/email-templates" - ], - "tasks": { - "dev": "deno task --cwd=apps/backend dev", - "dev:backend": "deno task --cwd=apps/backend dev", - "dev:frontend": "deno task --cwd=apps/frontend dev", - "start": "deno task --cwd=apps/backend start", - "worker": "deno task --cwd=apps/backend worker", - "test": "deno task --cwd=apps/backend test", - "lint": "deno lint", - "fmt": "deno fmt", - "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", - "db:migrate": "deno task --cwd=apps/backend db:migrate", - "db:studio": "deno task --cwd=apps/backend db:studio", - "db:generate": "deno task --cwd=apps/backend db:generate" - }, - "imports": { - "@std/expect": "jsr:@std/expect@^1.0.19", - "@std/testing/bdd": "jsr:@std/testing@^1.0.19/bdd" - }, - "nodeModulesDir": "auto", - "sloppyImports": true + "workspace": [ + "apps/backend", + "apps/frontend", + "packages/shared", + "packages/db", + "packages/email-templates" + ], + "tasks": { + "dev": "deno task --cwd=apps/backend dev", + "dev:backend": "deno task --cwd=apps/backend dev", + "dev:frontend": "deno task --cwd=apps/frontend dev", + "start": "deno task --cwd=apps/backend start", + "worker": "deno task --cwd=apps/backend worker", + "test": "deno task --cwd=apps/backend test", + "lint": "deno lint", + "fmt": "deno fmt", + "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", + "db:migrate": "deno task --cwd=apps/backend db:migrate", + "db:studio": "deno task --cwd=apps/backend db:studio", + "db:generate": "deno task --cwd=apps/backend db:generate" + }, + "imports": { + "@std/expect": "jsr:@std/expect@^1.0.19", + "@std/testing/bdd": "jsr:@std/testing@^1.0.19/bdd" + }, + "nodeModulesDir": "auto", + "sloppyImports": true } From b6320008a0d8881939b3e5ac3e8b45ddbb0ff83f Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 09:59:23 +0000 Subject: [PATCH 14/27] ci: add frontend checks and deno fmt to CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI never touched the frontend (no lint/typecheck/build) and never ran deno fmt --check, so both could silently break on master. Adds deno install + biome ci (pinned to the same 2.3.7 as the root devDependency) + tsc --noEmit + vite build for apps/frontend, mirroring apps/frontend/Dockerfile's actual build path. Pins setup-deno's deno-version to 2.7.14 to match what both Dockerfiles already pin, instead of floating v2.x. Enabling deno fmt --check surfaced that it disagreed with Biome across apps/frontend (tabs, Biome's territory) and reflowed markdown prose in docs — excluded both via deno.json's fmt.exclude, plus packages/db/migrations (drizzle-kit generated, shouldn't be hand -formatted, consistent with biome.json's existing exclusion for the same directory). The two remaining genuine strays (root package.json, packages/db/src/schema/users.ts, plus a line-wrap in the jobs/index.ts from the previous commit) are reformatted to match. --- .github/workflows/ci.yml | 20 +++++++++++++++++++- apps/backend/src/jobs/index.ts | 4 +++- deno.json | 3 +++ package.json | 30 +++++++++++++++--------------- packages/db/src/schema/users.ts | 30 +++++++++++++++--------------- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f032a7..2393a79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,17 +40,35 @@ jobs: - uses: denoland/setup-deno@v2 with: - deno-version: v2.x + deno-version: "2.7.14" - name: Cache dependencies run: deno cache apps/backend/src/index.ts apps/backend/src/jobs/worker.ts + - name: Format check + run: deno fmt --check + - name: Lint run: deno lint - name: Type check run: deno check apps/backend/src/index.ts + - name: Install frontend dependencies + working-directory: apps/frontend + run: deno install + + - name: Frontend lint + run: deno run -A npm:@biomejs/biome@2.3.7 ci apps/frontend + + - name: Frontend type check + working-directory: apps/frontend + run: deno task typecheck + + - name: Frontend build + working-directory: apps/frontend + run: deno task build + - name: Run migrations run: deno run --allow-env --allow-net --allow-read --allow-sys apps/backend/src/db/migrate.ts env: diff --git a/apps/backend/src/jobs/index.ts b/apps/backend/src/jobs/index.ts index 13b94cc..84a1cea 100644 --- a/apps/backend/src/jobs/index.ts +++ b/apps/backend/src/jobs/index.ts @@ -18,7 +18,9 @@ export interface JobData { // queueEmail's no-Redis fallback to send inline, so both paths build the same email. export const emailTemplates: Record< EmailTemplateName, - (props: { name: string; actionUrl?: string }) => ReturnType + ( + props: { name: string; actionUrl?: string }, + ) => ReturnType > = { welcome: welcomeEmail, passwordReset: passwordResetEmail, diff --git a/deno.json b/deno.json index d4473b6..3a00047 100644 --- a/deno.json +++ b/deno.json @@ -24,6 +24,9 @@ "@std/expect": "jsr:@std/expect@^1.0.19", "@std/testing/bdd": "jsr:@std/testing@^1.0.19/bdd" }, + "fmt": { + "exclude": ["apps/frontend", "**/*.md", "packages/db/migrations"] + }, "nodeModulesDir": "auto", "sloppyImports": true } diff --git a/package.json b/package.json index d864c13..128c5b8 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { - "name": "orcta-stack", - "version": "1.0.0", - "license": "MIT", - "private": true, - "type": "module", - "scripts": { - "setup": "./scripts/setup.sh", - "lint": "deno lint", - "fmt": "deno fmt", - "clean": "rm -rf apps/frontend/dist apps/frontend/node_modules node_modules/.cache" - }, - "devDependencies": { - "@biomejs/biome": "2.3.7" - }, - "packageManager": "pnpm@9.15.0" + "name": "orcta-stack", + "version": "1.0.0", + "license": "MIT", + "private": true, + "type": "module", + "scripts": { + "setup": "./scripts/setup.sh", + "lint": "deno lint", + "fmt": "deno fmt", + "clean": "rm -rf apps/frontend/dist apps/frontend/node_modules node_modules/.cache" + }, + "devDependencies": { + "@biomejs/biome": "2.3.7" + }, + "packageManager": "pnpm@9.15.0" } diff --git a/packages/db/src/schema/users.ts b/packages/db/src/schema/users.ts index 25c63da..6725517 100644 --- a/packages/db/src/schema/users.ts +++ b/packages/db/src/schema/users.ts @@ -4,21 +4,21 @@ import { createInsertSchema, createSelectSchema } from "drizzle-zod"; export const userRoleEnum = pgEnum("user_role", ["buyer", "seller", "admin"]); export const users = pgTable("users", { - id: text("id").primaryKey(), - email: text("email").notNull().unique(), - name: text("name").notNull(), - image: text("image"), - role: userRoleEnum("role").default("buyer").notNull(), - emailVerified: boolean("email_verified").default(false).notNull(), - twoFactorEnabled: boolean("two_factor_enabled").default(false).notNull(), - twoFactorSecret: text("two_factor_secret"), - backupCodes: text("backup_codes"), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .defaultNow() - .notNull(), + id: text("id").primaryKey(), + email: text("email").notNull().unique(), + name: text("name").notNull(), + image: text("image"), + role: userRoleEnum("role").default("buyer").notNull(), + emailVerified: boolean("email_verified").default(false).notNull(), + twoFactorEnabled: boolean("two_factor_enabled").default(false).notNull(), + twoFactorSecret: text("two_factor_secret"), + backupCodes: text("backup_codes"), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), }); // Zod schemas for validation From 7d7d3404cdc1ad5b694c925860ece30dfd8e8d0b Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:03:41 +0000 Subject: [PATCH 15/27] test(db): add schema tests for packages/db, fix stale UserRole type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserRole was hardcoded to "user" | "admin" while the actual userRoleEnum is ["buyer", "seller", "admin"] — now derived from the enum's own values instead of hand-maintained, so it can't drift again. packages/db had zero tests. Adds schema/type-level coverage for userRoleEnum and the drizzle-zod insert/select schemas, following packages/shared's existing describe/it + @std/expect convention — no DB connection, matching how packages/db has no live client of its own (that lives in apps/backend/src/db). --- packages/db/deno.json | 3 + packages/db/src/__tests__/schema.test.ts | 80 ++++++++++++++++++++++++ packages/db/src/schema/users.ts | 1 + packages/db/src/types.ts | 7 +-- 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 packages/db/src/__tests__/schema.test.ts diff --git a/packages/db/deno.json b/packages/db/deno.json index 97fa8e1..1e0e08e 100644 --- a/packages/db/deno.json +++ b/packages/db/deno.json @@ -19,5 +19,8 @@ "rules": { "exclude": ["no-slow-types"] } + }, + "test": { + "include": ["src/**/*.test.ts"] } } diff --git a/packages/db/src/__tests__/schema.test.ts b/packages/db/src/__tests__/schema.test.ts new file mode 100644 index 0000000..d973209 --- /dev/null +++ b/packages/db/src/__tests__/schema.test.ts @@ -0,0 +1,80 @@ +// Schema/type-level tests — no DB connection. packages/db has no live client +// of its own (that lives in apps/backend/src/db); these tests validate the +// drizzle table definitions and drizzle-zod schemas in isolation. + +import { describe, it } from "@std/testing/bdd"; +import { expect } from "@std/expect"; +import { + insertUserSchema, + selectUserSchema, + userRoleEnum, +} from "../schema/users.ts"; + +// ─── userRoleEnum ─────────────────────────────────────────────────────────── + +describe("userRoleEnum", () => { + it("has exactly buyer, seller, and admin", () => { + expect(userRoleEnum.enumValues).toEqual(["buyer", "seller", "admin"]); + }); +}); + +// ─── insertUserSchema ─────────────────────────────────────────────────────── + +describe("insertUserSchema", () => { + const validInsert = { + id: "user-1", + email: "alice@example.com", + name: "Alice", + }; + + it("accepts a minimal valid insert, defaulting role-dependent fields", () => { + expect(() => insertUserSchema.parse(validInsert)).not.toThrow(); + }); + + it("accepts an explicit valid role", () => { + const result = insertUserSchema.parse({ ...validInsert, role: "seller" }); + expect(result.role).toBe("seller"); + }); + + it("rejects a role outside the enum", () => { + expect(() => insertUserSchema.parse({ ...validInsert, role: "superadmin" })) + .toThrow(); + }); + + it("rejects when a required field is missing", () => { + const { email: _email, ...withoutEmail } = validInsert; + expect(() => insertUserSchema.parse(withoutEmail)).toThrow(); + }); +}); + +// ─── selectUserSchema ─────────────────────────────────────────────────────── + +describe("selectUserSchema", () => { + const validRow = { + id: "user-1", + email: "alice@example.com", + name: "Alice", + image: null, + role: "buyer" as const, + emailVerified: true, + twoFactorEnabled: false, + twoFactorSecret: null, + backupCodes: null, + createdAt: new Date("2024-01-01"), + updatedAt: new Date("2024-01-01"), + }; + + it("accepts a fully-populated valid row", () => { + expect(() => selectUserSchema.parse(validRow)).not.toThrow(); + }); + + it("rejects a row missing a required field", () => { + const { createdAt: _createdAt, ...withoutCreatedAt } = validRow; + expect(() => selectUserSchema.parse(withoutCreatedAt)).toThrow(); + }); + + it("rejects a role outside the enum", () => { + expect(() => selectUserSchema.parse({ ...validRow, role: "root" })) + .toThrow(); + }); +}); diff --git a/packages/db/src/schema/users.ts b/packages/db/src/schema/users.ts index 6725517..9fcce4d 100644 --- a/packages/db/src/schema/users.ts +++ b/packages/db/src/schema/users.ts @@ -2,6 +2,7 @@ import { boolean, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core"; import { createInsertSchema, createSelectSchema } from "drizzle-zod"; export const userRoleEnum = pgEnum("user_role", ["buyer", "seller", "admin"]); +export type UserRole = (typeof userRoleEnum.enumValues)[number]; export const users = pgTable("users", { id: text("id").primaryKey(), diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index eeff6d0..15f8402 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -1,8 +1,5 @@ import type { Account, Session } from "./schema/sessions.ts"; -import type { InsertUser, User } from "./schema/users.ts"; +import type { InsertUser, User, UserRole } from "./schema/users.ts"; // Re-export schema types -export type { Account, InsertUser, Session, User }; - -// Database-specific types -export type UserRole = "user" | "admin"; +export type { Account, InsertUser, Session, User, UserRole }; From ce622de527ad0cd8f08b555f3c08efc8c39e67ef Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:11:47 +0000 Subject: [PATCH 16/27] test(frontend): add Deno-native test setup with one component and one unit test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apps/frontend had zero tests. This migration branch already dropped standalone vitest.config.ts from packages/shared and packages/email -templates in favor of deno test + @std/testing/bdd + @std/expect, so the frontend follows the same convention rather than reintroducing Vitest — npm:jsdom for a DOM environment, npm:@testing-library/react for rendering, installed via apps/frontend/src/test-setup.ts (Deno's runner has no Vitest-style global setupFiles, so it's imported explicitly per test file). Two examples, mirroring the backend's prepareEmailChange as the canonical pure-function pattern: - lib/utils.ts's cn() — pure, no DOM - components/ui/field-error.tsx — simplest real component with actual conditional logic and no router/query context dependency apps/frontend/tsconfig.json now excludes test files — tsc has no way to resolve @std/* JSR specifiers (they're Deno-only), and test files never go through Vite's build anyway, so deno test/check remains the one authoritative type-checker for them. Also adds a root test:frontend task mirroring the existing dev/dev:frontend split. --- README.md | 3 +- apps/frontend/deno.json | 6 +- apps/frontend/package.json | 2 + .../ui/__tests__/field-error.test.tsx | 27 ++ .../src/components/ui/field-error.tsx | 2 +- apps/frontend/src/lib/__tests__/utils.test.ts | 23 ++ apps/frontend/src/test-setup.ts | 26 ++ apps/frontend/tsconfig.json | 3 +- deno.json | 1 + deno.lock | 284 +++++++++++++++++- 10 files changed, 372 insertions(+), 5 deletions(-) create mode 100644 apps/frontend/src/components/ui/__tests__/field-error.test.tsx create mode 100644 apps/frontend/src/lib/__tests__/utils.test.ts create mode 100644 apps/frontend/src/test-setup.ts diff --git a/README.md b/README.md index ea2fb70..4da94e2 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,8 @@ Open [localhost:3000](http://localhost:3000). You're live. ```bash deno task dev # Run backend deno task dev:frontend # Run frontend -deno test -A # Run tests +deno test -A # Run backend + shared/db/email-templates tests +deno task test:frontend # Run frontend tests deno lint # Check code deno check # Check types ``` diff --git a/apps/frontend/deno.json b/apps/frontend/deno.json index 89eb75c..a8889c8 100644 --- a/apps/frontend/deno.json +++ b/apps/frontend/deno.json @@ -13,7 +13,11 @@ "dev": "deno run -A npm:vite dev --port 3000", "build": "deno run -A npm:vite build", "preview": "deno run -A npm:vite preview", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "deno test -A" + }, + "test": { + "include": ["src/**/*.test.ts", "src/**/*.test.tsx"] }, "lint": { "rules": { diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 50dc028..983a785 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -31,9 +31,11 @@ }, "devDependencies": { "@tanstack/router-plugin": "^1.134.14", + "@testing-library/react": "^16.3.2", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", + "jsdom": "^30.0.1", "typescript": "~5.9.3", "vite": "^7.1.7" } diff --git a/apps/frontend/src/components/ui/__tests__/field-error.test.tsx b/apps/frontend/src/components/ui/__tests__/field-error.test.tsx new file mode 100644 index 0000000..2a6ade0 --- /dev/null +++ b/apps/frontend/src/components/ui/__tests__/field-error.test.tsx @@ -0,0 +1,27 @@ +import "../../../test-setup.ts"; + +import { expect } from "@std/expect"; +import { afterEach, describe, it } from "@std/testing/bdd"; +import { cleanup, render, screen } from "@testing-library/react"; +import { FieldError } from "../field-error.tsx"; + +afterEach(cleanup); + +describe("FieldError", () => { + it("renders nothing when there are no errors", () => { + const { container } = render(); + expect(container.innerHTML).toBe(""); + }); + + it("renders a single error message", () => { + render(); + expect(screen.getByRole("alert").textContent).toBe("Name is required"); + }); + + it("joins multiple error messages with a comma", () => { + render(); + expect(screen.getByRole("alert").textContent).toBe( + "Too short, Must be unique", + ); + }); +}); diff --git a/apps/frontend/src/components/ui/field-error.tsx b/apps/frontend/src/components/ui/field-error.tsx index f9b295c..adc9131 100644 --- a/apps/frontend/src/components/ui/field-error.tsx +++ b/apps/frontend/src/components/ui/field-error.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils.ts"; type FieldErrorProps = { errors: string[]; diff --git a/apps/frontend/src/lib/__tests__/utils.test.ts b/apps/frontend/src/lib/__tests__/utils.test.ts new file mode 100644 index 0000000..1edd2f4 --- /dev/null +++ b/apps/frontend/src/lib/__tests__/utils.test.ts @@ -0,0 +1,23 @@ +// Pure function — no DOM. Call with plain values. + +import { expect } from "@std/expect"; +import { describe, it } from "@std/testing/bdd"; +import { cn } from "../utils.ts"; + +describe("cn", () => { + it("joins multiple class strings", () => { + expect(cn("a", "b", "c")).toBe("a b c"); + }); + + it("drops falsy values", () => { + expect(cn("a", false, null, undefined, "b")).toBe("a b"); + }); + + it("lets a later conflicting Tailwind class win", () => { + expect(cn("px-2", "px-4")).toBe("px-4"); + }); + + it("applies conditional classes via object syntax", () => { + expect(cn("base", { active: true, disabled: false })).toBe("base active"); + }); +}); diff --git a/apps/frontend/src/test-setup.ts b/apps/frontend/src/test-setup.ts new file mode 100644 index 0000000..319b437 --- /dev/null +++ b/apps/frontend/src/test-setup.ts @@ -0,0 +1,26 @@ +// Installs a jsdom environment on globalThis so @testing-library/react can +// render components under `deno test`. Import this first in any test file +// that needs a DOM — Deno's test runner has no Vitest-style global setupFiles. + +import { JSDOM } from "jsdom"; + +const dom = new JSDOM("", { + url: "http://localhost/", + pretendToBeVisual: true, +}); + +// biome-ignore lint/suspicious/noExplicitAny: assigning jsdom globals onto globalThis +const g = globalThis as any; + +g.window = dom.window; +g.document = dom.window.document; +g.navigator = dom.window.navigator; +g.HTMLElement = dom.window.HTMLElement; +g.Element = dom.window.Element; +g.Node = dom.window.Node; +g.customElements = dom.window.customElements; +g.getComputedStyle = dom.window.getComputedStyle; +g.requestAnimationFrame = (cb: FrameRequestCallback) => + setTimeout(() => cb(Date.now()), 0); +g.cancelAnimationFrame = (id: number) => clearTimeout(id); +g.IS_REACT_ACT_ENVIRONMENT = true; diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index ec93b6d..64c8ba4 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -23,5 +23,6 @@ "@/*": ["./src/*"] } }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/test-setup.ts"] } diff --git a/deno.json b/deno.json index 3a00047..a7a5007 100644 --- a/deno.json +++ b/deno.json @@ -13,6 +13,7 @@ "start": "deno task --cwd=apps/backend start", "worker": "deno task --cwd=apps/backend worker", "test": "deno task --cwd=apps/backend test", + "test:frontend": "deno task --cwd=apps/frontend test", "lint": "deno lint", "fmt": "deno fmt", "check": "deno check apps/backend/src/index.ts apps/backend/src/jobs/worker.ts", diff --git a/deno.lock b/deno.lock index 57983ac..cebf4db 100644 --- a/deno.lock +++ b/deno.lock @@ -25,6 +25,7 @@ "npm:@tanstack/react-router-devtools@^1.134.13": "1.166.13_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_@tanstack+router-core@1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6_csstype@3.2.3", "npm:@tanstack/react-router@^1.134.13": "1.169.2_react@19.2.6_react-dom@19.2.6__react@19.2.6", "npm:@tanstack/router-plugin@^1.134.14": "1.167.35_@tanstack+react-router@1.169.2__react@19.2.6__react-dom@19.2.6___react@19.2.6_vite@7.3.3_react@19.2.6_react-dom@19.2.6__react@19.2.6", + "npm:@testing-library/react@^16.3.2": "16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.14_@types+react-dom@19.2.3__@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6", "npm:@types/react-dom@^19.1.9": "19.2.3_@types+react@19.2.14", "npm:@types/react@^19.1.16": "19.2.14", "npm:@vitejs/plugin-react@^5.0.4": "5.2.0_vite@7.3.3", @@ -41,6 +42,7 @@ "npm:hono-pino@*": "0.10.3_hono@4.12.18_pino@10.3.1", "npm:hono@*": "4.12.18", "npm:ioredis@*": "5.10.1", + "npm:jsdom@^30.0.1": "30.0.1", "npm:lucide-react@0.553": "0.553.0_react@19.2.6", "npm:pino-pretty@*": "13.1.3", "npm:pino@*": "10.3.1", @@ -92,6 +94,25 @@ } }, "npm": { + "@asamuzakjp/css-color@6.0.5": { + "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", + "dependencies": [ + "@csstools/css-calc", + "@csstools/css-color-parser", + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer", + "lru-cache@11.5.2" + ] + }, + "@asamuzakjp/dom-selector@8.3.2": { + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dependencies": [ + "bidi-js", + "css-tree", + "is-potential-custom-element-name", + "lru-cache@11.5.2" + ] + }, "@asteasolutions/zod-to-openapi@8.5.0_zod@4.4.3": { "integrity": "sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q==", "dependencies": [ @@ -691,7 +712,7 @@ "@babel/compat-data", "@babel/helper-validator-option", "browserslist", - "lru-cache", + "lru-cache@5.1.1", "semver@6.3.1" ] }, @@ -957,6 +978,50 @@ "os": ["win32"], "cpu": ["x64"] }, + "@bramus/specificity@2.4.2": { + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dependencies": [ + "css-tree" + ], + "bin": true + }, + "@csstools/color-helpers@6.1.0": { + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==" + }, + "@csstools/css-calc@3.3.0_@csstools+css-parser-algorithms@4.0.0__@csstools+css-tokenizer@4.0.0_@csstools+css-tokenizer@4.0.0": { + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dependencies": [ + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-color-parser@4.1.10_@csstools+css-parser-algorithms@4.0.0__@csstools+css-tokenizer@4.0.0_@csstools+css-tokenizer@4.0.0": { + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dependencies": [ + "@csstools/color-helpers", + "@csstools/css-calc", + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-parser-algorithms@4.0.0_@csstools+css-tokenizer@4.0.0": { + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dependencies": [ + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-syntax-patches-for-csstree@1.1.7_css-tree@3.2.1": { + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dependencies": [ + "css-tree" + ], + "optionalPeers": [ + "css-tree" + ] + }, + "@csstools/css-tokenizer@4.0.0": { + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==" + }, "@drizzle-team/brocli@0.10.2": { "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==" }, @@ -1365,6 +1430,9 @@ "os": ["win32"], "cpu": ["x64"] }, + "@exodus/bytes@1.15.1": { + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==" + }, "@floating-ui/core@1.7.5": { "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "dependencies": [ @@ -2429,12 +2497,43 @@ "integrity": "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==", "bin": true }, + "@testing-library/dom@10.4.1": { + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dependencies": [ + "@babel/code-frame", + "@babel/runtime", + "@types/aria-query", + "aria-query", + "dom-accessibility-api", + "lz-string", + "picocolors", + "pretty-format" + ] + }, + "@testing-library/react@16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.14_@types+react-dom@19.2.3__@types+react@19.2.14_react@19.2.6_react-dom@19.2.6__react@19.2.6": { + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dependencies": [ + "@babel/runtime", + "@testing-library/dom", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, "@tybys/wasm-util@0.10.2": { "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dependencies": [ "tslib" ] }, + "@types/aria-query@5.0.4": { + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" + }, "@types/babel__core@7.20.5": { "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dependencies": [ @@ -2497,6 +2596,12 @@ "event-target-shim" ] }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles@5.2.0": { + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, "ansis@4.2.0": { "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==" }, @@ -2507,6 +2612,12 @@ "picomatch@2.3.2" ] }, + "aria-query@5.3.0": { + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": [ + "dequal" + ] + }, "atomic-sleep@1.0.0": { "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, @@ -2571,6 +2682,12 @@ "zod@4.4.3" ] }, + "bidi-js@1.0.3": { + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dependencies": [ + "require-from-string" + ] + }, "binary-extensions@2.3.0": { "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" }, @@ -2658,11 +2775,26 @@ "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", "dependencies": [ "luxon" + ], + "deprecated": true + }, + "css-tree@3.2.1": { + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dependencies": [ + "mdn-data", + "source-map-js" ] }, "csstype@3.2.3": { "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, + "data-urls@7.0.0": { + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dependencies": [ + "whatwg-mimetype", + "whatwg-url@16.0.1" + ] + }, "dateformat@4.6.3": { "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" }, @@ -2672,18 +2804,27 @@ "ms" ] }, + "decimal.js@10.6.0": { + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==" + }, "defu@6.1.7": { "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==" }, "denque@2.1.0": { "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" }, + "dequal@2.0.3": { + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "detect-libc@2.1.2": { "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" }, "diff@8.0.4": { "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==" }, + "dom-accessibility-api@0.5.16": { + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, "dotenv-expand@13.0.0": { "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", "dependencies": [ @@ -2737,6 +2878,9 @@ "tapable" ] }, + "entities@8.0.0": { + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==" + }, "esbuild@0.18.20": { "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "optionalDependencies": [ @@ -2928,6 +3072,12 @@ "hono@4.12.18": { "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==" }, + "html-encoding-sniffer@6.0.0": { + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dependencies": [ + "@exodus/bytes" + ] + }, "ieee754@1.2.1": { "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, @@ -2963,6 +3113,9 @@ "is-number@7.0.0": { "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, + "is-potential-custom-element-name@1.0.1": { + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "isbot@5.1.40": { "integrity": "sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==" }, @@ -2979,6 +3132,32 @@ "js-tokens@4.0.0": { "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "jsdom@30.0.1": { + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dependencies": [ + "@asamuzakjp/css-color", + "@asamuzakjp/dom-selector", + "@bramus/specificity", + "@csstools/css-syntax-patches-for-csstree", + "@exodus/bytes", + "css-tree", + "data-urls", + "decimal.js", + "html-encoding-sniffer", + "is-potential-custom-element-name", + "lru-cache@11.5.2", + "parse5", + "saxes", + "symbol-tree", + "tough-cookie", + "undici", + "w3c-xmlserializer", + "webidl-conversions", + "whatwg-mimetype", + "whatwg-url@17.1.0", + "xml-name-validator" + ] + }, "jsesc@3.1.0": { "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "bin": true @@ -3070,6 +3249,9 @@ "lodash.isarguments@3.1.0": { "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, + "lru-cache@11.5.2": { + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==" + }, "lru-cache@5.1.1": { "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dependencies": [ @@ -3085,12 +3267,19 @@ "luxon@3.7.2": { "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==" }, + "lz-string@1.5.0": { + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": true + }, "magic-string@0.30.21": { "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dependencies": [ "@jridgewell/sourcemap-codec" ] }, + "mdn-data@2.27.1": { + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==" + }, "minimist@1.2.8": { "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, @@ -3161,6 +3350,12 @@ "yaml" ] }, + "parse5@8.0.1": { + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dependencies": [ + "entities" + ] + }, "path-expression-matcher@1.5.0": { "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==" }, @@ -3246,6 +3441,14 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "bin": true }, + "pretty-format@27.5.1": { + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": [ + "ansi-regex", + "ansi-styles", + "react-is" + ] + }, "process-warning@5.0.0": { "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" }, @@ -3259,6 +3462,9 @@ "once" ] }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, "quick-format-unescaped@4.0.4": { "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, @@ -3269,6 +3475,9 @@ "scheduler" ] }, + "react-is@17.0.2": { + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "react-refresh@0.18.0": { "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==" }, @@ -3303,6 +3512,9 @@ "redis-errors" ] }, + "require-from-string@2.0.2": { + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "reselect@5.1.1": { "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, @@ -3360,6 +3572,12 @@ "safe-stable-stringify@2.5.0": { "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" }, + "saxes@6.0.0": { + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": [ + "xmlchars" + ] + }, "scheduler@0.27.0": { "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" }, @@ -3453,6 +3671,9 @@ "standardwebhooks" ] }, + "symbol-tree@3.2.4": { + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "tagged-tag@1.0.0": { "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" }, @@ -3478,12 +3699,34 @@ "picomatch@4.0.4" ] }, + "tldts-core@7.4.10": { + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==" + }, + "tldts@7.4.10": { + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dependencies": [ + "tldts-core" + ], + "bin": true + }, "to-regex-range@5.0.1": { "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dependencies": [ "is-number" ] }, + "tough-cookie@6.0.2": { + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dependencies": [ + "tldts" + ] + }, + "tr46@6.0.0": { + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dependencies": [ + "punycode" + ] + }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, @@ -3508,6 +3751,9 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "bin": true }, + "undici@8.9.0": { + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==" + }, "unplugin@3.0.0": { "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", "dependencies": [ @@ -3546,15 +3792,49 @@ ], "bin": true }, + "w3c-xmlserializer@5.0.0": { + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": [ + "xml-name-validator" + ] + }, + "webidl-conversions@8.0.1": { + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==" + }, "webpack-virtual-modules@0.6.2": { "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" }, + "whatwg-mimetype@5.0.0": { + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==" + }, + "whatwg-url@16.0.1": { + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dependencies": [ + "@exodus/bytes", + "tr46", + "webidl-conversions" + ] + }, + "whatwg-url@17.1.0": { + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dependencies": [ + "@exodus/bytes", + "tr46", + "webidl-conversions" + ] + }, "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "xml-name-validator@5.0.0": { + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==" + }, "xml-naming@0.1.0": { "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==" }, + "xmlchars@2.2.0": { + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "yallist@3.1.1": { "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, @@ -3630,12 +3910,14 @@ "npm:@tanstack/react-router-devtools@^1.134.13", "npm:@tanstack/react-router@^1.134.13", "npm:@tanstack/router-plugin@^1.134.14", + "npm:@testing-library/react@^16.3.2", "npm:@types/react-dom@^19.1.9", "npm:@types/react@^19.1.16", "npm:@vitejs/plugin-react@^5.0.4", "npm:better-auth@^1.3.34", "npm:class-variance-authority@~0.7.1", "npm:clsx@^2.1.1", + "npm:jsdom@^30.0.1", "npm:lucide-react@0.553", "npm:react-dom@^19.1.1", "npm:react@^19.1.1", From 61beda74fa6c3e4096a838116df9a90897c76f5e Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:14:05 +0000 Subject: [PATCH 17/27] docs: fill remaining doc gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Note why apps/frontend/tsconfig.json coexists with deno.json (tsc --noEmit and Vite's tsconfigPaths both need it; deno.json's own compilerOptions serve deno check/test/LSP separately) and why apps/backend/deno.json sets module: NodeNext (matches the CJS shape of bullmq/ioredis/pino/@aws-sdk/*) — both flagged as inconsistencies in an earlier audit pass, neither is one. - packages/email-templates/deno.json was missing the test.include block every other package declares, despite having a real __tests__ dir already. - docs/DEPLOYMENT.md's env var reference was missing SERVER_URL and the Google/GitHub OAuth vars that .env.example documents, and had no pointer to docs/BATTERIES.md for what each var actually does. --- docs/DENO_WORKSPACE_SCOPE.md | 17 +++++++++++++++++ docs/DEPLOYMENT.md | 19 ++++++++++++++++--- packages/email-templates/deno.json | 5 ++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/DENO_WORKSPACE_SCOPE.md b/docs/DENO_WORKSPACE_SCOPE.md index f140927..ab68c12 100644 --- a/docs/DENO_WORKSPACE_SCOPE.md +++ b/docs/DENO_WORKSPACE_SCOPE.md @@ -106,12 +106,29 @@ and test config. No `@repo/*` entries — those resolve through workspace bare specifiers. No `nodeModulesDir` — that's a root-level concern per Deno docs. +Its `compilerOptions.module`/`moduleResolution` are set to `NodeNext`, unlike +every other workspace member. This isn't inherited boilerplate — the backend's +`imports` map pulls in several CJS-authored npm packages with dual-package +`exports` maps (`bullmq`, `ioredis`, `pino`, `@aws-sdk/*`). `NodeNext` +resolution is what correctly types their default exports and `require()`-style +interop; the plain resolution the other members use would misinfer or reject +some of those imports. + ### `apps/frontend/deno.json` Consumer member only. Has `@/*` import map for Vite/React imports. No `name` or `exports` — nothing imports the frontend. Uses `deno run -A npm:vite` for dev and build tasks. +It coexists with a separate `apps/frontend/tsconfig.json` rather than folding +everything into `compilerOptions` here — `tsconfig.json` is load-bearing for +`tsc --noEmit` (the actual `typecheck` task) and for Vite's `tsconfigPaths` +resolution (`vite.config.ts`'s `resolve.tsconfigPaths: true`), neither of +which reads `deno.json`. `deno.json`'s own `compilerOptions` (`jsx`, `lib`, +`types`) exist separately for `deno check`/`deno test`/the Deno LSP. They're +two different type-checkers with two different config files by necessity, not +an inconsistency to clean up. + ### `packages/shared/deno.json` ```json diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 4a66d94..eeed358 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -192,31 +192,44 @@ docker run -d \ ## Environment Variables Reference +Every var below is set in `apps/backend/.env` (see `.env.example` for the +full annotated template). See [docs/BATTERIES.md](BATTERIES.md) for what each +battery does once its vars are set, not just which vars exist. + ### Required ```bash DATABASE_URL=postgres://user:pass@host:5432/db BETTER_AUTH_SECRET=<32+ random characters> BETTER_AUTH_URL=https://api.yourdomain.com +SERVER_URL=https://api.yourdomain.com # usually the same as BETTER_AUTH_URL FRONTEND_URL=https://yourdomain.com ``` ### Optional ```bash -# Redis +# Redis — background jobs (BATTERIES.md#background-jobs), caching (#caching) REDIS_URL=redis://... -# File storage +# File storage — presigned S3/R2 uploads (BATTERIES.md#file-uploads) S3_ENDPOINT=https://... S3_BUCKET=uploads S3_REGION=auto S3_ACCESS_KEY_ID=... S3_SECRET_ACCESS_KEY=... -# Email +# Email — Resend (BATTERIES.md#email). Without this, sendEmail logs instead +# of sending, so auth flows still work end-to-end in dev with no setup. RESEND_API_KEY=re_... +# Social OAuth — activates only when both vars for a provider are set +# (BATTERIES.md#social-oauth-google--github) +GOOGLE_CLIENT_ID=... +GOOGLE_CLIENT_SECRET=... +GITHUB_CLIENT_ID=... +GITHUB_CLIENT_SECRET=... + # Tuning PORT=9999 LOG_LEVEL=info diff --git a/packages/email-templates/deno.json b/packages/email-templates/deno.json index 0e01e0f..e371c36 100644 --- a/packages/email-templates/deno.json +++ b/packages/email-templates/deno.json @@ -2,5 +2,8 @@ "name": "@repo/email-templates", "version": "0.1.0", "exports": "./src/index.ts", - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "test": { + "include": ["src/**/*.test.ts"] + } } From 3a34473e3e0f380cb5963054aed76aeeddeae1ec Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:19:48 +0000 Subject: [PATCH 18/27] fix: resolve 12 pre-existing require-await lint errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deno lint's require-await rule was failing repo-wide, unrelated to this branch's other work — likely surfaced by a Deno version difference from whenever these were last verified clean. - pingHandler, getUploadUrl/getDownloadUrl, and the rate-limit middleware factory return synchronously (or return an already -Promise-returning call directly) and never needed async in the first place — dropped it. - andThenAsync (packages/shared) genuinely needs to normalize a mixed sync/async return into one Promise — that's a correct use of async without an explicit await, but the lint rule can't tell the difference from a mistake. Added an explicit await on the async branch, which is behavior-preserving and satisfies the rule for real rather than suppressing it. - The test callbacks passed to andThenAsync (in both apps/backend's and packages/shared's result.test.ts) only needed to return a Promise, not be declared async — switched them to `() => Promise.resolve(...)`. --- apps/backend/src/lib/__tests__/result.test.ts | 16 +++++++++++----- apps/backend/src/lib/rate-limit.ts | 2 +- apps/backend/src/lib/storage.ts | 4 ++-- apps/backend/src/modules/health/handlers.ts | 2 +- packages/shared/src/__tests__/result.test.ts | 8 ++++---- packages/shared/src/result.ts | 2 +- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/apps/backend/src/lib/__tests__/result.test.ts b/apps/backend/src/lib/__tests__/result.test.ts index 37536c1..e4430ff 100644 --- a/apps/backend/src/lib/__tests__/result.test.ts +++ b/apps/backend/src/lib/__tests__/result.test.ts @@ -122,26 +122,32 @@ describe("andThen", () => { describe("andThenAsync", () => { it("chains the async function when Ok", async () => { - const result = await andThenAsync(ok(5), async (n) => ok(n * 2)); + const result = await andThenAsync(ok(5), (n) => Promise.resolve(ok(n * 2))); expect(result).toEqual(ok(10)); }); it("short-circuits when the input is Err", async () => { const original = err("input failed"); - const result = await andThenAsync(original, async (n: number) => ok(n)); + const result = await andThenAsync( + original, + (n: number) => Promise.resolve(ok(n)), + ); expect(result).toEqual(original); }); it("propagates Err returned by the async function", async () => { - const result = await andThenAsync(ok("x"), async () => err("async failed")); + const result = await andThenAsync( + ok("x"), + () => Promise.resolve(err("async failed")), + ); expect(result).toEqual(err("async failed")); }); it("does not call fn on Err input", async () => { let called = false; - await andThenAsync(err("e"), async () => { + await andThenAsync(err("e"), () => { called = true; - return ok(0); + return Promise.resolve(ok(0)); }); expect(called).toBe(false); }); diff --git a/apps/backend/src/lib/rate-limit.ts b/apps/backend/src/lib/rate-limit.ts index dba8c0c..507d0d3 100644 --- a/apps/backend/src/lib/rate-limit.ts +++ b/apps/backend/src/lib/rate-limit.ts @@ -38,7 +38,7 @@ export function rateLimit(options: RateLimitOptions = {}) { ), } = options; - return async (c: Context, next: Next) => { + return (c: Context, next: Next) => { const key = keyGenerator(c); const now = Date.now(); const entry = store.get(key); diff --git a/apps/backend/src/lib/storage.ts b/apps/backend/src/lib/storage.ts index a46379b..0d74abb 100644 --- a/apps/backend/src/lib/storage.ts +++ b/apps/backend/src/lib/storage.ts @@ -35,7 +35,7 @@ interface DownloadOptions { /** * Generate presigned URL for uploading */ -export async function getUploadUrl({ +export function getUploadUrl({ key, contentType, expiresIn = 3600, @@ -51,7 +51,7 @@ export async function getUploadUrl({ /** * Generate presigned URL for downloading */ -export async function getDownloadUrl({ +export function getDownloadUrl({ key, expiresIn = 3600, }: DownloadOptions): Promise { diff --git a/apps/backend/src/modules/health/handlers.ts b/apps/backend/src/modules/health/handlers.ts index a1c066f..59f6403 100644 --- a/apps/backend/src/modules/health/handlers.ts +++ b/apps/backend/src/modules/health/handlers.ts @@ -28,6 +28,6 @@ export const healthCheckHandler: AppRouteHandler = async ( return c.json(success(data), databaseUp ? OK : SERVICE_UNAVAILABLE); }; -export const pingHandler: AppRouteHandler = async (c) => { +export const pingHandler: AppRouteHandler = (c) => { return c.json(success({ message: "pong" as const }), OK); }; diff --git a/packages/shared/src/__tests__/result.test.ts b/packages/shared/src/__tests__/result.test.ts index cb4a80c..7977c30 100644 --- a/packages/shared/src/__tests__/result.test.ts +++ b/packages/shared/src/__tests__/result.test.ts @@ -138,15 +138,15 @@ describe("andThen", () => { describe("andThenAsync", () => { it("chains an async function on an Ok result", async () => { - const result = await andThenAsync(ok(5), async (n) => ok(n * 2)); + const result = await andThenAsync(ok(5), (n) => Promise.resolve(ok(n * 2))); expect(result).toEqual(ok(10)); }); it("short-circuits on an Err result without calling the function", async () => { let called = false; - const fn = async () => { + const fn = () => { called = true; - return ok(0); + return Promise.resolve(ok(0)); }; const result = await andThenAsync(err("already failed"), fn); expect(result).toEqual(err("already failed")); @@ -156,7 +156,7 @@ describe("andThenAsync", () => { it("propagates an async Err from the chained function", async () => { const result = await andThenAsync( ok("user"), - async (_) => err({ type: "CONFLICT", detail: "duplicate" }), + (_) => Promise.resolve(err({ type: "CONFLICT", detail: "duplicate" })), ); expect(result).toEqual(err({ type: "CONFLICT", detail: "duplicate" })); }); diff --git a/packages/shared/src/result.ts b/packages/shared/src/result.ts index 7038c53..6e8b81d 100644 --- a/packages/shared/src/result.ts +++ b/packages/shared/src/result.ts @@ -136,7 +136,7 @@ export const andThen = ( export const andThenAsync = async ( result: Result, fn: (value: T) => Promise>, -): Promise> => (result.ok ? fn(result.value) : result); +): Promise> => (result.ok ? await fn(result.value) : result); /** * Exhaustively handle both branches in a single expression. From 1d18abef0ad5a46b6ec2afdc8938354a269cf6f6 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:55:09 +0000 Subject: [PATCH 19/27] fix(auth): disable better-auth rate limiting in test environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit better-auth applies a built-in 3-requests-per-10s rule to /sign-in, /sign-up, /change-password, and /change-email regardless of the configured max. With storage: "secondary-storage" and no Redis configured, that storage layer silently no-ops (optional chaining on an undefined secondaryStorage), so the limit was never actually enforced anywhere except environments with Redis — including CI, which provisions Redis and legitimately signs up/in many test users in quick succession across handlers.test.ts, tripping the limit and returning 429s where 200/403/404 were expected. Disabling rate limiting under NODE_ENV=test keeps the production protection intact while letting the integration suite run at the speed it always has locally (where this was silently never enforced). --- apps/backend/src/lib/auth.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/lib/auth.ts b/apps/backend/src/lib/auth.ts index 037525f..e82892f 100644 --- a/apps/backend/src/lib/auth.ts +++ b/apps/backend/src/lib/auth.ts @@ -121,7 +121,14 @@ export const auth = betterAuth({ }, secondaryStorage, rateLimit: { - enabled: true, + // better-auth applies a built-in 3-requests-per-10s rule to + // /sign-in, /sign-up, /change-password, /change-email regardless of the + // max below. With storage: "secondary-storage" and no Redis configured, + // that storage layer silently no-ops (optional chaining on an undefined + // secondaryStorage), so the limit was never actually enforced outside + // of environments with Redis — including the integration test suite, + // which legitimately signs up/in many users in quick succession. + enabled: env.NODE_ENV !== "test", window: 10, max: 100, storage: "secondary-storage", From 710b8df6c42879de5e3ab6ce72bd6ca52dee4cbf Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:55:21 +0000 Subject: [PATCH 20/27] fix(ci): isolate frontend tests from backend and bump Deno for jsdom compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two independent CI failures, both real: 1. jsdom@30 (via its undici@8.9.0 dependency) calls webidl.util.markAsUncloneable, a Node-compat API Deno 2.7.14 doesn't provide — reproduced locally by installing that exact version. Deno 2.8.0 has it; bumped CI's pin to 2.9.0. This only affects test-tooling — production images never run `deno test`, so the Dockerfiles' 2.7.14 pin is unaffected and unchanged. 2. Running `deno test -A` from the root swept up apps/frontend's jsdom-based tests into the same process as the backend's. The webidl crash above was an uncaught rejection that corrupted the shared fetch/undici runtime mid-run, which is why unrelated backend handlers.test.ts assertions failed in the same CI run — they share process state with a frontend test suite that was never designed to run alongside them. Split into two steps: "Test" now explicitly excludes apps/frontend, and a new "Frontend test" step runs it via the existing deno task test, isolated in its own process. --- .github/workflows/ci.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2393a79..50471b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,11 @@ jobs: - uses: denoland/setup-deno@v2 with: - deno-version: "2.7.14" + # 2.9.0, not 2.7.14 (what the Dockerfiles pin) — the Node-compat + # layer needed for jsdom's undici dependency to work isn't present + # until 2.8.0. Test-tooling only; production images are unaffected + # since they never run `deno test`. + deno-version: "2.9.0" - name: Cache dependencies run: deno cache apps/backend/src/index.ts apps/backend/src/jobs/worker.ts @@ -75,7 +79,11 @@ jobs: DATABASE_URL: postgres://test:test@localhost:5432/test - name: Test - run: deno test -A + # Excludes apps/frontend deliberately — its jsdom-based tests run in + # a separate process below. A frontend DOM-testing crash previously + # took down unrelated backend test results by corrupting the shared + # fetch/undici runtime when run in the same `deno test` invocation. + run: deno test -A --ignore=apps/frontend env: NODE_ENV: test DATABASE_URL: postgres://test:test@localhost:5432/test @@ -84,3 +92,7 @@ jobs: BETTER_AUTH_URL: http://localhost:9999 SERVER_URL: http://localhost:9999 FRONTEND_URL: http://localhost:3000 + + - name: Frontend test + working-directory: apps/frontend + run: deno task test From 232c93f627796e91ec766bdf933ff1ad08d3be95 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 10:59:37 +0000 Subject: [PATCH 21/27] docs: document the CI rate-limit and jsdom/Deno-version bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were non-obvious enough to burn real debugging time finding — worth writing down so the next person (or future me) doesn't re-derive them from a confusing CI failure: - apps/backend/docs/DECISIONS.md: why better-auth's rate limiter was silently inert everywhere except Redis-backed environments, and why disabling it under NODE_ENV=test is the fix rather than a workaround. - docs/DENO_WORKSPACE_SCOPE.md: the jsdom/undici/Deno-version incompatibility (with the exact repro), and why apps/frontend's tests need to run in their own `deno test` process rather than getting swept into a root-level `deno test -A`. --- apps/backend/docs/DECISIONS.md | 44 ++++++++++++++++++++++++++++++++++ docs/DENO_WORKSPACE_SCOPE.md | 34 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/apps/backend/docs/DECISIONS.md b/apps/backend/docs/DECISIONS.md index b19c0a8..ce050e5 100644 --- a/apps/backend/docs/DECISIONS.md +++ b/apps/backend/docs/DECISIONS.md @@ -266,6 +266,50 @@ See the [Better Auth database docs](https://www.better-auth.com/docs/concepts/database) for the full table reference and plugin schema additions. +**Gotcha: rate limiting silently no-ops without Redis** + +better-auth ships a built-in rate-limit rule — 3 requests per 10 seconds on +`/sign-in`, `/sign-up`, `/change-password`, `/change-email` — that overrides +whatever `max` you configure at the top level. This project points +`rateLimit.storage` at `"secondary-storage"`: + +```typescript +rateLimit: { + enabled: env.NODE_ENV !== "test", + window: 10, + max: 100, + storage: "secondary-storage", +}, +``` + +`"secondary-storage"` means better-auth calls `ctx.options.secondaryStorage` +to persist rate-limit counters. In this codebase, `secondaryStorage` is only +defined when `REDIS_URL` is set: + +```typescript +const secondaryStorage = redis + ? { get: ..., set: ..., delete: ... } + : undefined; +``` + +Without Redis, better-auth's own rate-limit code calls +`ctx.options.secondaryStorage?.get(key)` — optional chaining on `undefined` +resolves to `undefined` and short-circuits. `get()` always reports "no prior +request," `set()` is a silent no-op. The 3-per-10s rule is configured but +never actually enforced. + +I found this the hard way: `apps/backend/src/modules/users/__tests__/handlers.test.ts` +passed locally for months because nobody runs the test suite with `REDIS_URL` +set by hand. CI provisions Redis unconditionally, so the rule went from +inert to real the moment it ran there — and a test suite that legitimately +signs up a dozen users in a few seconds blew through it instantly, turning +into 429s that the tests then read as 401s (no session cookie, because the +sign-up/sign-in call that was supposed to produce one got rate-limited +instead). `enabled: env.NODE_ENV !== "test"` keeps the protection in every +real environment and turns it off for the one environment where Redis being +present or absent was only ever a test-infra accident, never a security +decision. + **Trade-offs**: - Newer library, smaller community than Auth.js/Lucia diff --git a/docs/DENO_WORKSPACE_SCOPE.md b/docs/DENO_WORKSPACE_SCOPE.md index ab68c12..894fcc8 100644 --- a/docs/DENO_WORKSPACE_SCOPE.md +++ b/docs/DENO_WORKSPACE_SCOPE.md @@ -231,3 +231,37 @@ React, etc.). The Deno workspace and pnpm workspace coexist — Deno handles the backend and packages, pnpm handles the frontend's npm deps. The `package.json` has been cleaned up: no `@repo/*` workspace deps (those are resolved via Deno workspace now). + +### 6. Frontend tests need Deno ≥ 2.8.0, and they run in their own process + +`apps/frontend`'s tests use `npm:jsdom` under `deno test` +(`apps/frontend/src/test-setup.ts` installs the DOM globals). `jsdom@30` +pulls in `undici@8.9.0`, whose `CacheStorage` constructor calls +`webidl.util.markAsUncloneable` — a Node-compat function Deno doesn't +provide until 2.8.0. On 2.7.14 (what the Dockerfiles pin for +production), it throws: + +``` +error: (in promise) TypeError: webidl.util.markAsUncloneable is not a function + at new CacheStorage (.../undici@8.9.0/node_modules/undici/lib/web/cache/cachestorage.js:20:17) + at Object. (.../jsdom@30.0.1/node_modules/jsdom/lib/api.js:12:33) +``` + +I found this by installing 2.7.14 with `mise` and reproducing it directly, +then bisecting up to 2.8.0 where it's fixed. CI pins `2.9.0` — a full +minor ahead of what ships in the Docker images — because test-tooling +correctness and production-runtime parity are different concerns here: the +Dockerfiles never run `deno test`, so they don't need the newer Deno, and +floating CI's version just to chase this fix would reintroduce the exact +drift `deno-version` pinning was meant to prevent. + +The second half of this: don't run `deno test -A` unscoped from the repo +root once `apps/frontend` has tests. Deno's workspace auto-discovery will +happily sweep frontend and backend test files into one process, and an +uncaught rejection in one (like the jsdom crash above, before it was fixed) +corrupts shared runtime state — like the global `fetch`/`undici` +implementation — for the other. I watched it take out unrelated +`handlers.test.ts` assertions in the same CI run, with no code connecting +the two. CI runs `deno test -A --ignore=apps/frontend` and a separate +`deno task test` inside `apps/frontend` as two different steps — two +processes, two failure domains. From 71dea7eaa8b4e1ac13e1c6143415e7800eea53a1 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 12:02:33 +0000 Subject: [PATCH 22/27] fix(deploy): run migrations from a deno image, not the compiled backend image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apps/backend/Dockerfile compiles to a standalone binary on bare alpine:3.20 — no deno executable, no source tree. deploy.yml's migration step ran `docker compose run --rm backend deno run ... migrate.ts` against that image, which would fail on the first deploy that ships a schema change. Verified by inspecting the built image and reproducing the failure mode locally. Adds a `migrate` service to docker-compose.prod.yml using the same Deno base image apps/backend/Dockerfile builds from, mounting the already-checked-out repo on the VPS instead of baking source into an image. profiles: ["tools"] keeps it from ever starting via `docker compose up`. Verified end-to-end locally: `docker compose -f docker-compose.prod.yml run --rm migrate` against a real Postgres applies both migrations and reports "Migrations complete." Also adds .env.production to .gitignore — docker-compose.prod.yml's own header comment already claimed it's "never committed to git," but nothing actually enforced that. --- .github/workflows/deploy.yml | 6 ++++-- .gitignore | 1 + docker-compose.prod.yml | 26 ++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eaf422e..3d60650 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -133,8 +133,10 @@ jobs: docker compose --env-file .env.production -f docker-compose.prod.yml ps - # Run database migrations - IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml run --rm backend deno run --allow-env --allow-net --allow-read --allow-sys apps/backend/src/db/migrate.ts + # Run database migrations (deno image with the repo checkout + # mounted — the compiled backend image has no deno executable + # or source tree to run migrate.ts from) + IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml run --rm migrate # Recreate backend and frontend IMAGE_TAG=latest docker compose --env-file .env.production -f docker-compose.prod.yml up -d backend frontend diff --git a/.gitignore b/.gitignore index 87e8cad..dfe578c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ pnpm-lock.yaml .env .env.local .env.*.local +.env.production # IDE # Ignore personal VS Code files; track shared workspace config. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 0e6ca6f..d62d737 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -53,6 +53,32 @@ services: redis: condition: service_healthy + # Runs migrations from the repo checkout on the VPS. The backend image + # (orcta-backend) is a compiled standalone binary on bare alpine — no deno + # executable, no source tree — so it can't run migrate.ts itself. This + # service uses the same Deno base image the backend Dockerfile builds + # with, mounting the deploy directory instead of baking source into an + # image. Opt-in only (profiles) — never starts via `docker compose up`. + migrate: + image: denoland/deno:debian-2.7.14 + working_dir: /app + volumes: + - .:/app + env_file: .env.production + depends_on: + db: + condition: service_healthy + command: [ + "deno", + "run", + "--allow-env", + "--allow-net", + "--allow-read", + "--allow-sys", + "apps/backend/src/db/migrate.ts", + ] + profiles: ["tools"] + volumes: postgres_data: redis_data: From 60b7c4beda2941cb377f31eb0bf28f168112bd38 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 12:02:46 +0000 Subject: [PATCH 23/27] fix(db): map legacy 'user' role to 'buyer' in the enum migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 0001 drops the old user_role enum ('user'|'admin') and casts the column into the new one ('buyer'|'seller'|'admin') via USING "role"::"public"."user_role". Any existing row with role = 'user' has no matching value in the new enum — the cast aborts and the migration fails partway through. Verified by seeding a row with role = 'user' against a fresh database, running the unpatched migration (fails: invalid input value for enum user_role), then the patched one (succeeds, row becomes 'buyer' — matching the column's own new default). Only matters for a database that already had migration 0000 applied before this one exists; harmless no-op on a fresh install. --- packages/db/migrations/0001_curious_fantastic_four.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/db/migrations/0001_curious_fantastic_four.sql b/packages/db/migrations/0001_curious_fantastic_four.sql index 7643db2..642a21d 100644 --- a/packages/db/migrations/0001_curious_fantastic_four.sql +++ b/packages/db/migrations/0001_curious_fantastic_four.sql @@ -1,5 +1,6 @@ ALTER TABLE "users" ALTER COLUMN "role" SET DATA TYPE text;--> statement-breakpoint ALTER TABLE "users" ALTER COLUMN "role" SET DEFAULT 'buyer'::text;--> statement-breakpoint +UPDATE "users" SET "role" = 'buyer' WHERE "role" = 'user';--> statement-breakpoint DROP TYPE "public"."user_role";--> statement-breakpoint CREATE TYPE "public"."user_role" AS ENUM('buyer', 'seller', 'admin');--> statement-breakpoint ALTER TABLE "users" ALTER COLUMN "role" SET DEFAULT 'buyer'::"public"."user_role";--> statement-breakpoint From 0baec47f1e2552be0ec5cbbb8021f75ddc7c1927 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 12:03:02 +0000 Subject: [PATCH 24/27] fix(docker): make frontend image installs reproducible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend Dockerfile copied only apps/frontend's own deno.json and package.json before running deno install — no root deno.json, no lockfile, no sibling members. Deno correctly treats that as a standalone project, not the workspace it actually belongs to, and silently re-resolves everything: verified locally that better-auth and @better-fetch/fetch drifted to newer versions than the checked-in deno.lock pins, on every single build. Copying just deno.lock alongside isn't enough either — same drift, confirmed the same way. The lockfile only stays exact once the full workspace skeleton is present (root deno.json + deno.lock + package.json, plus every member's own deno.json, matching what apps/backend/Dockerfile already does correctly). Verified by replicating the exact copied file layout in a plain directory (no Docker needed for this part) and diffing deno.lock before/after install — identical only with the full skeleton in place. Running deno install/build scoped to apps/frontend (rather than the whole workspace) keeps the frontend image from pulling in backend-only npm deps it doesn't need. Also updates the final stage's COPY path (dist/ now lives at apps/frontend/dist/, not the repo root, since the build no longer flattens frontend's files into /app directly) — the full install→build pipeline was verified outside Docker to confirm this new path is correct. --- apps/frontend/Dockerfile | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile index 3f6aa50..74a3e40 100644 --- a/apps/frontend/Dockerfile +++ b/apps/frontend/Dockerfile @@ -3,18 +3,28 @@ FROM denoland/deno:debian-2.7.14 AS builder WORKDIR /app -# Layer 1: Config files (changes infrequently — preserves dep cache) -COPY apps/frontend/deno.json apps/frontend/package.json ./ -RUN deno install +# Layer 1: Workspace skeleton (changes infrequently — preserves dep cache). +# Deno requires every deno.json under the workspace root to be a declared +# member (see docs/DENO_WORKSPACE_SCOPE.md), and `deno install` needs the +# full workspace + lockfile present to resolve pinned versions instead of +# silently re-resolving them — copying just apps/frontend's own files let +# npm deps drift on every build. +COPY deno.json deno.lock package.json ./ +COPY apps/backend/deno.json ./apps/backend/ +COPY apps/frontend/deno.json apps/frontend/package.json ./apps/frontend/ +COPY packages/shared/deno.json ./packages/shared/ +COPY packages/db/deno.json ./packages/db/ +COPY packages/email-templates/deno.json ./packages/email-templates/ +RUN cd apps/frontend && deno install -# Layer 2: Source -COPY apps/frontend/ ./ -RUN deno task build +# Layer 2: Frontend source +COPY apps/frontend/ ./apps/frontend/ +RUN cd apps/frontend && deno task build # ─── Run (Caddy serves SPA + proxies /api/* to backend) ────────────── FROM caddy:alpine -COPY --from=builder /app/dist/ /srv/ +COPY --from=builder /app/apps/frontend/dist/ /srv/ COPY apps/frontend/Caddyfile /etc/caddy/Caddyfile EXPOSE 80 From 545f4c74e82c91a36720422740019909b3746a07 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 12:03:12 +0000 Subject: [PATCH 25/27] docs: fix stale port and file-layout references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README.md, CONTRIBUTING.md: frontend dev server moved to :3000 a while back; both still said :5173. - README.md, scripts/new-module.sh: the scaffolder generates `.usecases.ts` at the module root, not a `usecases/` directory — the module table and the scaffolder's own success message both still described the old layout. --- CONTRIBUTING.md | 4 ++-- README.md | 2 +- scripts/new-module.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45b0724..91f070e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,11 +27,11 @@ deno task dev # Backend on :9999 In a separate terminal: ```bash -deno task dev:frontend # Frontend on :5173 via Vite +deno task dev:frontend # Frontend on :3000 via Vite ``` Backend on [localhost:9999/docs](http://localhost:9999/docs). Frontend on -[localhost:5173](http://localhost:5173). Run both together or independently. +[localhost:3000](http://localhost:3000). Run both together or independently. --- diff --git a/README.md b/README.md index 4da94e2..65b1d03 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ This scaffolds a complete module at `apps/backend/src/modules/posts/`: | `handlers.ts` | HTTP handlers — reads input, calls repo, maps Result to response | | `posts.repository.ts` | Data access — uses `tryInfra`, returns `Result`, never throws | | `posts.errors.ts` | Typed domain error variants (`PostNotFound`, etc.) | -| `usecases/` | Pure business logic — no DB, no async, fully unit-testable | +| `posts.usecases.ts` | Pure business logic — no DB, no async, fully unit-testable | | `__tests__/` | Integration test stubs | | `index.ts` | Wires routes to handlers, exports the router | diff --git a/scripts/new-module.sh b/scripts/new-module.sh index 240dd58..b8739b0 100755 --- a/scripts/new-module.sh +++ b/scripts/new-module.sh @@ -130,7 +130,7 @@ cat > "${MODULE_DIR}/${MODULE}.usecases.ts" << EOF // return ok(input); // } EOF -success "usecases/${MODULE}.usecases.ts" +success "${MODULE}.usecases.ts" # ── handlers.ts ──────────────────────────────────────────────────────────────── cat > "${MODULE_DIR}/handlers.ts" << EOF From 1d5456320a75c1a6f6f029510746fb45121398cd Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 12:03:29 +0000 Subject: [PATCH 26/27] fix(scripts): use portable grep for Deno version detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grep -oP relies on GNU grep's PCRE flag, unavailable on macOS/BSD grep by default — setup.sh would fail parsing the Deno major version on a stock macOS machine. grep -oE '[0-9]+' is POSIX-portable and extracts the same first match. --- scripts/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index 72daf6b..1e8d7c1 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -23,7 +23,7 @@ if ! command -v deno >/dev/null 2>&1; then error "Deno not found. Install v2+ from https://deno.com" exit 1 fi -DENO_MAJOR=$(deno --version | head -1 | grep -oP '\d+' | head -1) +DENO_MAJOR=$(deno --version | head -1 | grep -oE '[0-9]+' | head -1) if [[ "$DENO_MAJOR" -lt 2 ]]; then error "Deno v${DENO_MAJOR} found — v2 or higher required." exit 1 From af21060e760d900d493fe28b8495481038d9e493 Mon Sep 17 00:00:00 2001 From: Bernard Katamanso Date: Mon, 3 Aug 2026 12:12:34 +0000 Subject: [PATCH 27/27] ci: run backend and frontend checks as parallel jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test and Frontend test were sequential steps in one job — never actually concurrent, just visually separated. Splits into two jobs with no needs: dependency between them, so GitHub Actions schedules them on separate runners simultaneously instead of one after another. backend keeps the postgres/redis services (needed for migrations and the test suite); frontend doesn't need either, so it no longer waits on service containers it never touches. Lint now excludes apps/frontend explicitly, since Biome (checked in the frontend job) is the linter of record there — deno lint was already implicitly scanning those files redundantly. No branch protection rule references the old job name ("check"), so renaming to backend/frontend doesn't affect required-check gating. --- .github/workflows/ci.yml | 54 +++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50471b4..d657f7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [master] jobs: - check: + backend: runs-on: ubuntu-latest services: @@ -53,26 +53,14 @@ jobs: run: deno fmt --check - name: Lint - run: deno lint + # Excludes apps/frontend — Biome is the linter of record for it, + # checked in the frontend job. Avoids double-linting the same files + # with two different rulesets. + run: deno lint --ignore=apps/frontend - name: Type check run: deno check apps/backend/src/index.ts - - name: Install frontend dependencies - working-directory: apps/frontend - run: deno install - - - name: Frontend lint - run: deno run -A npm:@biomejs/biome@2.3.7 ci apps/frontend - - - name: Frontend type check - working-directory: apps/frontend - run: deno task typecheck - - - name: Frontend build - working-directory: apps/frontend - run: deno task build - - name: Run migrations run: deno run --allow-env --allow-net --allow-read --allow-sys apps/backend/src/db/migrate.ts env: @@ -80,9 +68,10 @@ jobs: - name: Test # Excludes apps/frontend deliberately — its jsdom-based tests run in - # a separate process below. A frontend DOM-testing crash previously - # took down unrelated backend test results by corrupting the shared - # fetch/undici runtime when run in the same `deno test` invocation. + # the frontend job, in a separate process. A frontend DOM-testing + # crash previously took down unrelated backend test results by + # corrupting the shared fetch/undici runtime when run in the same + # `deno test` invocation. run: deno test -A --ignore=apps/frontend env: NODE_ENV: test @@ -93,6 +82,31 @@ jobs: SERVER_URL: http://localhost:9999 FRONTEND_URL: http://localhost:3000 + frontend: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: "2.9.0" + + - name: Install frontend dependencies + working-directory: apps/frontend + run: deno install + + - name: Frontend lint + run: deno run -A npm:@biomejs/biome@2.3.7 ci apps/frontend + + - name: Frontend type check + working-directory: apps/frontend + run: deno task typecheck + + - name: Frontend build + working-directory: apps/frontend + run: deno task build + - name: Frontend test working-directory: apps/frontend run: deno task test