From 787fdf34486b9d1c3dec351597cbda6ef30d1e06 Mon Sep 17 00:00:00 2001 From: Jeff7842 Date: Thu, 1 Oct 2026 21:36:13 +0300 Subject: [PATCH 1/6] feat: refactor impersonation_sessions model for improved clarity and structure --- stackable-app/prisma/schema.prisma | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/stackable-app/prisma/schema.prisma b/stackable-app/prisma/schema.prisma index f7821b9..e8d6b50 100644 --- a/stackable-app/prisma/schema.prisma +++ b/stackable-app/prisma/schema.prisma @@ -592,7 +592,6 @@ model user_sessions { revoked_at DateTime? @db.Timestamptz(6) schools schools @relation(fields: [school_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_sessions_school_fk") users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_sessions_user_fk") - impersonation_sessions impersonation_sessions[] @@index([user_id], map: "idx_user_sessions_user_id") } @@ -680,21 +679,23 @@ model audit_logs { /// This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info. model impersonation_sessions { - id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid - actor_user_id String @db.Uuid - actor_session_id String? @db.Uuid - target_user_id String @db.Uuid + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + actor_user_id String @db.Uuid + // NOT a FK: holds user_sessions.id (a uuid) on the legacy path, or ba_session.id + // (a non-uuid random token) under Better Auth — see guard.ts's resolveContext(). + // Plain text so either shape fits. + actor_session_id String? + target_user_id String @db.Uuid // Only the hash of the random cookie token is stored. token_hash String reason String - expires_at DateTime @db.Timestamptz(6) - ended_at DateTime? @db.Timestamptz(6) - created_at DateTime @default(now()) @db.Timestamptz(6) - ip_address String? @db.Inet + expires_at DateTime @db.Timestamptz(6) + ended_at DateTime? @db.Timestamptz(6) + created_at DateTime @default(now()) @db.Timestamptz(6) + ip_address String? @db.Inet user_agent String? - actor users @relation("ImpersonationActor", fields: [actor_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "impersonation_sessions_actor_user_fk") - actor_session user_sessions? @relation(fields: [actor_session_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "impersonation_sessions_actor_session_fk") - target users @relation("ImpersonationTarget", fields: [target_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "impersonation_sessions_target_user_fk") + actor users @relation("ImpersonationActor", fields: [actor_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "impersonation_sessions_actor_user_fk") + target users @relation("ImpersonationTarget", fields: [target_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "impersonation_sessions_target_user_fk") @@unique([token_hash], map: "uq_impersonation_sessions_token_hash") @@index([actor_user_id], map: "idx_impersonation_sessions_actor_user_id") From 20e6b205ee5fae112c12c81ad48d5cbc81f921f5 Mon Sep 17 00:00:00 2001 From: Jeff7842 Date: Mon, 5 Oct 2026 03:53:02 +0300 Subject: [PATCH 2/6] feat: D1-D2 foundation, staff portal, demo mode and service scaffolds - harden auth: fail-closed rate limits, no secret fallbacks, env schema, error requestId - pnpm workspace, Vitest, check runner, CI workflow - roles finance/secretary/driver/dept-head, /staff portal, role-filtered nav, Soon tags - /demo with session-only fixtures, health/ready endpoints, error and loading boundaries - TS service-kit, contracts, identity skeleton; Go go-kit and payments (webhooks, fees WIP) - DB migrations written (not yet applied), docs set and ADRs Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 38 + .gitignore | 7 + go.work | 6 + go.work.sum | 4 + package.json | 14 + .../events/notification.requested.v1.json | 16 + packages/contracts/openapi/identity.yaml | 51 + packages/contracts/package.json | 17 + packages/contracts/src/index.ts | 28 + packages/contracts/tsconfig.json | 12 + packages/go-kit/errors.go | 87 + packages/go-kit/go.mod | 15 + packages/go-kit/go.sum | 30 + packages/go-kit/health.go | 75 + packages/go-kit/http.go | 36 + packages/go-kit/kit_test.go | 371 ++ packages/go-kit/logger.go | 66 + packages/go-kit/requestid.go | 54 + packages/go-kit/school.go | 34 + packages/go-kit/server.go | 58 + packages/go-kit/servicetoken.go | 91 + packages/service-kit/README.md | 4 + packages/service-kit/package.json | 23 + packages/service-kit/src/errors.test.ts | 39 + packages/service-kit/src/errors.ts | 55 + packages/service-kit/src/index.ts | 7 + packages/service-kit/src/logger.ts | 20 + packages/service-kit/src/service.test.ts | 99 + packages/service-kit/src/service.ts | 106 + packages/service-kit/src/token.test.ts | 48 + packages/service-kit/src/token.ts | 52 + packages/service-kit/src/with-school.test.ts | 31 + packages/service-kit/src/with-school.ts | 19 + packages/service-kit/tsconfig.json | 12 + .../pnpm-lock.yaml => pnpm-lock.yaml | 5718 +++++++++-------- pnpm-workspace.yaml | 10 + services/academics/package.json | 24 + services/academics/src/lib/auth.ts | 48 + services/academics/src/lib/validate.ts | 110 + services/academics/src/placeholder.ts | 1 + services/academics/tsconfig.json | 12 + services/ai/package.json | 24 + services/ai/src/placeholder.ts | 1 + services/ai/tsconfig.json | 12 + services/communication/go.mod | 11 + services/communication/go.sum | 30 + .../internal/notifications/callbacks.go | 112 + .../internal/notifications/handler.go | 166 + .../internal/notifications/memory_store.go | 322 + .../internal/notifications/pg_store.go | 372 ++ .../internal/notifications/service.go | 347 + .../internal/notifications/store.go | 32 + .../internal/notifications/types.go | 138 + .../migrations/00001_create_communication.sql | 141 + services/documents/go.mod | 11 + .../documents/internal/documents/cachekey.go | 41 + .../documents/internal/documents/content.go | 181 + .../documents/internal/documents/errors.go | 34 + services/documents/internal/documents/id.go | 20 + .../documents/internal/documents/money.go | 33 + services/documents/internal/documents/pdf.go | 133 + .../documents/internal/documents/renderer.go | 16 + .../documents/internal/documents/types.go | 93 + services/documents/internal/documents/xlsx.go | 67 + services/identity/package.json | 24 + services/identity/src/app.test.ts | 53 + services/identity/src/app.ts | 22 + services/identity/src/main.ts | 19 + services/identity/tsconfig.json | 12 + services/payments/README.md | 36 + services/payments/cmd/payments/main.go | 68 + services/payments/go.mod | 20 + services/payments/go.sum | 30 + services/payments/internal/fees/store.go | 58 + services/payments/internal/fees/types.go | 241 + services/payments/internal/provider/fake.go | 69 + services/payments/internal/provider/mpesa.go | 325 + .../payments/internal/provider/provider.go | 70 + .../internal/provider/provider_test.go | 196 + .../payments/internal/webhooks/handler.go | 37 + .../payments/internal/webhooks/pg_store.go | 33 + .../payments/internal/webhooks/service.go | 72 + services/payments/internal/webhooks/store.go | 50 + .../internal/webhooks/webhooks_test.go | 91 + .../00001_create_webhook_events.sql | 16 + services/payments/queries/webhook_events.sql | 4 + services/payments/sqlc.yaml | 10 + stackable-app/AGENTS.md | 9 + stackable-app/CLAUDE.md | 1 + stackable-app/app/(developer)/error.tsx | 3 + stackable-app/app/(developer)/loading.tsx | 1 + stackable-app/app/(parent)/error.tsx | 3 + .../app/(parent)/family/fees/page.tsx | 22 +- .../app/(parent)/family/inbox/page.tsx | 20 +- .../app/(parent)/family/notices/page.tsx | 20 +- stackable-app/app/(parent)/loading.tsx | 1 + stackable-app/app/(principal)/error.tsx | 3 + stackable-app/app/(principal)/loading.tsx | 1 + stackable-app/app/(staff)/error.tsx | 3 + stackable-app/app/(staff)/layout.tsx | 8 + stackable-app/app/(staff)/loading.tsx | 1 + .../app/(staff)/staff/[...slug]/page.tsx | 8 + stackable-app/app/(staff)/staff/page.tsx | 11 + .../app/(staff)/staff/settings/page.tsx | 10 + stackable-app/app/(student)/error.tsx | 3 + .../app/(student)/learn/homework/page.tsx | 21 +- .../app/(student)/learn/subjects/page.tsx | 23 +- stackable-app/app/(student)/loading.tsx | 1 + stackable-app/app/(teacher)/error.tsx | 3 + stackable-app/app/(teacher)/loading.tsx | 1 + .../app/(teacher)/teach/classes/page.tsx | 16 +- .../app/(teacher)/teach/subjects/page.tsx | 16 +- stackable-app/app/api/health/route.ts | 18 + stackable-app/app/api/ready/route.ts | 34 + .../app/dashboard/ai/flashcard-maker/page.tsx | 15 +- .../dashboard/ai/homework-assistant/page.tsx | 15 +- .../app/dashboard/ai/quiz-generator/page.tsx | 15 +- .../app/dashboard/ai/summarizer/page.tsx | 15 +- .../app/dashboard/analytics/page.tsx | 14 +- stackable-app/app/dashboard/calender/page.tsx | 16 +- stackable-app/app/dashboard/classes/page.tsx | 13 +- .../cognitive-abilities-test/page.tsx | 15 +- stackable-app/app/dashboard/error.tsx | 3 + stackable-app/app/dashboard/exams/page.tsx | 15 +- .../app/dashboard/grades-reports/page.tsx | 15 +- stackable-app/app/dashboard/homework/page.tsx | 15 +- stackable-app/app/dashboard/inbox/page.tsx | 16 +- stackable-app/app/dashboard/library/page.tsx | 13 +- .../app/dashboard/live-activity/page.tsx | 14 +- .../live-activity/real-time/page.tsx | 15 +- .../live-activity/transport-status/page.tsx | 16 +- stackable-app/app/dashboard/loading.tsx | 1 + .../app/dashboard/message-app/page.tsx | 16 +- .../app/dashboard/notifications/page.tsx | 14 +- stackable-app/app/dashboard/parents/page.tsx | 18 +- .../parents/parents-resources/page.tsx | 18 +- .../payments/events/maintenance/page.tsx | 16 +- .../app/dashboard/payments/events/page.tsx | 16 +- stackable-app/app/dashboard/payments/page.tsx | 14 +- .../app/dashboard/payments/salaries/page.tsx | 16 +- .../dashboard/payments/school-fees/page.tsx | 16 +- stackable-app/app/dashboard/quizes/page.tsx | 15 +- .../dashboard/staff/duty-allocation/page.tsx | 13 +- stackable-app/app/dashboard/staff/page.tsx | 13 +- .../dashboard/staff/staff-resources/page.tsx | 13 +- .../dashboard/students/allocation/page.tsx | 15 +- .../students/students-resources/page.tsx | 13 +- .../teachers/load-allocation/page.tsx | 13 +- .../teachers/teachers-resources/page.tsx | 13 +- stackable-app/app/demo/page.tsx | 52 + stackable-app/app/error.tsx | 28 + stackable-app/app/layout.tsx | 13 +- stackable-app/app/not-found.tsx | 18 + .../components/admin/users/Badges.tsx | 4 + .../components/admin/users/userUtils.ts | 4 + .../components/dashboard/MobileBottomNav.tsx | 6 +- .../components/dashboard/PortalError.tsx | 30 + .../components/dashboard/PortalLoading.tsx | 16 + .../components/dashboard/QuickSearch.tsx | 3 +- .../components/dashboard/UserMenu.tsx | 2 +- stackable-app/components/dashboard/format.ts | 1 + .../components/demo/DemoProvider.tsx | 93 + stackable-app/components/dev/SitemapView.tsx | 4 +- stackable-app/components/dev/format.ts | 8 + .../components/header/Profile-Modal.tsx | 2 +- .../components/header/dashboard-navbar.tsx | 5 +- stackable-app/components/sidebar/sidebar.tsx | 28 +- stackable-app/docs/CHANGELOG.md | 176 + stackable-app/docs/CONNECTIONS.md | 67 + .../ADR-001-microservices-with-go.md | 70 + .../ADR-002-keep-web-app-in-place.md | 34 + .../DECISIONS/ADR-003-defer-membership.md | 35 + .../docs/DECISIONS/ADR-004-error-envelope.md | 46 + .../docs/DECISIONS/ADR-005-demo-mode.md | 47 + stackable-app/docs/DELETIONS.md | 44 + stackable-app/docs/FOUNDATION.md | 5 + stackable-app/docs/HANDOFF.md | 63 + stackable-app/docs/OPEN-QUESTIONS.md | 30 + stackable-app/docs/RESOURCES.md | 37 + stackable-app/docs/RUNBOOK.md | 38 + stackable-app/docs/WARNINGS.md | 76 + stackable-app/lib/api/errors.ts | 15 +- stackable-app/lib/api/guard.ts | 25 +- stackable-app/lib/api/ratelimit.test.ts | 22 + stackable-app/lib/api/ratelimit.ts | 16 +- stackable-app/lib/api/session.ts | 1 + stackable-app/lib/auth/otp.ts | 14 +- .../lib/demo/fixtures/admin-schools.ts | 164 + .../lib/demo/fixtures/admin-teachers.ts | 259 + stackable-app/lib/demo/fixtures/admin.ts | 196 + stackable-app/lib/demo/fixtures/data.ts | 195 + stackable-app/lib/demo/fixtures/dev.ts | 131 + .../lib/demo/fixtures/fixtures.test.ts | 217 + stackable-app/lib/demo/fixtures/index.ts | 73 + stackable-app/lib/demo/fixtures/parent.ts | 48 + stackable-app/lib/demo/fixtures/student.ts | 54 + stackable-app/lib/demo/fixtures/subjects.ts | 221 + stackable-app/lib/demo/fixtures/teacher.ts | 112 + stackable-app/lib/demo/mode.test.ts | 12 + stackable-app/lib/demo/mode.ts | 9 + stackable-app/lib/demo/router.test.ts | 23 + stackable-app/lib/demo/router.ts | 48 + stackable-app/lib/demo/store.ts | 32 + stackable-app/lib/env.ts | 38 +- stackable-app/lib/nav.ts | 125 +- stackable-app/lib/school-security.test.ts | 20 + stackable-app/lib/school-security.ts | 9 +- stackable-app/lib/validation/shared.ts | 22 +- stackable-app/package.json | 5 +- stackable-app/pnpm-workspace.yaml | 6 - .../migration.sql | 88 + .../migration.sql | 17 + .../migration.sql | 18 + .../migration.sql | 39 + .../migration.sql | 20 + stackable-app/prisma/schema.prisma | 9 +- stackable-app/proxy.ts | 7 +- stackable-app/scripts/db-inspect.mjs | 8 + stackable-app/scripts/db-rls-check.ts | 57 + stackable-app/scripts/run-checks.mjs | 29 + vitest.config.ts | 12 + 221 files changed, 12799 insertions(+), 2897 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 go.work create mode 100644 go.work.sum create mode 100644 package.json create mode 100644 packages/contracts/events/notification.requested.v1.json create mode 100644 packages/contracts/openapi/identity.yaml create mode 100644 packages/contracts/package.json create mode 100644 packages/contracts/src/index.ts create mode 100644 packages/contracts/tsconfig.json create mode 100644 packages/go-kit/errors.go create mode 100644 packages/go-kit/go.mod create mode 100644 packages/go-kit/go.sum create mode 100644 packages/go-kit/health.go create mode 100644 packages/go-kit/http.go create mode 100644 packages/go-kit/kit_test.go create mode 100644 packages/go-kit/logger.go create mode 100644 packages/go-kit/requestid.go create mode 100644 packages/go-kit/school.go create mode 100644 packages/go-kit/server.go create mode 100644 packages/go-kit/servicetoken.go create mode 100644 packages/service-kit/README.md create mode 100644 packages/service-kit/package.json create mode 100644 packages/service-kit/src/errors.test.ts create mode 100644 packages/service-kit/src/errors.ts create mode 100644 packages/service-kit/src/index.ts create mode 100644 packages/service-kit/src/logger.ts create mode 100644 packages/service-kit/src/service.test.ts create mode 100644 packages/service-kit/src/service.ts create mode 100644 packages/service-kit/src/token.test.ts create mode 100644 packages/service-kit/src/token.ts create mode 100644 packages/service-kit/src/with-school.test.ts create mode 100644 packages/service-kit/src/with-school.ts create mode 100644 packages/service-kit/tsconfig.json rename stackable-app/pnpm-lock.yaml => pnpm-lock.yaml (50%) create mode 100644 pnpm-workspace.yaml create mode 100644 services/academics/package.json create mode 100644 services/academics/src/lib/auth.ts create mode 100644 services/academics/src/lib/validate.ts create mode 100644 services/academics/src/placeholder.ts create mode 100644 services/academics/tsconfig.json create mode 100644 services/ai/package.json create mode 100644 services/ai/src/placeholder.ts create mode 100644 services/ai/tsconfig.json create mode 100644 services/communication/go.mod create mode 100644 services/communication/go.sum create mode 100644 services/communication/internal/notifications/callbacks.go create mode 100644 services/communication/internal/notifications/handler.go create mode 100644 services/communication/internal/notifications/memory_store.go create mode 100644 services/communication/internal/notifications/pg_store.go create mode 100644 services/communication/internal/notifications/service.go create mode 100644 services/communication/internal/notifications/store.go create mode 100644 services/communication/internal/notifications/types.go create mode 100644 services/communication/migrations/00001_create_communication.sql create mode 100644 services/documents/go.mod create mode 100644 services/documents/internal/documents/cachekey.go create mode 100644 services/documents/internal/documents/content.go create mode 100644 services/documents/internal/documents/errors.go create mode 100644 services/documents/internal/documents/id.go create mode 100644 services/documents/internal/documents/money.go create mode 100644 services/documents/internal/documents/pdf.go create mode 100644 services/documents/internal/documents/renderer.go create mode 100644 services/documents/internal/documents/types.go create mode 100644 services/documents/internal/documents/xlsx.go create mode 100644 services/identity/package.json create mode 100644 services/identity/src/app.test.ts create mode 100644 services/identity/src/app.ts create mode 100644 services/identity/src/main.ts create mode 100644 services/identity/tsconfig.json create mode 100644 services/payments/README.md create mode 100644 services/payments/cmd/payments/main.go create mode 100644 services/payments/go.mod create mode 100644 services/payments/go.sum create mode 100644 services/payments/internal/fees/store.go create mode 100644 services/payments/internal/fees/types.go create mode 100644 services/payments/internal/provider/fake.go create mode 100644 services/payments/internal/provider/mpesa.go create mode 100644 services/payments/internal/provider/provider.go create mode 100644 services/payments/internal/provider/provider_test.go create mode 100644 services/payments/internal/webhooks/handler.go create mode 100644 services/payments/internal/webhooks/pg_store.go create mode 100644 services/payments/internal/webhooks/service.go create mode 100644 services/payments/internal/webhooks/store.go create mode 100644 services/payments/internal/webhooks/webhooks_test.go create mode 100644 services/payments/migrations/00001_create_webhook_events.sql create mode 100644 services/payments/queries/webhook_events.sql create mode 100644 services/payments/sqlc.yaml create mode 100644 stackable-app/AGENTS.md create mode 100644 stackable-app/CLAUDE.md create mode 100644 stackable-app/app/(developer)/error.tsx create mode 100644 stackable-app/app/(developer)/loading.tsx create mode 100644 stackable-app/app/(parent)/error.tsx create mode 100644 stackable-app/app/(parent)/loading.tsx create mode 100644 stackable-app/app/(principal)/error.tsx create mode 100644 stackable-app/app/(principal)/loading.tsx create mode 100644 stackable-app/app/(staff)/error.tsx create mode 100644 stackable-app/app/(staff)/layout.tsx create mode 100644 stackable-app/app/(staff)/loading.tsx create mode 100644 stackable-app/app/(staff)/staff/[...slug]/page.tsx create mode 100644 stackable-app/app/(staff)/staff/page.tsx create mode 100644 stackable-app/app/(staff)/staff/settings/page.tsx create mode 100644 stackable-app/app/(student)/error.tsx create mode 100644 stackable-app/app/(student)/loading.tsx create mode 100644 stackable-app/app/(teacher)/error.tsx create mode 100644 stackable-app/app/(teacher)/loading.tsx create mode 100644 stackable-app/app/api/health/route.ts create mode 100644 stackable-app/app/api/ready/route.ts create mode 100644 stackable-app/app/dashboard/error.tsx create mode 100644 stackable-app/app/dashboard/loading.tsx create mode 100644 stackable-app/app/demo/page.tsx create mode 100644 stackable-app/app/error.tsx create mode 100644 stackable-app/app/not-found.tsx create mode 100644 stackable-app/components/dashboard/PortalError.tsx create mode 100644 stackable-app/components/dashboard/PortalLoading.tsx create mode 100644 stackable-app/components/demo/DemoProvider.tsx create mode 100644 stackable-app/docs/CHANGELOG.md create mode 100644 stackable-app/docs/CONNECTIONS.md create mode 100644 stackable-app/docs/DECISIONS/ADR-001-microservices-with-go.md create mode 100644 stackable-app/docs/DECISIONS/ADR-002-keep-web-app-in-place.md create mode 100644 stackable-app/docs/DECISIONS/ADR-003-defer-membership.md create mode 100644 stackable-app/docs/DECISIONS/ADR-004-error-envelope.md create mode 100644 stackable-app/docs/DECISIONS/ADR-005-demo-mode.md create mode 100644 stackable-app/docs/DELETIONS.md create mode 100644 stackable-app/docs/HANDOFF.md create mode 100644 stackable-app/docs/OPEN-QUESTIONS.md create mode 100644 stackable-app/docs/RESOURCES.md create mode 100644 stackable-app/docs/RUNBOOK.md create mode 100644 stackable-app/docs/WARNINGS.md create mode 100644 stackable-app/lib/api/ratelimit.test.ts create mode 100644 stackable-app/lib/demo/fixtures/admin-schools.ts create mode 100644 stackable-app/lib/demo/fixtures/admin-teachers.ts create mode 100644 stackable-app/lib/demo/fixtures/admin.ts create mode 100644 stackable-app/lib/demo/fixtures/data.ts create mode 100644 stackable-app/lib/demo/fixtures/dev.ts create mode 100644 stackable-app/lib/demo/fixtures/fixtures.test.ts create mode 100644 stackable-app/lib/demo/fixtures/index.ts create mode 100644 stackable-app/lib/demo/fixtures/parent.ts create mode 100644 stackable-app/lib/demo/fixtures/student.ts create mode 100644 stackable-app/lib/demo/fixtures/subjects.ts create mode 100644 stackable-app/lib/demo/fixtures/teacher.ts create mode 100644 stackable-app/lib/demo/mode.test.ts create mode 100644 stackable-app/lib/demo/mode.ts create mode 100644 stackable-app/lib/demo/router.test.ts create mode 100644 stackable-app/lib/demo/router.ts create mode 100644 stackable-app/lib/demo/store.ts create mode 100644 stackable-app/lib/school-security.test.ts delete mode 100644 stackable-app/pnpm-workspace.yaml create mode 100644 stackable-app/prisma/migrations/20261001000001_baseline_better_auth/migration.sql create mode 100644 stackable-app/prisma/migrations/20261001000002_data_bug_fixes/migration.sql create mode 100644 stackable-app/prisma/migrations/20261001000003_schools_primary_key/migration.sql create mode 100644 stackable-app/prisma/migrations/20261001000004_rls_foundation/migration.sql create mode 100644 stackable-app/prisma/migrations/20261001000005_role_status_checks/migration.sql create mode 100644 stackable-app/scripts/db-inspect.mjs create mode 100644 stackable-app/scripts/db-rls-check.ts create mode 100644 stackable-app/scripts/run-checks.mjs create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9978f81 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: ci + +on: + pull_request: + push: + branches: [main, stage] + +jobs: + node: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm typecheck + - run: pnpm test + - run: pnpm check + env: + SKIP_DB_CHECKS: "1" + OTP_HASH_SECRET: ci-only-secret + SCHOOL_SECURITY_CODES_SECRET: ci-only-secret + + go: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + - name: vet and test every Go module + run: | + for m in $(find . -name go.mod -not -path "*/node_modules/*" -exec dirname {} \;); do + (cd "$m" && go vet ./... && go test ./...) + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8903e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +coverage +.env* +!.env.example +*.tsbuildinfo +.DS_Store diff --git a/go.work b/go.work new file mode 100644 index 0000000..1e62796 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.25.0 + +use ( + ./packages/go-kit + ./services/payments +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..fc8d03b --- /dev/null +++ b/go.work.sum @@ -0,0 +1,4 @@ +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/package.json b/package.json new file mode 100644 index 0000000..42cd902 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "stackable", + "private": true, + "packageManager": "pnpm@10.30.1", + "scripts": { + "typecheck": "pnpm -r --if-present typecheck", + "test": "vitest run", + "check": "pnpm --filter stackable-app check", + "verify": "pnpm typecheck && pnpm test && pnpm check" + }, + "devDependencies": { + "vitest": "^3.2.4" + } +} diff --git a/packages/contracts/events/notification.requested.v1.json b/packages/contracts/events/notification.requested.v1.json new file mode 100644 index 0000000..7122ddd --- /dev/null +++ b/packages/contracts/events/notification.requested.v1.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://stackable.app/events/notification.requested.v1.json", + "title": "notification.requested.v1", + "type": "object", + "required": ["eventId", "schoolId", "type", "audience", "payload", "occurredAt"], + "additionalProperties": false, + "properties": { + "eventId": { "type": "string", "description": "Unique per event; consumers dedupe on it" }, + "schoolId": { "type": "string" }, + "type": { "type": "string", "description": "Notification kind, e.g. fee.invoice_issued" }, + "audience": { "type": "object", "description": "Who receives it (roles, user ids or class ids)", "additionalProperties": true }, + "payload": { "type": "object", "description": "Template variables", "additionalProperties": true }, + "occurredAt": { "type": "string", "format": "date-time" } + } +} diff --git a/packages/contracts/openapi/identity.yaml b/packages/contracts/openapi/identity.yaml new file mode 100644 index 0000000..75be3df --- /dev/null +++ b/packages/contracts/openapi/identity.yaml @@ -0,0 +1,51 @@ +openapi: 3.1.0 +info: + title: Stackable Identity Service + version: 0.1.0 +paths: + /v1/me: + get: + operationId: getMe + summary: Current actor taken from the service token + security: + - serviceToken: [] + responses: + "200": + description: The actor and school the token was issued for + content: + application/json: + schema: + $ref: "#/components/schemas/Me" + "401": + $ref: "#/components/responses/Error" + "500": + $ref: "#/components/responses/Error" +components: + securitySchemes: + serviceToken: + type: http + scheme: bearer + bearerFormat: JWT + responses: + Error: + description: Standard error envelope + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + schemas: + Me: + type: object + required: [userId, schoolId, role] + properties: + userId: { type: string } + schoolId: { type: string } + role: { type: [string, "null"] } + ErrorEnvelope: + type: object + required: [error, code, details, requestId] + properties: + error: { type: string } + code: { type: string } + details: { type: object, additionalProperties: true } + requestId: { type: string } diff --git a/packages/contracts/package.json b/packages/contracts/package.json new file mode 100644 index 0000000..d0adf1e --- /dev/null +++ b/packages/contracts/package.json @@ -0,0 +1,17 @@ +{ + "name": "@stackable/contracts", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { ".": "./src/index.ts" }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "vitest run --root ../.." + }, + "dependencies": { + "zod": "^4.0.0" + }, + "devDependencies": { + "typescript": "^5.9.0" + } +} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts new file mode 100644 index 0000000..04e1acc --- /dev/null +++ b/packages/contracts/src/index.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; + +/** Body every service returns on failure; clients branch on `code`, never on `error`. */ +export const ErrorEnvelopeSchema = z.object({ + error: z.string(), + code: z.string(), + details: z.record(z.string(), z.unknown()), + requestId: z.string(), +}); +export type ErrorEnvelope = z.infer; + +/** Claims of the short-lived HS256 token the gateway sends to services. */ +export const ServiceTokenClaimsSchema = z.object({ + iss: z.string().min(1), + aud: z.string().min(1), + sub: z.string().min(1), + schoolId: z.string().min(1), + role: z.string().min(1).optional(), + exp: z.number().int(), +}); +export type ServiceTokenClaims = z.infer; + +export const MeResponseSchema = z.object({ + userId: z.string(), + schoolId: z.string(), + role: z.string().nullable(), +}); +export type MeResponse = z.infer; diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json new file mode 100644 index 0000000..638bc06 --- /dev/null +++ b/packages/contracts/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": [] + }, + "include": ["src"] +} diff --git a/packages/go-kit/errors.go b/packages/go-kit/errors.go new file mode 100644 index 0000000..345dd19 --- /dev/null +++ b/packages/go-kit/errors.go @@ -0,0 +1,87 @@ +package kit + +import ( + "errors" + "net/http" +) + +// AppError is an operational error that maps to one HTTP status and one stable code. +type AppError struct { + Status int + Code string + Message string + Details any +} + +func (e *AppError) Error() string { return e.Code + ": " + e.Message } + +func newErr(status int, code, message string, details any) *AppError { + return &AppError{Status: status, Code: code, Message: message, Details: details} +} + +// Validation builds a 400 error; details usually lists the failed fields. +func Validation(message string, details any) *AppError { + return newErr(http.StatusBadRequest, "VALIDATION_FAILED", message, details) +} + +// Unauthorized builds a 401 error. +func Unauthorized(message string) *AppError { + return newErr(http.StatusUnauthorized, "AUTH_UNAUTHORIZED", message, nil) +} + +// Forbidden builds a 403 error. +func Forbidden(message string) *AppError { + return newErr(http.StatusForbidden, "AUTH_FORBIDDEN", message, nil) +} + +// NotFound builds a 404 error. +func NotFound(message string) *AppError { + return newErr(http.StatusNotFound, "RESOURCE_NOT_FOUND", message, nil) +} + +// Conflict builds a 409 error. +func Conflict(message string) *AppError { + return newErr(http.StatusConflict, "RESOURCE_CONFLICT", message, nil) +} + +// RateLimited builds a 429 error. +func RateLimited(message string) *AppError { + return newErr(http.StatusTooManyRequests, "RATE_LIMITED", message, nil) +} + +// Upstream builds a 502 error for a failing dependency. +func Upstream(message string) *AppError { + return newErr(http.StatusBadGateway, "UPSTREAM_FAILED", message, nil) +} + +// Unavailable builds a 503 error. +func Unavailable(message string) *AppError { + return newErr(http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", message, nil) +} + +// Internal builds a 500 error with a fixed safe message. +func Internal() *AppError { + return newErr(http.StatusInternalServerError, "INTERNAL_ERROR", "Something went wrong. Please try again.", nil) +} + +type envelope struct { + Error string `json:"error"` + Code string `json:"code"` + Details any `json:"details,omitempty"` + RequestID string `json:"requestId"` +} + +// WriteError sends the standard envelope; unknown errors are logged and become a safe 500. +func WriteError(w http.ResponseWriter, r *http.Request, err error) { + var appErr *AppError + if !errors.As(err, &appErr) { + LoggerFrom(r.Context()).Error("unhandled error", "err", err) + appErr = Internal() + } + WriteJSON(w, appErr.Status, envelope{ + Error: appErr.Message, + Code: appErr.Code, + Details: appErr.Details, + RequestID: RequestID(r.Context()), + }) +} diff --git a/packages/go-kit/go.mod b/packages/go-kit/go.mod new file mode 100644 index 0000000..c48ea99 --- /dev/null +++ b/packages/go-kit/go.mod @@ -0,0 +1,15 @@ +module github.com/stackable/go-kit + +go 1.25.0 + +require ( + github.com/go-chi/chi/v5 v5.3.2 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/jackc/pgx/v5 v5.11.0 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + golang.org/x/text v0.29.0 // indirect +) diff --git a/packages/go-kit/go.sum b/packages/go-kit/go.sum new file mode 100644 index 0000000..a785dba --- /dev/null +++ b/packages/go-kit/go.sum @@ -0,0 +1,30 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY= +github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/packages/go-kit/health.go b/packages/go-kit/health.go new file mode 100644 index 0000000..00b03a6 --- /dev/null +++ b/packages/go-kit/health.go @@ -0,0 +1,75 @@ +package kit + +import ( + "context" + "net/http" + "sync" + "time" +) + +const checkTimeout = 2 * time.Second // a slow dependency must not hang the probe + +// Check is one dependency probe; a failing Required check makes the service not ready. +type Check struct { + Name string + Required bool + Fn func(ctx context.Context) error +} + +type checkResult struct { + Name string `json:"name"` + Required bool `json:"required"` + Status string `json:"status"` + LatencyMs int64 `json:"latencyMs"` +} + +func runCheck(ctx context.Context, c Check) checkResult { + ctx, cancel := context.WithTimeout(ctx, checkTimeout) + defer cancel() + start := time.Now() + status := "ok" + if err := c.Fn(ctx); err != nil { + LoggerFrom(ctx).Warn("readiness check failed", "check", c.Name, "err", err) + status = "failed" + } + return checkResult{Name: c.Name, Required: c.Required, Status: status, LatencyMs: time.Since(start).Milliseconds()} +} + +func healthHandler(name, version string, started time.Time) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + WriteJSON(w, http.StatusOK, map[string]any{ + "status": "ok", + "name": name, + "version": version, + "uptimeSeconds": int64(time.Since(started).Seconds()), + }) + } +} + +func readyHandler(name, version string, checks []Check) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + results := make([]checkResult, len(checks)) + var wg sync.WaitGroup + for i, c := range checks { + wg.Add(1) + go func() { + defer wg.Done() + results[i] = runCheck(r.Context(), c) + }() + } + wg.Wait() + + status, code := "ready", http.StatusOK + for _, res := range results { + if res.Status == "ok" { + continue + } + if res.Required { + status, code = "unavailable", http.StatusServiceUnavailable + break + } + status = "degraded" + } + WriteJSON(w, code, map[string]any{"status": status, "name": name, "version": version, "checks": results}) + } +} diff --git a/packages/go-kit/http.go b/packages/go-kit/http.go new file mode 100644 index 0000000..da6b3a1 --- /dev/null +++ b/packages/go-kit/http.go @@ -0,0 +1,36 @@ +package kit + +import ( + "encoding/json" + "net/http" +) + +const maxBodyBytes = 1 << 20 // 1 MiB cap so one request cannot exhaust memory + +// HandlerFunc is an http handler that returns its error instead of writing it. +type HandlerFunc func(w http.ResponseWriter, r *http.Request) error + +// Handle adapts a HandlerFunc so every returned error becomes the standard envelope. +func Handle(fn HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + WriteError(w, r, err) + } + } +} + +// WriteJSON writes v as JSON with the given status. +func WriteJSON(w http.ResponseWriter, status int, v any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(v) // headers are already sent, so a write failure cannot be reported +} + +// DecodeJSON reads a size-limited JSON body into dst and returns a validation error on bad input. +func DecodeJSON(w http.ResponseWriter, r *http.Request, dst any) error { + r.Body = http.MaxBytesReader(w, r.Body, maxBodyBytes) + if err := json.NewDecoder(r.Body).Decode(dst); err != nil { + return Validation("Request body must be valid JSON.", nil) + } + return nil +} diff --git a/packages/go-kit/kit_test.go b/packages/go-kit/kit_test.go new file mode 100644 index 0000000..788d82f --- /dev/null +++ b/packages/go-kit/kit_test.go @@ -0,0 +1,371 @@ +package kit + +import ( + "context" + "encoding/json" + "errors" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +const ( + testSecret = "test-secret-test-secret-test-secret" + testIssuer = "gateway" + testAudience = "payments" +) + +func quietLogger() *slog.Logger { return slog.New(slog.NewJSONHandler(io.Discard, nil)) } + +func do(t *testing.T, h http.Handler, method, path string, headers map[string]string) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(method, path, nil) + for k, v := range headers { + req.Header.Set(k, v) + } + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + return rec +} + +func decode(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { + t.Helper() + var out map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { + t.Fatalf("body is not JSON: %q", rec.Body.String()) + } + return out +} + +func TestErrorEnvelope(t *testing.T) { + cases := []struct { + err *AppError + status int + code string + }{ + {Validation("bad", map[string]string{"email": "required"}), 400, "VALIDATION_FAILED"}, + {Unauthorized("no"), 401, "AUTH_UNAUTHORIZED"}, + {Forbidden("no"), 403, "AUTH_FORBIDDEN"}, + {NotFound("no"), 404, "RESOURCE_NOT_FOUND"}, + {Conflict("no"), 409, "RESOURCE_CONFLICT"}, + {RateLimited("no"), 429, "RATE_LIMITED"}, + {Upstream("no"), 502, "UPSTREAM_FAILED"}, + {Unavailable("no"), 503, "SERVICE_UNAVAILABLE"}, + {Internal(), 500, "INTERNAL_ERROR"}, + } + for _, c := range cases { + t.Run(c.code, func(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + r.Get("/x", Handle(func(w http.ResponseWriter, r *http.Request) error { return c.err })) + rec := do(t, r, "GET", "/x", map[string]string{"X-Request-Id": "req-1"}) + body := decode(t, rec) + if rec.Code != c.status || body["code"] != c.code || body["requestId"] != "req-1" || body["error"] == "" { + t.Fatalf("got %d %v", rec.Code, body) + } + }) + } +} + +func TestValidationDetailsAreReturned(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + r.Get("/x", Handle(func(w http.ResponseWriter, r *http.Request) error { + return Validation("bad", map[string]string{"email": "required"}) + })) + body := decode(t, do(t, r, "GET", "/x", nil)) + details, _ := body["details"].(map[string]any) + if details["email"] != "required" { + t.Fatalf("details missing: %v", body) + } +} + +func TestUnknownErrorDoesNotLeak(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + r.Get("/x", Handle(func(w http.ResponseWriter, r *http.Request) error { + return errors.New("password=hunter2 at db.internal:5432") + })) + rec := do(t, r, "GET", "/x", nil) + if rec.Code != 500 || strings.Contains(rec.Body.String(), "hunter2") { + t.Fatalf("leaked or wrong status: %d %s", rec.Code, rec.Body.String()) + } +} + +func TestPanicIsRecoveredWithoutLeak(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + r.Get("/boom", func(w http.ResponseWriter, r *http.Request) { panic("secret internal detail") }) + rec := do(t, r, "GET", "/boom", nil) + body := decode(t, rec) + if rec.Code != 500 || body["code"] != "INTERNAL_ERROR" || strings.Contains(rec.Body.String(), "secret") { + t.Fatalf("got %d %s", rec.Code, rec.Body.String()) + } +} + +func TestNotFoundAndMethodNotAllowedUseEnvelope(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + if body := decode(t, do(t, r, "GET", "/nope", nil)); body["code"] != "RESOURCE_NOT_FOUND" { + t.Fatalf("404 body: %v", body) + } + if rec := do(t, r, "POST", "/health", nil); rec.Code != 405 || decode(t, rec)["code"] != "METHOD_NOT_ALLOWED" { + t.Fatalf("405: %d %s", rec.Code, rec.Body.String()) + } +} + +func TestRequestID(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + t.Run("reuses a safe incoming id", func(t *testing.T) { + rec := do(t, r, "GET", "/health", map[string]string{"X-Request-Id": "abc-123"}) + if got := rec.Header().Get("X-Request-Id"); got != "abc-123" { + t.Fatalf("got %q", got) + } + }) + t.Run("generates one when missing", func(t *testing.T) { + if got := do(t, r, "GET", "/health", nil).Header().Get("X-Request-Id"); len(got) != 32 { + t.Fatalf("got %q", got) + } + }) + t.Run("replaces an unsafe id", func(t *testing.T) { + got := do(t, r, "GET", "/health", map[string]string{"X-Request-Id": "bad id\twith spaces"}).Header().Get("X-Request-Id") + if got == "bad id\twith spaces" || len(got) != 32 { + t.Fatalf("got %q", got) + } + }) + t.Run("is available in the handler context", func(t *testing.T) { + var seen string + r.Get("/ctx", func(w http.ResponseWriter, r *http.Request) { seen = RequestID(r.Context()) }) + do(t, r, "GET", "/ctx", map[string]string{"X-Request-Id": "ctx-1"}) + if seen != "ctx-1" { + t.Fatalf("got %q", seen) + } + }) +} + +func TestHealth(t *testing.T) { + r := NewRouter(Config{Name: "payments", Version: "1.2.3", Logger: quietLogger()}) + rec := do(t, r, "GET", "/health", nil) + body := decode(t, rec) + if rec.Code != 200 || body["status"] != "ok" || body["name"] != "payments" || body["version"] != "1.2.3" { + t.Fatalf("got %d %v", rec.Code, body) + } + if _, ok := body["uptimeSeconds"]; !ok { + t.Fatal("uptimeSeconds missing") + } +} + +func okCheck(context.Context) error { return nil } + +func failCheck(context.Context) error { return errors.New("down") } + +func TestReady(t *testing.T) { + cases := []struct { + name string + checks []Check + code int + status string + }{ + {"no checks", nil, 200, "ready"}, + {"all ok", []Check{{"db", true, okCheck}}, 200, "ready"}, + {"optional failing is degraded", []Check{{"db", true, okCheck}, {"vendor", false, failCheck}}, 200, "degraded"}, + {"required failing is 503", []Check{{"db", true, failCheck}, {"vendor", false, okCheck}}, 503, "unavailable"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + r := NewRouter(Config{Name: "t", Logger: quietLogger(), Checks: c.checks}) + rec := do(t, r, "GET", "/ready", nil) + if rec.Code != c.code || decode(t, rec)["status"] != c.status { + t.Fatalf("got %d %s", rec.Code, rec.Body.String()) + } + if strings.Contains(rec.Body.String(), "down") { + t.Fatal("check error text leaked") + } + }) + } +} + +func TestReadyCheckTimesOut(t *testing.T) { + slow := func(ctx context.Context) error { + <-ctx.Done() + return ctx.Err() + } + r := NewRouter(Config{Name: "t", Logger: quietLogger(), Checks: []Check{{"slow", true, slow}}}) + start := time.Now() + rec := do(t, r, "GET", "/ready", nil) + if rec.Code != 503 || time.Since(start) > 4*time.Second { + t.Fatalf("got %d after %s", rec.Code, time.Since(start)) + } +} + +func signed(t *testing.T, secret string, mutate func(*ServiceClaims)) string { + t.Helper() + claims := ServiceClaims{ + SchoolID: "school-1", + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: testIssuer, + Audience: jwt.ClaimStrings{testAudience}, + Subject: "user-1", + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute)), + }, + } + if mutate != nil { + mutate(&claims) + } + tok, err := SignServiceToken(secret, claims) + if err != nil { + t.Fatal(err) + } + return tok +} + +func TestServiceToken(t *testing.T) { + v, err := NewServiceTokenVerifier(testSecret, testIssuer, testAudience) + if err != nil { + t.Fatal(err) + } + r := NewRouter(Config{Name: "t", Logger: quietLogger()}) + var got *ServiceClaims + r.With(RequireServiceToken(v)).Get("/secure", func(w http.ResponseWriter, r *http.Request) { + got, _ = ClaimsFrom(r.Context()) + }) + call := func(auth string) *httptest.ResponseRecorder { + h := map[string]string{} + if auth != "" { + h["Authorization"] = auth + } + return do(t, r, "GET", "/secure", h) + } + + t.Run("valid", func(t *testing.T) { + rec := call("Bearer " + signed(t, testSecret, nil)) + if rec.Code != 200 || got == nil || got.SchoolID != "school-1" || got.Subject != "user-1" { + t.Fatalf("got %d %+v", rec.Code, got) + } + }) + t.Run("expired", func(t *testing.T) { + tok := signed(t, testSecret, func(c *ServiceClaims) { c.ExpiresAt = jwt.NewNumericDate(time.Now().Add(-time.Minute)) }) + if rec := call("Bearer " + tok); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("no expiry", func(t *testing.T) { + tok := signed(t, testSecret, func(c *ServiceClaims) { c.ExpiresAt = nil }) + if rec := call("Bearer " + tok); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("wrong audience", func(t *testing.T) { + tok := signed(t, testSecret, func(c *ServiceClaims) { c.Audience = jwt.ClaimStrings{"identity"} }) + if rec := call("Bearer " + tok); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("wrong issuer", func(t *testing.T) { + tok := signed(t, testSecret, func(c *ServiceClaims) { c.Issuer = "attacker" }) + if rec := call("Bearer " + tok); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("wrong secret", func(t *testing.T) { + if rec := call("Bearer " + signed(t, "another-secret-another-secret-12", nil)); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("missing school", func(t *testing.T) { + tok := signed(t, testSecret, func(c *ServiceClaims) { c.SchoolID = "" }) + if rec := call("Bearer " + tok); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("alg none is rejected", func(t *testing.T) { + tok, _ := jwt.NewWithClaims(jwt.SigningMethodNone, ServiceClaims{SchoolID: "s", RegisteredClaims: jwt.RegisteredClaims{ + Issuer: testIssuer, Audience: jwt.ClaimStrings{testAudience}, Subject: "u", + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute)), + }}).SignedString(jwt.UnsafeAllowNoneSignatureType) + if rec := call("Bearer " + tok); rec.Code != 401 { + t.Fatalf("got %d", rec.Code) + } + }) + t.Run("missing header", func(t *testing.T) { + rec := call("") + if rec.Code != 401 || decode(t, rec)["code"] != "AUTH_UNAUTHORIZED" { + t.Fatalf("got %d %s", rec.Code, rec.Body.String()) + } + }) +} + +func TestServiceTokenRefusesMissingSecret(t *testing.T) { + if _, err := NewServiceTokenVerifier("", testIssuer, testAudience); err == nil { + t.Fatal("expected error for empty secret") + } + t.Setenv(serviceTokenSecretEnv, "") + if _, err := NewServiceTokenVerifierFromEnv(testIssuer, testAudience); err == nil { + t.Fatal("expected error when env is unset") + } + t.Setenv(serviceTokenSecretEnv, testSecret) + if _, err := NewServiceTokenVerifierFromEnv(testIssuer, testAudience); err != nil { + t.Fatalf("unexpected: %v", err) + } +} + +type fakeTx struct { + pgx.Tx + execSQL string + execArgs []any + execErr error + committed bool + rolled bool +} + +func (f *fakeTx) Exec(_ context.Context, sql string, args ...any) (pgconn.CommandTag, error) { + f.execSQL, f.execArgs = sql, args + return pgconn.CommandTag{}, f.execErr +} +func (f *fakeTx) Commit(context.Context) error { f.committed = true; return nil } +func (f *fakeTx) Rollback(context.Context) error { f.rolled = true; return nil } + +type fakePool struct{ tx *fakeTx } + +func (p fakePool) Begin(context.Context) (pgx.Tx, error) { return p.tx, nil } + +func TestWithSchool(t *testing.T) { + t.Run("sets app.school_id before fn and commits", func(t *testing.T) { + tx := &fakeTx{} + err := WithSchool(context.Background(), fakePool{tx}, "school-9", func(context.Context, pgx.Tx) error { + if !strings.Contains(tx.execSQL, "set_config('app.school_id', $1, true)") || tx.execArgs[0] != "school-9" { + t.Fatalf("setting not applied before fn: %q %v", tx.execSQL, tx.execArgs) + } + return nil + }) + if err != nil || !tx.committed { + t.Fatalf("err=%v committed=%v", err, tx.committed) + } + }) + t.Run("fn error rolls back without commit", func(t *testing.T) { + tx := &fakeTx{} + boom := errors.New("boom") + err := WithSchool(context.Background(), fakePool{tx}, "s", func(context.Context, pgx.Tx) error { return boom }) + if !errors.Is(err, boom) || tx.committed || !tx.rolled { + t.Fatalf("err=%v committed=%v rolled=%v", err, tx.committed, tx.rolled) + } + }) + t.Run("set_config failure never runs fn", func(t *testing.T) { + tx := &fakeTx{execErr: errors.New("db down")} + ran := false + err := WithSchool(context.Background(), fakePool{tx}, "s", func(context.Context, pgx.Tx) error { ran = true; return nil }) + if err == nil || ran || tx.committed { + t.Fatalf("err=%v ran=%v committed=%v", err, ran, tx.committed) + } + }) + t.Run("empty school id is a validation error", func(t *testing.T) { + err := WithSchool(context.Background(), fakePool{&fakeTx{}}, "", func(context.Context, pgx.Tx) error { return nil }) + var appErr *AppError + if !errors.As(err, &appErr) || appErr.Status != 400 { + t.Fatalf("got %v", err) + } + }) +} diff --git a/packages/go-kit/logger.go b/packages/go-kit/logger.go new file mode 100644 index 0000000..d4f711f --- /dev/null +++ b/packages/go-kit/logger.go @@ -0,0 +1,66 @@ +package kit + +import ( + "context" + "log/slog" + "net/http" + "os" + "runtime/debug" + "time" +) + +const loggerKey ctxKey = "logger" + +// NewLogger returns a JSON slog logger tagged with the service name. +func NewLogger(service string) *slog.Logger { + return slog.New(slog.NewJSONHandler(os.Stdout, nil)).With("service", service) +} + +// LoggerFrom returns the request logger from ctx, falling back to the default logger. +func LoggerFrom(ctx context.Context) *slog.Logger { + if l, ok := ctx.Value(loggerKey).(*slog.Logger); ok { + return l + } + return slog.Default() +} + +type statusWriter struct { + http.ResponseWriter + status int +} + +func (s *statusWriter) WriteHeader(code int) { + s.status = code + s.ResponseWriter.WriteHeader(code) +} + +// LoggingMiddleware puts a requestId-tagged logger in ctx and logs one line per request. +func LoggingMiddleware(base *slog.Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + l := base.With("requestId", RequestID(r.Context())) + sw := &statusWriter{ResponseWriter: w, status: http.StatusOK} + next.ServeHTTP(sw, r.WithContext(context.WithValue(r.Context(), loggerKey, l))) + l.Info("request", "method", r.Method, "path", r.URL.Path, "status", sw.status, "ms", time.Since(start).Milliseconds()) + }) + } +} + +// RecoverMiddleware turns a panic into a logged 500 envelope without leaking internals. +func RecoverMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + rec := recover() + if rec == nil { + return + } + if rec == http.ErrAbortHandler { + panic(rec) // net/http uses this value to abort a response on purpose + } + LoggerFrom(r.Context()).Error("panic", "panic", rec, "stack", string(debug.Stack())) + WriteError(w, r, Internal()) + }() + next.ServeHTTP(w, r) + }) +} diff --git a/packages/go-kit/requestid.go b/packages/go-kit/requestid.go new file mode 100644 index 0000000..1c515c9 --- /dev/null +++ b/packages/go-kit/requestid.go @@ -0,0 +1,54 @@ +package kit + +import ( + "context" + "crypto/rand" + "encoding/hex" + "net/http" +) + +const ( + requestIDHeader = "X-Request-Id" + maxRequestIDLen = 128 // longer ids are replaced so logs cannot be flooded +) + +type ctxKey string + +const requestIDKey ctxKey = "requestId" + +func newRequestID() string { + b := make([]byte, 16) + _, _ = rand.Read(b) // crypto/rand does not fail on supported platforms + return hex.EncodeToString(b) +} + +func isSafeRequestID(id string) bool { + if id == "" || len(id) > maxRequestIDLen { + return false + } + for _, c := range id { + ok := c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + if !ok { + return false + } + } + return true +} + +// RequestIDMiddleware reuses a safe incoming x-request-id or makes one, then echoes it. +func RequestIDMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id := r.Header.Get(requestIDHeader) + if !isSafeRequestID(id) { + id = newRequestID() + } + w.Header().Set(requestIDHeader, id) + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), requestIDKey, id))) + }) +} + +// RequestID returns the request id stored in ctx, or an empty string. +func RequestID(ctx context.Context) string { + id, _ := ctx.Value(requestIDKey).(string) + return id +} diff --git a/packages/go-kit/school.go b/packages/go-kit/school.go new file mode 100644 index 0000000..f7c372c --- /dev/null +++ b/packages/go-kit/school.go @@ -0,0 +1,34 @@ +package kit + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" +) + +// TxBeginner is satisfied by *pgxpool.Pool. +type TxBeginner interface { + Begin(ctx context.Context) (pgx.Tx, error) +} + +// WithSchool runs fn in a transaction whose app.school_id is set, so RLS applies to every query in fn. +// The setting is transaction-local (set_config third arg true) and cannot leak to other pool users. +func WithSchool(ctx context.Context, pool TxBeginner, schoolID string, fn func(ctx context.Context, tx pgx.Tx) error) error { + if schoolID == "" { + return Validation("schoolId is required.", nil) + } + tx, err := pool.Begin(ctx) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + defer func() { _ = tx.Rollback(ctx) }() // no-op after a successful commit + + if _, err := tx.Exec(ctx, "select set_config('app.school_id', $1, true)", schoolID); err != nil { + return fmt.Errorf("set app.school_id: %w", err) + } + if err := fn(ctx, tx); err != nil { + return err + } + return tx.Commit(ctx) +} diff --git a/packages/go-kit/server.go b/packages/go-kit/server.go new file mode 100644 index 0000000..db2275e --- /dev/null +++ b/packages/go-kit/server.go @@ -0,0 +1,58 @@ +package kit + +import ( + "context" + "errors" + "log/slog" + "net/http" + "time" + + "github.com/go-chi/chi/v5" +) + +const shutdownTimeout = 10 * time.Second // lets in-flight requests finish on SIGTERM + +// Config describes one service for NewRouter. +type Config struct { + Name string + Version string + Logger *slog.Logger + Checks []Check +} + +// NewRouter returns a chi router with request-id, logging, recover, /health, /ready and envelope 404/405. +func NewRouter(cfg Config) chi.Router { + if cfg.Logger == nil { + cfg.Logger = NewLogger(cfg.Name) + } + started := time.Now() + r := chi.NewRouter() + r.Use(RequestIDMiddleware, LoggingMiddleware(cfg.Logger), RecoverMiddleware) + r.Get("/health", healthHandler(cfg.Name, cfg.Version, started)) + r.Get("/ready", readyHandler(cfg.Name, cfg.Version, cfg.Checks)) + r.NotFound(Handle(func(w http.ResponseWriter, r *http.Request) error { return NotFound("Route not found.") })) + r.MethodNotAllowed(Handle(func(w http.ResponseWriter, r *http.Request) error { + return newErr(http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "Method not allowed.", nil) + })) + return r +} + +// Serve runs handler on addr until ctx is cancelled, then shuts down gracefully. +func Serve(ctx context.Context, addr string, handler http.Handler, logger *slog.Logger) error { + srv := &http.Server{Addr: addr, Handler: handler, ReadHeaderTimeout: 5 * time.Second} + errCh := make(chan error, 1) + go func() { errCh <- srv.ListenAndServe() }() + logger.Info("listening", "addr", addr) + + select { + case err := <-errCh: + return err + case <-ctx.Done(): + shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + if err := srv.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil + } +} diff --git a/packages/go-kit/servicetoken.go b/packages/go-kit/servicetoken.go new file mode 100644 index 0000000..9c9a41e --- /dev/null +++ b/packages/go-kit/servicetoken.go @@ -0,0 +1,91 @@ +package kit + +import ( + "context" + "errors" + "net/http" + "os" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +const ( + serviceTokenSecretEnv = "SERVICE_TOKEN_SECRET" + tokenLeeway = 5 * time.Second // tolerates small clock drift between services +) + +// ServiceClaims are the verified claims of a service token. +type ServiceClaims struct { + SchoolID string `json:"schoolId"` + jwt.RegisteredClaims +} + +// ServiceTokenVerifier checks HS256 service tokens for one issuer and audience. +type ServiceTokenVerifier struct { + secret []byte + issuer string + audience string +} + +// NewServiceTokenVerifier refuses an empty secret so there is never a fallback key. +func NewServiceTokenVerifier(secret, issuer, audience string) (*ServiceTokenVerifier, error) { + if secret == "" { + return nil, errors.New("service token secret is not set") + } + return &ServiceTokenVerifier{secret: []byte(secret), issuer: issuer, audience: audience}, nil +} + +// NewServiceTokenVerifierFromEnv reads SERVICE_TOKEN_SECRET and fails if it is unset. +func NewServiceTokenVerifierFromEnv(issuer, audience string) (*ServiceTokenVerifier, error) { + return NewServiceTokenVerifier(os.Getenv(serviceTokenSecretEnv), issuer, audience) +} + +// Verify parses a token and returns its claims, or an Unauthorized error. +func (v *ServiceTokenVerifier) Verify(token string) (*ServiceClaims, error) { + claims := &ServiceClaims{} + _, err := jwt.ParseWithClaims(token, claims, func(*jwt.Token) (any, error) { return v.secret, nil }, + jwt.WithValidMethods([]string{"HS256"}), + jwt.WithIssuer(v.issuer), + jwt.WithAudience(v.audience), + jwt.WithExpirationRequired(), + jwt.WithLeeway(tokenLeeway), + ) + if err != nil || claims.Subject == "" || claims.SchoolID == "" { + return nil, Unauthorized("Invalid or expired service token.") + } + return claims, nil +} + +// SignServiceToken creates a token for callers and tests. +func SignServiceToken(secret string, claims ServiceClaims) (string, error) { + return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret)) +} + +const claimsKey ctxKey = "serviceClaims" + +// ClaimsFrom returns the verified service claims stored by RequireServiceToken. +func ClaimsFrom(ctx context.Context) (*ServiceClaims, bool) { + c, ok := ctx.Value(claimsKey).(*ServiceClaims) + return c, ok +} + +// RequireServiceToken rejects requests without a valid bearer service token. +func RequireServiceToken(v *ServiceTokenVerifier) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + raw, found := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ") + if !found || raw == "" { + WriteError(w, r, Unauthorized("Missing service token.")) + return + } + claims, err := v.Verify(raw) + if err != nil { + WriteError(w, r, err) + return + } + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), claimsKey, claims))) + }) + } +} diff --git a/packages/service-kit/README.md b/packages/service-kit/README.md new file mode 100644 index 0000000..00522a3 --- /dev/null +++ b/packages/service-kit/README.md @@ -0,0 +1,4 @@ +# @stackable/service-kit + +Hono over Fastify: smaller, web-standard, and `app.request()` tests need no server or port. +Gives every TS service the same envelope, request id, `/health`, `/ready` and service-token check. diff --git a/packages/service-kit/package.json b/packages/service-kit/package.json new file mode 100644 index 0000000..c815993 --- /dev/null +++ b/packages/service-kit/package.json @@ -0,0 +1,23 @@ +{ + "name": "@stackable/service-kit", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { ".": "./src/index.ts" }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "vitest run --root ../.." + }, + "dependencies": { + "@stackable/contracts": "workspace:*", + "hono": "^4.9.0", + "jose": "^6.0.0", + "pg": "^8.16.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/pg": "^8.15.0", + "typescript": "^5.9.0" + } +} diff --git a/packages/service-kit/src/errors.test.ts b/packages/service-kit/src/errors.test.ts new file mode 100644 index 0000000..d62c159 --- /dev/null +++ b/packages/service-kit/src/errors.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "vitest"; +import { z } from "zod"; +import { ErrorEnvelopeSchema } from "@stackable/contracts"; +import { errors, toErrorResponse } from "./errors"; + +describe("toErrorResponse", () => { + const cases = [ + ["validation", errors.validation(), 400, "REQUEST_VALIDATION_FAILED"], + ["unauthorized", errors.unauthorized(), 401, "AUTH_UNAUTHORIZED"], + ["forbidden", errors.forbidden(), 403, "AUTH_FORBIDDEN"], + ["notFound", errors.notFound(), 404, "RESOURCE_NOT_FOUND"], + ["conflict", errors.conflict(), 409, "RESOURCE_CONFLICT"], + ["rateLimited", errors.rateLimited(), 429, "RATE_LIMIT_EXCEEDED"], + ["upstream", errors.upstream(), 502, "UPSTREAM_FAILED"], + ["unavailable", errors.unavailable(), 503, "SERVICE_UNAVAILABLE"], + ["internal", errors.internal(), 500, "INTERNAL_ERROR"], + ] as const; + + it.each(cases)("maps %s to its status and code", (_name, err, status, code) => { + const res = toErrorResponse(err, "req-1"); + expect(res.status).toBe(status); + expect(res.body.code).toBe(code); + expect(ErrorEnvelopeSchema.safeParse(res.body).success).toBe(true); + expect(res.body.requestId).toBe("req-1"); + }); + + it("turns unknown errors into a generic 500 without leaking the message", () => { + const res = toErrorResponse(new Error("password=hunter2 in connection string"), "req-2"); + expect(res.status).toBe(500); + expect(JSON.stringify(res.body)).not.toContain("hunter2"); + }); + + it("turns a ZodError into a 400 listing the failed fields", () => { + const parsed = z.object({ age: z.number() }).safeParse({ age: "x" }); + const res = toErrorResponse(parsed.error, "req-3"); + expect(res.status).toBe(400); + expect(res.body.details).toEqual({ fields: [{ path: "age", message: expect.any(String) }] }); + }); +}); diff --git a/packages/service-kit/src/errors.ts b/packages/service-kit/src/errors.ts new file mode 100644 index 0000000..a4c3c54 --- /dev/null +++ b/packages/service-kit/src/errors.ts @@ -0,0 +1,55 @@ +import { ZodError } from "zod"; +import { HTTPException } from "hono/http-exception"; +import type { ContentfulStatusCode } from "hono/utils/http-status"; +import type { ErrorEnvelope } from "@stackable/contracts"; + +type Details = Record; + +/** An expected failure; its message is safe to show the caller. */ +export class AppError extends Error { + readonly status: ContentfulStatusCode; + readonly code: string; + readonly details: Details; + + constructor(status: ContentfulStatusCode, code: string, message: string, details: Details = {}) { + super(message); + this.name = "AppError"; + this.status = status; + this.code = code; + this.details = details; + } +} + +export const errors = { + validation: (message = "Request validation failed", details: Details = {}) => + new AppError(400, "REQUEST_VALIDATION_FAILED", message, details), + unauthorized: (message = "Authentication required") => new AppError(401, "AUTH_UNAUTHORIZED", message), + forbidden: (message = "You do not have access to this resource") => new AppError(403, "AUTH_FORBIDDEN", message), + notFound: (message = "Resource not found") => new AppError(404, "RESOURCE_NOT_FOUND", message), + conflict: (message = "Resource state conflict", details: Details = {}) => + new AppError(409, "RESOURCE_CONFLICT", message, details), + rateLimited: (message = "Too many requests") => new AppError(429, "RATE_LIMIT_EXCEEDED", message), + upstream: (message = "A dependency failed") => new AppError(502, "UPSTREAM_FAILED", message), + unavailable: (message = "Service temporarily unavailable") => new AppError(503, "SERVICE_UNAVAILABLE", message), + internal: () => new AppError(500, "INTERNAL_ERROR", "Something went wrong"), +}; + +/** Maps any thrown value to a status and envelope; unknown errors become a generic 500. */ +export function toErrorResponse(err: unknown, requestId: string): { status: ContentfulStatusCode; body: ErrorEnvelope } { + let appError: AppError; + if (err instanceof AppError) { + appError = err; + } else if (err instanceof ZodError) { + appError = errors.validation("Request validation failed", { + fields: err.issues.map((i) => ({ path: i.path.join("."), message: i.message })), + }); + } else if (err instanceof HTTPException && err.status === 400) { + appError = errors.validation("Malformed request"); + } else { + appError = errors.internal(); + } + return { + status: appError.status, + body: { error: appError.message, code: appError.code, details: appError.details, requestId }, + }; +} diff --git a/packages/service-kit/src/index.ts b/packages/service-kit/src/index.ts new file mode 100644 index 0000000..768b8f7 --- /dev/null +++ b/packages/service-kit/src/index.ts @@ -0,0 +1,7 @@ +export { createService, requireServiceToken, READY_CHECK_TIMEOUT_MS } from "./service"; +export type { AppEnv, ReadyCheck, ServiceOptions } from "./service"; +export { AppError, errors, toErrorResponse } from "./errors"; +export { signServiceToken, verifyServiceToken, SERVICE_TOKEN_ISSUER } from "./token"; +export { withSchool } from "./with-school"; +export { createLogger } from "./logger"; +export type { Logger } from "./logger"; diff --git a/packages/service-kit/src/logger.ts b/packages/service-kit/src/logger.ts new file mode 100644 index 0000000..1a3c496 --- /dev/null +++ b/packages/service-kit/src/logger.ts @@ -0,0 +1,20 @@ +export type LogFields = Record; + +export interface Logger { + info(msg: string, fields?: LogFields): void; + warn(msg: string, fields?: LogFields): void; + error(msg: string, fields?: LogFields): void; + child(fields: LogFields): Logger; +} + +/** One JSON line per event on stdout so any log shipper can parse it. */ +export function createLogger(service: string, base: LogFields = {}): Logger { + const write = (level: string, msg: string, fields: LogFields = {}) => + console.log(JSON.stringify({ time: new Date().toISOString(), level, service, msg, ...base, ...fields })); + return { + info: (msg, fields) => write("info", msg, fields), + warn: (msg, fields) => write("warn", msg, fields), + error: (msg, fields) => write("error", msg, fields), + child: (fields) => createLogger(service, { ...base, ...fields }), + }; +} diff --git a/packages/service-kit/src/service.test.ts b/packages/service-kit/src/service.test.ts new file mode 100644 index 0000000..d918b7e --- /dev/null +++ b/packages/service-kit/src/service.test.ts @@ -0,0 +1,99 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createService } from "./service"; +import { errors } from "./errors"; + +beforeEach(() => { + vi.spyOn(console, "log").mockImplementation(() => undefined); +}); +afterEach(() => { + vi.restoreAllMocks(); + vi.useRealTimers(); +}); + +describe("request id", () => { + it("echoes an incoming x-request-id and puts it in error bodies", async () => { + const app = createService({ name: "t", version: "1" }); + app.get("/boom", () => { + throw errors.conflict("nope"); + }); + const res = await app.request("/boom", { headers: { "x-request-id": "abc-123" } }); + expect(res.headers.get("x-request-id")).toBe("abc-123"); + expect(await res.json()).toMatchObject({ code: "RESOURCE_CONFLICT", requestId: "abc-123" }); + }); + + it("generates an id when missing or malformed", async () => { + const app = createService({ name: "t", version: "1" }); + const missing = await app.request("/health"); + const bad = await app.request("/health", { headers: { "x-request-id": "bad id with spaces" } }); + expect(missing.headers.get("x-request-id")).toMatch(/^[0-9a-f-]{36}$/); + expect(bad.headers.get("x-request-id")).toMatch(/^[0-9a-f-]{36}$/); + }); +}); + +describe("error handling", () => { + it("hides internals of unexpected errors and returns the envelope", async () => { + const app = createService({ name: "t", version: "1" }); + app.get("/crash", () => { + throw new Error("db password leaked"); + }); + const res = await app.request("/crash"); + expect(res.status).toBe(500); + expect(JSON.stringify(await res.json())).not.toContain("leaked"); + }); + + it("returns the envelope for unknown routes", async () => { + const res = await createService({ name: "t", version: "1" }).request("/nope"); + expect(res.status).toBe(404); + expect(await res.json()).toMatchObject({ code: "RESOURCE_NOT_FOUND" }); + }); +}); + +describe("/health", () => { + it("is always 200 with name, version and uptime", async () => { + const res = await createService({ name: "svc", version: "9.9.9" }).request("/health"); + expect(res.status).toBe(200); + expect(await res.json()).toMatchObject({ status: "ok", name: "svc", version: "9.9.9", uptimeSeconds: expect.any(Number) }); + }); +}); + +describe("/ready", () => { + const pass = async () => undefined; + const fail = async () => { + throw new Error("down"); + }; + + it("is ready when all checks pass", async () => { + const app = createService({ name: "t", version: "1", checks: [{ name: "db", run: pass }] }); + const res = await app.request("/ready"); + expect(res.status).toBe(200); + expect(await res.json()).toMatchObject({ status: "ready", checks: [{ name: "db", ok: true }] }); + }); + + it("is degraded (200) when only an optional check fails", async () => { + const app = createService({ + name: "t", + version: "1", + checks: [{ name: "db", run: pass }, { name: "resend", run: fail, required: false }], + }); + const res = await app.request("/ready"); + expect(res.status).toBe(200); + expect(await res.json()).toMatchObject({ status: "degraded" }); + }); + + it("is 503 when a required check fails", async () => { + const app = createService({ name: "t", version: "1", checks: [{ name: "db", run: fail }] }); + const res = await app.request("/ready"); + expect(res.status).toBe(503); + expect(await res.json()).toMatchObject({ status: "not_ready", checks: [{ name: "db", ok: false }] }); + }); + + it("fails a check that hangs past the timeout", async () => { + vi.useFakeTimers(); + const hang = () => new Promise(() => undefined); + const app = createService({ name: "t", version: "1", checks: [{ name: "slow", run: hang }] }); + const pending = app.request("/ready"); + await vi.advanceTimersByTimeAsync(2100); + const res = await pending; + expect(res.status).toBe(503); + }); +}); diff --git a/packages/service-kit/src/service.ts b/packages/service-kit/src/service.ts new file mode 100644 index 0000000..bd3b4ab --- /dev/null +++ b/packages/service-kit/src/service.ts @@ -0,0 +1,106 @@ +import { Hono, type MiddlewareHandler } from "hono"; +import type { ServiceTokenClaims } from "@stackable/contracts"; +import { errors, toErrorResponse } from "./errors"; +import { createLogger, type Logger } from "./logger"; +import { verifyServiceToken } from "./token"; + +export const READY_CHECK_TIMEOUT_MS = 2000; // a hung dependency must not hang the probe +const REQUEST_ID_PATTERN = /^[\w.-]{1,128}$/; // reject anything that could forge log lines + +export interface ReadyCheck { + name: string; + run: () => Promise; + /** Defaults to true; a failing optional check only marks the service degraded. */ + required?: boolean; +} + +export interface ServiceOptions { + name: string; + version: string; + checks?: ReadyCheck[]; +} + +export type AppEnv = { + Variables: { requestId: string; log: Logger; claims: ServiceTokenClaims }; +}; + +async function runCheck(check: ReadyCheck) { + const started = Date.now(); + let timer: NodeJS.Timeout | undefined; + let ok = true; + let failure: unknown; + try { + await Promise.race([ + check.run(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error("check timed out")), READY_CHECK_TIMEOUT_MS); + }), + ]); + } catch (err) { + ok = false; + failure = err; + } finally { + clearTimeout(timer); + } + return { name: check.name, ok, latencyMs: Date.now() - started, required: check.required !== false, failure }; +} + +/** Builds a Hono app with request ids, the error envelope, /health and /ready already wired. */ +export function createService({ name, version, checks = [] }: ServiceOptions): Hono { + const app = new Hono(); + const logger = createLogger(name); + const startedAt = Date.now(); + + const requestId: MiddlewareHandler = async (c, next) => { + const incoming = c.req.header("x-request-id"); + const id = incoming && REQUEST_ID_PATTERN.test(incoming) ? incoming : crypto.randomUUID(); + const log = logger.child({ requestId: id }); + c.set("requestId", id); + c.set("log", log); + c.header("x-request-id", id); + const began = Date.now(); + await next(); + log.info("request", { method: c.req.method, path: c.req.path, status: c.res.status, durationMs: Date.now() - began }); + }; + app.use("*", requestId); + + app.get("/health", (c) => + c.json({ status: "ok", name, version, uptimeSeconds: Math.floor((Date.now() - startedAt) / 1000) }), + ); + + app.get("/ready", async (c) => { + const results = await Promise.all(checks.map(runCheck)); + for (const r of results) { + if (!r.ok) c.var.log.warn("ready check failed", { check: r.name, reason: String(r.failure) }); + } + const body = results.map(({ name: checkName, ok, latencyMs }) => ({ name: checkName, ok, latencyMs })); + if (results.some((r) => !r.ok && r.required)) return c.json({ status: "not_ready", checks: body }, 503); + return c.json({ status: results.some((r) => !r.ok) ? "degraded" : "ready", checks: body }); + }); + + app.notFound(() => { + throw errors.notFound("Route not found"); + }); + + app.onError((err, c) => { + const id = c.var.requestId ?? "unknown"; + const { status, body } = toErrorResponse(err, id); + if (status === 500) { + (c.var.log ?? logger).error("unhandled error", { requestId: id, err: String(err), stack: (err as Error).stack }); + } + return c.json(body, status); + }); + + return app; +} + +/** Guards a route: requires a valid Bearer service token for this audience and exposes its claims. */ +export function requireServiceToken(audience: string): MiddlewareHandler { + return async (c, next) => { + const header = c.req.header("authorization") ?? ""; + const token = header.startsWith("Bearer ") ? header.slice("Bearer ".length) : ""; + if (!token) throw errors.unauthorized("Missing service token"); + c.set("claims", await verifyServiceToken(token, audience)); + await next(); + }; +} diff --git a/packages/service-kit/src/token.test.ts b/packages/service-kit/src/token.test.ts new file mode 100644 index 0000000..5651515 --- /dev/null +++ b/packages/service-kit/src/token.test.ts @@ -0,0 +1,48 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { SignJWT } from "jose"; +import { signServiceToken, verifyServiceToken } from "./token"; + +const SECRET = "test-secret-test-secret-test-secret-1234"; +const base = { audience: "identity", sub: "user-1", schoolId: "school-1" }; + +beforeEach(() => { + process.env.SERVICE_TOKEN_SECRET = SECRET; +}); +afterEach(() => { + delete process.env.SERVICE_TOKEN_SECRET; +}); + +describe("service token", () => { + it("accepts a valid token and returns its claims", async () => { + const claims = await verifyServiceToken(await signServiceToken({ ...base, role: "teacher" }), "identity"); + expect(claims).toMatchObject({ sub: "user-1", schoolId: "school-1", aud: "identity", role: "teacher" }); + }); + + it("rejects an expired token", async () => { + const token = await signServiceToken({ ...base, ttlSeconds: -10 }); + await expect(verifyServiceToken(token, "identity")).rejects.toMatchObject({ status: 401 }); + }); + + it("rejects the wrong audience", async () => { + const token = await signServiceToken({ ...base, audience: "payments" }); + await expect(verifyServiceToken(token, "identity")).rejects.toMatchObject({ status: 401 }); + }); + + it("rejects a token signed with another secret", async () => { + const forged = await new SignJWT({ schoolId: "school-1" }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuer("stackable-gateway") + .setAudience("identity") + .setSubject("user-1") + .setExpirationTime("1m") + .sign(new TextEncoder().encode("another-secret-another-secret-12345")); + await expect(verifyServiceToken(forged, "identity")).rejects.toMatchObject({ status: 401 }); + }); + + it("throws when the secret is unset, for signing and verifying", async () => { + const token = await signServiceToken(base); + delete process.env.SERVICE_TOKEN_SECRET; + await expect(signServiceToken(base)).rejects.toThrow("SERVICE_TOKEN_SECRET"); + await expect(verifyServiceToken(token, "identity")).rejects.toThrow("SERVICE_TOKEN_SECRET"); + }); +}); diff --git a/packages/service-kit/src/token.ts b/packages/service-kit/src/token.ts new file mode 100644 index 0000000..9723bd8 --- /dev/null +++ b/packages/service-kit/src/token.ts @@ -0,0 +1,52 @@ +import { SignJWT, jwtVerify } from "jose"; +import { ServiceTokenClaimsSchema, type ServiceTokenClaims } from "@stackable/contracts"; +import { errors } from "./errors"; + +export const SERVICE_TOKEN_ISSUER = "stackable-gateway"; +const MIN_SECRET_LENGTH = 32; // HS256 needs a key at least as long as its 256-bit hash +export const DEFAULT_TOKEN_TTL_SECONDS = 60; // short-lived so a leaked token is nearly useless + +// A missing secret must stop the service, never fall back to a default. +function getSecret(): Uint8Array { + const secret = process.env.SERVICE_TOKEN_SECRET; + if (!secret || secret.length < MIN_SECRET_LENGTH) { + throw new Error(`SERVICE_TOKEN_SECRET must be set to at least ${MIN_SECRET_LENGTH} characters`); + } + return new TextEncoder().encode(secret); +} + +export interface SignTokenInput { + audience: string; + sub: string; + schoolId: string; + role?: string; + ttlSeconds?: number; +} + +/** Issues a service token for one actor and school; used by the gateway and by tests. */ +export async function signServiceToken(input: SignTokenInput): Promise { + const now = Math.floor(Date.now() / 1000); + return new SignJWT({ schoolId: input.schoolId, ...(input.role ? { role: input.role } : {}) }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuer(SERVICE_TOKEN_ISSUER) + .setAudience(input.audience) + .setSubject(input.sub) + .setIssuedAt(now) + .setExpirationTime(now + (input.ttlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS)) + .sign(getSecret()); +} + +/** Returns the claims of a valid token for this audience; throws unauthorized otherwise. */ +export async function verifyServiceToken(token: string, audience: string): Promise { + const secret = getSecret(); + try { + const { payload } = await jwtVerify(token, secret, { + algorithms: ["HS256"], + issuer: SERVICE_TOKEN_ISSUER, + audience, + }); + return ServiceTokenClaimsSchema.parse(payload); + } catch { + throw errors.unauthorized("Invalid or expired service token"); + } +} diff --git a/packages/service-kit/src/with-school.test.ts b/packages/service-kit/src/with-school.test.ts new file mode 100644 index 0000000..c596379 --- /dev/null +++ b/packages/service-kit/src/with-school.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it, vi } from "vitest"; +import type { Pool } from "pg"; +import { withSchool } from "./with-school"; + +function fakePool() { + const query = vi.fn(async () => ({ rows: [] })); + const release = vi.fn(); + const pool = { connect: async () => ({ query, release }) } as unknown as Pool; + return { pool, query, release }; +} + +describe("withSchool", () => { + it("sets app.school_id inside a transaction and commits", async () => { + const { pool, query, release } = fakePool(); + const result = await withSchool(pool, "school-1", async () => "done"); + expect(result).toBe("done"); + expect(query.mock.calls).toEqual([ + ["BEGIN"], + ["select set_config('app.school_id', $1, true)", ["school-1"]], + ["COMMIT"], + ]); + expect(release).toHaveBeenCalledOnce(); + }); + + it("rolls back, rethrows and releases when fn fails", async () => { + const { pool, query, release } = fakePool(); + await expect(withSchool(pool, "school-1", async () => Promise.reject(new Error("x")))).rejects.toThrow("x"); + expect(query).toHaveBeenLastCalledWith("ROLLBACK"); + expect(release).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/service-kit/src/with-school.ts b/packages/service-kit/src/with-school.ts new file mode 100644 index 0000000..719ee89 --- /dev/null +++ b/packages/service-kit/src/with-school.ts @@ -0,0 +1,19 @@ +import type { Pool, PoolClient } from "pg"; + +/** Runs fn in a transaction scoped to one school so row-level security applies to every query in it. */ +export async function withSchool(pool: Pool, schoolId: string, fn: (client: PoolClient) => Promise): Promise { + const client = await pool.connect(); + try { + await client.query("BEGIN"); + // is_local=true drops the setting at commit, so a pooled connection never leaks a tenant. + await client.query("select set_config('app.school_id', $1, true)", [schoolId]); + const result = await fn(client); + await client.query("COMMIT"); + return result; + } catch (err) { + await client.query("ROLLBACK"); + throw err; + } finally { + client.release(); + } +} diff --git a/packages/service-kit/tsconfig.json b/packages/service-kit/tsconfig.json new file mode 100644 index 0000000..cc2c43a --- /dev/null +++ b/packages/service-kit/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src"] +} diff --git a/stackable-app/pnpm-lock.yaml b/pnpm-lock.yaml similarity index 50% rename from stackable-app/pnpm-lock.yaml rename to pnpm-lock.yaml index eb843e3..91aff9f 100644 --- a/stackable-app/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,351 +7,431 @@ settings: importers: .: + devDependencies: + vitest: + specifier: ^3.2.4 + version: 3.2.7(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15) + + packages/contracts: + dependencies: + zod: + specifier: ^4.0.0 + version: 4.6.5 + devDependencies: + typescript: + specifier: ^5.9.0 + version: 5.9.3 + + packages/service-kit: + dependencies: + '@stackable/contracts': + specifier: workspace:* + version: link:../contracts + hono: + specifier: ^4.9.0 + version: 4.13.13 + jose: + specifier: ^6.0.0 + version: 6.2.12 + pg: + specifier: ^8.16.0 + version: 8.23.1 + zod: + specifier: ^4.0.0 + version: 4.6.5 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.1 + '@types/pg': + specifier: ^8.15.0 + version: 8.23.1 + typescript: + specifier: ^5.9.0 + version: 5.9.3 + + services/academics: + dependencies: + '@hono/node-server': + specifier: ^1.19.0 + version: 1.19.17(hono@4.13.13) + '@stackable/contracts': + specifier: workspace:* + version: link:../../packages/contracts + '@stackable/service-kit': + specifier: workspace:* + version: link:../../packages/service-kit + pg: + specifier: ^8.16.0 + version: 8.23.1 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.1 + '@types/pg': + specifier: ^8.15.0 + version: 8.23.1 + tsx: + specifier: ^4.20.0 + version: 4.23.15 + typescript: + specifier: ^5.9.0 + version: 5.9.3 + + services/ai: + dependencies: + '@hono/node-server': + specifier: ^1.19.0 + version: 1.19.17(hono@4.13.13) + '@stackable/contracts': + specifier: workspace:* + version: link:../../packages/contracts + '@stackable/service-kit': + specifier: workspace:* + version: link:../../packages/service-kit + pg: + specifier: ^8.16.0 + version: 8.23.1 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.1 + '@types/pg': + specifier: ^8.15.0 + version: 8.23.1 + tsx: + specifier: ^4.20.0 + version: 4.23.15 + typescript: + specifier: ^5.9.0 + version: 5.9.3 + + services/identity: + dependencies: + '@hono/node-server': + specifier: ^1.19.0 + version: 1.19.17(hono@4.13.13) + '@stackable/contracts': + specifier: workspace:* + version: link:../../packages/contracts + '@stackable/service-kit': + specifier: workspace:* + version: link:../../packages/service-kit + pg: + specifier: ^8.16.0 + version: 8.23.1 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.1 + '@types/pg': + specifier: ^8.15.0 + version: 8.23.1 + tsx: + specifier: ^4.20.0 + version: 4.23.15 + typescript: + specifier: ^5.9.0 + version: 5.9.3 + + stackable-app: dependencies: '@aws-sdk/client-s3': specifier: ^3.1068.0 - version: 3.1068.0 + version: 3.1146.0 '@aws-sdk/s3-request-presigner': specifier: ^3.1068.0 - version: 3.1068.0 + version: 3.1146.0 '@iconify-icon/react': specifier: ^3.0.3 - version: 3.0.3(react@19.2.4) + version: 3.0.4(react@19.3.0) '@infobip-api/sdk': specifier: ^0.3.2 version: 0.3.2 '@prisma/adapter-pg': specifier: ^7.8.0 - version: 7.8.0 + version: 7.10.0 '@prisma/client': specifier: ^7.8.0 - version: 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) + version: 7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3) '@react-email/components': specifier: ^1.0.8 - version: 1.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.0.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@react-email/render': specifier: ^2.0.4 - version: 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@sentry/nextjs': specifier: '11' - version: 11.0.0(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 11.4.0(@babel/core@7.29.7)(next@16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) '@supabase/ssr': specifier: ^0.9.0 - version: 0.9.0(@supabase/supabase-js@2.98.0) + version: 0.9.0(@supabase/supabase-js@2.117.2(@opentelemetry/api@1.9.1)) '@supabase/supabase-js': specifier: ^2.98.0 - version: 2.98.0 + version: 2.117.2(@opentelemetry/api@1.9.1) '@tanstack/react-query': specifier: ^5.101.0 - version: 5.101.0(react@19.2.4) + version: 5.104.1(react@19.3.0) '@tanstack/react-table': specifier: ^8.21.3 - version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 8.21.3(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@upstash/qstash': specifier: ^2.9.0 - version: 2.9.0 + version: 2.12.0 '@upstash/ratelimit': specifier: ^2.0.8 - version: 2.0.8(@upstash/redis@1.38.0) + version: 2.2.0(@upstash/redis@1.39.0) '@upstash/redis': specifier: ^1.38.0 - version: 1.38.0 + version: 1.39.0 axios: specifier: ^1.13.6 - version: 1.13.6 + version: 1.20.0 bcryptjs: specifier: ^3.0.3 version: 3.0.3 better-auth: - specifier: ^1.6.18 - version: 1.6.18(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(mysql2@3.15.3)(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.21.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 1.6.18 + version: 1.6.18(@opentelemetry/api@1.9.1)(@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3))(mysql2@3.15.3)(next@16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.1)(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@3.2.7(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)) clsx: specifier: ^2.1.1 version: 2.1.1 framer-motion: specifier: ^12.38.0 - version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) lucide-react: specifier: ^0.577.0 - version: 0.577.0(react@19.2.4) + version: 0.577.0(react@19.3.0) next: specifier: ^16.1.6 - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - next-auth: - specifier: 5.0.0-beta.30 - version: 5.0.0-beta.30(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) next-themes: specifier: ^0.4.6 - version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 0.4.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0) pg: specifier: ^8.21.0 - version: 8.21.0 + version: 8.23.1 pingram: specifier: ^1.0.1 - version: 1.0.1 + version: 1.0.36 posthog-js: specifier: ^1.434.15 - version: 1.434.15(@types/react@19.2.14)(preact-render-to-string@6.5.11(preact@10.24.3))(react@19.2.4) + version: 1.435.8(@types/react@19.3.0)(react@19.3.0) qrcode: specifier: ^1.5.4 version: 1.5.4 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.3.0 react-apexcharts: specifier: ^2.1.0 - version: 2.1.0(apexcharts@5.10.3)(react@19.2.4) + version: 2.1.1(apexcharts@7.8.0)(react@19.3.0) react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.3.0(react@19.3.0) resend: specifier: ^6.9.3 - version: 6.9.3(@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 6.32.0(@react-email/render@2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)) styled-components: specifier: ^6.3.11 - version: 6.3.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 6.5.3(react-dom@19.3.0(react@19.3.0))(react@19.3.0) zod: specifier: ^4.4.3 - version: 4.4.3 + version: 4.6.5 devDependencies: '@tailwindcss/postcss': specifier: ^4.2.1 - version: 4.2.1 + version: 4.3.3 '@types/bcryptjs': specifier: ^3.0.0 version: 3.0.0 '@types/node': specifier: ^25.3.5 - version: 25.3.5 + version: 25.9.9 '@types/pg': specifier: ^8.20.0 - version: 8.20.0 + version: 8.23.1 '@types/qrcode': specifier: ^1.5.6 version: 1.5.6 '@types/react': specifier: ^19.2.14 - version: 19.2.14 + version: 19.3.0 '@types/react-dom': specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) + version: 19.3.0(@types/react@19.3.0) babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 eslint: specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + version: 9.39.5(jiti@2.7.0) eslint-config-next: specifier: ^16.1.6 - version: 16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.3.8(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) postcss: specifier: ^8.5.8 - version: 8.5.8 + version: 8.5.28 prisma: specifier: ^7.8.0 - version: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3) tailwindcss: specifier: ^4.2.1 - version: 4.2.1 + version: 4.3.3 tsx: specifier: ^4.22.4 - version: 4.22.4 + version: 4.23.15 typescript: specifier: ^5.9.3 version: 5.9.3 packages: - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + '@alloc/quick-lru@5.3.0': + resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==} engines: {node: '>=10'} - '@auth/core@0.41.0': - resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - nodemailer: ^6.8.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - - '@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/checksums@3.1000.5': - resolution: {integrity: sha512-zOXUUnilC6lgCsQtp77p/QNPmRlTES9Xi6tlDwbR6kfC/kz5PCzZckgHWm5z+8DskdwuMAbFDq61x3zr10GEEQ==} + '@aws-sdk/checksums@3.1001.1': + resolution: {integrity: sha512-x12Q17KYlJAd3nKf8LV5LV0vt8sh8/6YfQLGPtrGnQf/tW4jqxPGq5GPpuVitpQYM3eUR4XB7CbxZf751NMbLw==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-s3@3.1068.0': - resolution: {integrity: sha512-lFgaIpxZvloNbJvQ337YPdMXhzI2zJdDw13nATVGnkAGNoNPx4ksD84AQAcuW75hsaaMaIuNmXU9sSx6+FTirA==} + '@aws-sdk/client-s3@3.1146.0': + resolution: {integrity: sha512-WY0YCBzxc4muFfY6UbGpA+oib0nGT/Px2aEPGYX7pp1la2OmDLChnK81mu8H7LdYvwGU1pLAsLIa/qgad0Wd7g==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.974.20': - resolution: {integrity: sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==} + '@aws-sdk/core@3.978.1': + resolution: {integrity: sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.46': - resolution: {integrity: sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==} + '@aws-sdk/credential-provider-env@3.972.72': + resolution: {integrity: sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.48': - resolution: {integrity: sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==} + '@aws-sdk/credential-provider-http@3.972.74': + resolution: {integrity: sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.53': - resolution: {integrity: sha512-ZfdhIOR41q8TcWEnUac+gCOb+O2LBWdHLmjedXpXz4IEFW2ppNuFcm6p0sMTavpM+zD5TYfpH5Gp7guRyqSgsQ==} + '@aws-sdk/credential-provider-ini@3.973.17': + resolution: {integrity: sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.52': - resolution: {integrity: sha512-9hu2oR0qH7Fst5Tzdx+UWxm+w5zCXtErTLtOOW5hwwQc170CLwOeniRxyFY6s9mHfGEfC5zFukNBdKBwJR8mhQ==} + '@aws-sdk/credential-provider-login@3.972.79': + resolution: {integrity: sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.55': - resolution: {integrity: sha512-zMGLa/dhESVqmCD7mmIFFKSwSFrJGScvCXcjvBZEVOOMauFS5JRQvLTMukFpMEFWiV6dTAlsen2ATDBulLPtbg==} + '@aws-sdk/credential-provider-node@3.972.84': + resolution: {integrity: sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.46': - resolution: {integrity: sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==} + '@aws-sdk/credential-provider-process@3.972.72': + resolution: {integrity: sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.52': - resolution: {integrity: sha512-nb2/n4o/HQf+FVpVbZe9vCTFngmuDoIsltMgLAtjixaKzvzhB4J8WSDFyWgnErgLHk55ctWH+I4PU+LIHhyffg==} + '@aws-sdk/credential-provider-sso@3.973.16': + resolution: {integrity: sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.52': - resolution: {integrity: sha512-lKj6aRSGbqLmpYmM24bY7a1Xmfcq2vkE3hv8CSPYfc1yCu0BPu/XEJ1L4Fm61MsU6ULLNSG8UGsffNoFUBjESA==} + '@aws-sdk/credential-provider-web-identity@3.972.78': + resolution: {integrity: sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.974.30': - resolution: {integrity: sha512-OaIhub+3yTgfFWPzKO8OzOZFIMUoJaiS5v67y3spQg7SoULGoMx4jKVBbE+uhnzkiZXQ+rEDS0RqrK4/aD1yJw==} + '@aws-sdk/middleware-sdk-s3@3.972.77': + resolution: {integrity: sha512-E7W2UOeUoc+lg3uIfR/dM7ZwusHwhBQrKMnlkRv4EXRR+C0YtV1pg25xC7GdZIhXH+NAMgZPCbE7o5to2cjFiw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.51': - resolution: {integrity: sha512-keQgcIUTcHL0Qn7guhsuLaxQU36r9norCrxgaPH4DNCwon4TPtXdI/UdYuycl9vj3Dlwc3YR1dfL3U+6iIwJ6w==} + '@aws-sdk/nested-clients@3.997.46': + resolution: {integrity: sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.20': - resolution: {integrity: sha512-IYJuLpXp2DEILVQpQOy0PMpkftv0AHEOCn52o0atyOaumA0CdWQ3klPyXdViGYLbNpESsVFMVybvHUeZAuiGxA==} + '@aws-sdk/s3-request-presigner@3.1146.0': + resolution: {integrity: sha512-5BEfVr2OYX2bwB45NSWAXPoTN5M2NPLOHohgKV+XfMNcL5TF7UQN4hAOVF4S6DwrA4BrI3MsAry7vqdZ2aJEjg==} engines: {node: '>=20.0.0'} - '@aws-sdk/s3-request-presigner@3.1068.0': - resolution: {integrity: sha512-XTUoaBQH3pUlegqdoo7vkrOvlcRhSz/EO0/JYy9qKz4jdsOnPUlPY0p+rKTl27F91Gjgip8OXopiOFxVeN3Yvg==} + '@aws-sdk/signature-v4-multi-region@3.996.47': + resolution: {integrity: sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.34': - resolution: {integrity: sha512-mx1L5qlumSOt/nKM3BFaHE2HVkWwz0i4Bw0pyYO42FfX/FeLlo8YI6csC0gSPprEk6fTIqI+CZN9RwUwKd5krQ==} + '@aws-sdk/token-providers@3.1138.0': + resolution: {integrity: sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1066.0': - resolution: {integrity: sha512-UqEUJq7dqa44hneLDUcX7UJy95cg8YqEWyakRpvIPnrNS3Mq+UlQHgCDGu5pvwAPtlIW4qcYbvW6reG6++FyvA==} + '@aws-sdk/types@3.974.6': + resolution: {integrity: sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==} engines: {node: '>=20.0.0'} - '@aws-sdk/types@3.973.12': - resolution: {integrity: sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==} + '@aws-sdk/xml-builder@3.972.41': + resolution: {integrity: sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-locate-window@3.965.7': - resolution: {integrity: sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/xml-builder@3.972.29': - resolution: {integrity: sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==} - engines: {node: '>=20.0.0'} - - '@aws/lambda-invoke-store@0.2.4': - resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.0': - resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} '@better-auth/core@1.6.18': @@ -430,31 +510,40 @@ packages: '@better-auth/utils@0.4.1': resolution: {integrity: sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==} + '@better-auth/utils@0.4.2': + resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==} + '@better-fetch/fetch@1.3.0': resolution: {integrity: sha512-Lgkl18IrUURFqa1nE38GNDWXf7XGzfxXQDCE/alCQV0yZ98YrPGtlmW61ch1T4YRt3lxrSAGA+Ft73FzuWWb3A==} - '@electric-sql/pglite-socket@0.1.1': - resolution: {integrity: sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==} + '@better-fetch/fetch@1.3.2': + resolution: {integrity: sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow==} + + '@electric-sql/pglite-socket@0.1.3': + resolution: {integrity: sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==} hasBin: true peerDependencies: - '@electric-sql/pglite': 0.4.1 + '@electric-sql/pglite': 0.4.3 - '@electric-sql/pglite-tools@0.3.1': - resolution: {integrity: sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==} + '@electric-sql/pglite-tools@0.3.3': + resolution: {integrity: sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==} peerDependencies: - '@electric-sql/pglite': 0.4.1 + '@electric-sql/pglite': 0.4.3 - '@electric-sql/pglite@0.4.1': - resolution: {integrity: sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==} + '@electric-sql/pglite@0.4.3': + resolution: {integrity: sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==} - '@emnapi/core@1.8.1': - resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@emotion/is-prop-valid@1.4.0': resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} @@ -462,165 +551,168 @@ packages: '@emotion/memoize@0.9.0': resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -643,12 +735,12 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + '@eslint/eslintrc@3.3.7': + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -659,18 +751,22 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@hono/node-server@1.19.11': - resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + '@hono/node-server@1.19.17': + resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -681,8 +777,8 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@iconify-icon/react@3.0.3': - resolution: {integrity: sha512-i4N/J0+UZWQu5Ty9Z+SxsNsuNdY7w2TnSuI65Qw9UrWudvnRodKfTUMvyg0hvxSZolqAE1YJVZu1HqgfpgPX9w==} + '@iconify-icon/react@3.0.4': + resolution: {integrity: sha512-0xhOSFqAOT3grFA6XpTsQcoapIEOfaFaCHjWaEsJITwLC7Hj/oL/2A118wpZNmEVKCtwwHf1hw2C+wgKujv4Dg==} peerDependencies: react: '>=18.0.0' @@ -693,152 +789,161 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-arm64@0.35.5': + resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-x64@0.35.5': + resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-freebsd-wasm32@0.35.5': + resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.4': + resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.3.4': + resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.3.4': + resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.3.4': + resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.3.4': + resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + '@img/sharp-libvips-linux-riscv64@1.3.4': + resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.3.4': + resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.3.4': + resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm64@0.35.5': + resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm@0.35.5': + resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} + engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-ppc64@0.35.5': + resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} + engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-riscv64@0.35.5': + resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-s390x@0.35.5': + resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-x64@0.35.5': + resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-arm64@0.35.5': + resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-x64@0.35.5': + resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-wasm32@0.35.5': + resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.5': + resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.5': + resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-ia32@0.35.5': + resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.5': + resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -856,15 +961,12 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@kurkle/color@0.3.4': - resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} - '@napi-rs/lzma-linux-x64-gnu@1.5.1': resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} engines: {node: ^22.20 || ^24.12 || >=25} @@ -872,78 +974,79 @@ packages: os: [linux] libc: [glibc] - '@napi-rs/wasm-runtime@0.2.12': - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@napi-rs/wasm-runtime@1.2.4': + resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - '@next/env@16.1.6': - resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} + '@next/env@16.3.8': + resolution: {integrity: sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==} - '@next/eslint-plugin-next@16.1.6': - resolution: {integrity: sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==} + '@next/eslint-plugin-next@16.3.8': + resolution: {integrity: sha512-eCR9RcLTZrVS+K3+VOhi0xMdUUGNuriCHHhU1ZoABC/QQ/Gp0OWbLKQxV2/9DkmihYdYRcQWFmZXW8M1AMUXYw==} - '@next/swc-darwin-arm64@16.1.6': - resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} + '@next/swc-darwin-arm64@16.3.8': + resolution: {integrity: sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.1.6': - resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} + '@next/swc-darwin-x64@16.3.8': + resolution: {integrity: sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.1.6': - resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} + '@next/swc-linux-arm64-gnu@16.3.8': + resolution: {integrity: sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.1.6': - resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} + '@next/swc-linux-arm64-musl@16.3.8': + resolution: {integrity: sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.1.6': - resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} + '@next/swc-linux-x64-gnu@16.3.8': + resolution: {integrity: sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.1.6': - resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} + '@next/swc-linux-x64-musl@16.3.8': + resolution: {integrity: sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.1.6': - resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} + '@next/swc-win32-arm64-msvc@16.3.8': + resolution: {integrity: sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.1.6': - resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} + '@next/swc-win32-x64-msvc@16.3.8': + resolution: {integrity: sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@noble/ciphers@2.2.0': - resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + '@noble/ciphers@2.4.0': + resolution: {integrity: sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==} engines: {node: '>= 20.19.0'} - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + '@noble/hashes@2.4.0': + resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} engines: {node: '>= 20.19.0'} - '@nodable/entities@2.2.0': - resolution: {integrity: sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -964,155 +1067,152 @@ packages: resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/semantic-conventions@1.41.1': - resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} engines: {node: '>=14'} - '@oxc-parser/binding-android-arm-eabi@0.143.0': - resolution: {integrity: sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==} + '@oxc-parser/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-393VtirINkae4XHBTkq/yQxhOwEr4V+r0et37P2fp+sYZSdYgZTRDPo5Um8vBh0QjMZ+RmaMy4PP0qwr4cTMwA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxc-parser/binding-android-arm64@0.143.0': - resolution: {integrity: sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==} + '@oxc-parser/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-iYFcExATJNZ0rZj0o02THbD1Nc9jAW0nVgIQPee6ejbSASpbZXlIMw8jxtpzaG14JQTWwGSZ6U5dEtRJXE6V3Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-parser/binding-darwin-arm64@0.143.0': - resolution: {integrity: sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==} + '@oxc-parser/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-fnU+DfUzi5/rFy0LzmipYRz06PAUUkOS8kCJQ3Eg6oqlunC1XZXoffFqxmdL51RmcbYtUG3P8DVG1U5fckoa2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-parser/binding-darwin-x64@0.143.0': - resolution: {integrity: sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==} + '@oxc-parser/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-Y3U2okryWTs7hDld6UhjJwLNf8/do+x4g4C+1U5SiGwrTLwbO83WJYJA8+l9iFSilWBgnfpd/+U76tfeNxUHzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-parser/binding-freebsd-x64@0.143.0': - resolution: {integrity: sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==} + '@oxc-parser/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-XkhkRrrPffEw+ZLv/d/5pEni0buxinm/E/N6fLbJCunD1DBbeCTtVP23ESV+4CWCWdSz397asks1ViGslpmWiQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': - resolution: {integrity: sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-MLzsRRbdfmdzo6JEhbC1yKU9ItfvM8tEf18HM32jCiNYZEz6J3bRBRl3cTkKR0vBdrk9ZVlcARXtwHccs+epDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': - resolution: {integrity: sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==} + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-HHerTEJz7Iy10Ji6BGsk4gqG6PU0UzFuBu91jIEOX89FnrI5ECWSEr7BLpkYQhp7U0QFtMnBhNfQKI0hYZK78w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm64-gnu@0.143.0': - resolution: {integrity: sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==} + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-9Bb8GyzZ2uSLzRDZ27usaKokr07mxiBe2dDF5b6bd2janmaJTT1gL51UfP7AU30yFiXrJvcRyFKREAgKwZYs0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-arm64-musl@0.143.0': - resolution: {integrity: sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==} + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-om7iDMUroI74A/lfRRWY9/Ev3VWrVsrUZ4tyvyh8raBey/sdcfyAD7tYWc+iykAiotnmftyKTpUKFr1QnaBrAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': - resolution: {integrity: sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==} + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-Lf1OL2lMTqwDMFC6jnRb30B+WYHKV0XT+bPHcFM6xHtNVwxpobiTzUIvKH6a0LWpvfwyru8Yu6zCGHqqFHH+gg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': - resolution: {integrity: sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==} + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-BYWSMQK+YJ9ZILStPcmmiSVq8KEG9Qdq/c4BHPC3rUEq/fEDCVjUIwdj5ZzjgZ9P9is9PKu0tcAXzq1ZXg4urQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-musl@0.143.0': - resolution: {integrity: sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==} + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-d2LqHNNNqdH185sRK5dPKWZu201JrNu9/CRFuVEuT+ht7n/QaVblEMWT9MALQPrDWYvhG10P+NSKJFquDWZ09w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-s390x-gnu@0.143.0': - resolution: {integrity: sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==} + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-8gBeHt5rA/8+pKhHl+zLggfa3L/V/L2at96p7ty6pBE7id6lFO19nFUc15u2PcjC4OYeYK465MKy1fhhbpFIGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-gnu@0.143.0': - resolution: {integrity: sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==} + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-ltgrSF6L+yPTVsIdaAyqKq3zTqzj/1VlNbxWZuxAe8flR3sWdyNsKvdXukoO8/ucJbLickhmieRBge+o/e8Ugw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-musl@0.143.0': - resolution: {integrity: sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==} + '@oxc-parser/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-LcMp6BvrBnewSfdkLHoSqidhoAq4syKOTBV5TYHWd+2VnGkfD/nPRRVIzTBt/Lkbt7pfOFDGtkiTntFS0p6sQQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxc-parser/binding-openharmony-arm64@0.143.0': - resolution: {integrity: sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==} + '@oxc-parser/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-jbhoMCslVxzRCRNQ8TSHxjYdSKRvk8O6ciLVDkahJvmyHDzBqba/bMQHM6ULKj/rgHNNbSzlIiakdx8SEfkJzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-parser/binding-win32-arm64-msvc@0.143.0': - resolution: {integrity: sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==} + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-JZYVlhv36mr/nertnRB7vchJECOBb1Lz6lltalILsB+o9M/is+H6BEDRLQKdqA4JKs6NjJszo46+iM5sfwj6jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-parser/binding-win32-ia32-msvc@0.143.0': - resolution: {integrity: sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==} + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-1X9oganvVsunRH0lH10UU05N+H9atpX6WsOybByUjwnfcd3h/0tYwvuv16XuHglrIUOnxV2TodbLVEwwafd64g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.143.0': - resolution: {integrity: sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==} + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-sw0M80/dn9rcpu+Rqqph/C+MjvDDce6MlXmFjsNQtPEymha0eJy3ml6+foSUTqFfpDIDyHZZ4eoJ43IoMe8bJw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxc-project/types@0.143.0': - resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} - - '@panva/hkdf@1.2.1': - resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} - '@posthog/browser-common@0.9.0': - resolution: {integrity: sha512-EWoVzs1vVY7ihpn8yOjj3jyjriHUEEMVSBNL/TW3hk09fTriWRe76vJXrRg0G0yjTYxHmtlSkNn4mOV24ZEzzA==} + '@posthog/browser-common@0.9.1': + resolution: {integrity: sha512-Dl8uV2OZAxfHfWyC7K0Y3HtT7ld+WGLbXIwgCgAGziOMGDIa/bepCRrCxy7IxsEzqY1HOrq/VDdcoNN7hBTSQQ==} - '@posthog/core@1.55.2': - resolution: {integrity: sha512-2mYxGbDmTLUs096VbcZqSWo0lrI48/BrX65r54NynxxFq409BOx9w73HxOAtOKZ1vblkdV6/GxQTe7KDcIQidg==} + '@posthog/core@1.55.3': + resolution: {integrity: sha512-XnmG9jo7WlxTeN4oCNWH4ESLQHR6ZPZ+mcq+8JxU62k5i1BAe3BSrybnmXHjqKp7e3rUvLK2bOzGZGSlfHvlNA==} - '@posthog/types@1.412.4': - resolution: {integrity: sha512-Q7lV9O9TbLngjOYw1ucm3bS3tn48xb1B4ZQdDy8gv7yfKe99hCTeWYG4xZ36nJM80VxpTF9TriiJt5hngDOkBg==} + '@posthog/types@1.413.0': + resolution: {integrity: sha512-tYAVzNojWckuG/wxBB0fwE70sFruQQq4xbhkci7eH2ARU3mMfUTkvpN1HwBMpm0AtbxidLiH0IFvagmdJ8opPw==} - '@prisma/adapter-pg@7.8.0': - resolution: {integrity: sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==} + '@prisma/adapter-pg@7.10.0': + resolution: {integrity: sha512-N7nwSor0HO1Kz6xBv0TPAjAPysKK0fac6p4fVN3ensLOuzc/83Fgmln5k92eK/cvzqdkSR/2kkAqlbcdwVrwpw==} - '@prisma/client-runtime-utils@7.8.0': - resolution: {integrity: sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==} + '@prisma/client-runtime-utils@7.10.0': + resolution: {integrity: sha512-cnCy7lUV8/CctgKVEmqAbSLAmwqJdE/qAlqTBk/0NDk59zEb2cZ0M0M0E4vVPnqbSEYudRroQDvOWfUZH6RIfw==} - '@prisma/client@7.8.0': - resolution: {integrity: sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==} + '@prisma/client@7.10.0': + resolution: {integrity: sha512-Ubw/QS9JGIBSBUsyxAUQuK/Jcu0Tsva7le7QbLd91Kix9yJvYDdj5QkwgEbbZniH80dd+sziQcALPc+HnvQC8Q==} engines: {node: ^20.19 || ^22.12 || >=24.0} peerDependencies: prisma: '*' @@ -1123,45 +1223,45 @@ packages: typescript: optional: true - '@prisma/config@7.8.0': - resolution: {integrity: sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==} + '@prisma/config@7.10.0': + resolution: {integrity: sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==} + + '@prisma/debug@7.10.0': + resolution: {integrity: sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==} '@prisma/debug@7.2.0': resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} - '@prisma/debug@7.8.0': - resolution: {integrity: sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==} + '@prisma/dev@0.24.17': + resolution: {integrity: sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==} - '@prisma/dev@0.24.3': - resolution: {integrity: sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==} + '@prisma/driver-adapter-utils@7.10.0': + resolution: {integrity: sha512-u8zkcRLlaryO652T4qavBg0HmzNW5tSKdsCn6hc1PhWAp/J6k0vrxLuUs+b9o+HcjsK7Dfa01o4OFSn0frauJA==} - '@prisma/driver-adapter-utils@7.8.0': - resolution: {integrity: sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==} + '@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3': + resolution: {integrity: sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==} - '@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a': - resolution: {integrity: sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==} + '@prisma/engines@7.10.0': + resolution: {integrity: sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==} - '@prisma/engines@7.8.0': - resolution: {integrity: sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==} + '@prisma/fetch-engine@7.10.0': + resolution: {integrity: sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==} - '@prisma/fetch-engine@7.8.0': - resolution: {integrity: sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==} + '@prisma/get-platform@7.10.0': + resolution: {integrity: sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==} '@prisma/get-platform@7.2.0': resolution: {integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==} - '@prisma/get-platform@7.8.0': - resolution: {integrity: sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==} - '@prisma/query-plan-executor@7.2.0': resolution: {integrity: sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==} - '@prisma/streams-local@0.1.2': - resolution: {integrity: sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==} - engines: {bun: '>=1.3.6', node: '>=22.0.0'} + '@prisma/streams-local@0.1.11': + resolution: {integrity: sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==} + engines: {bun: '>=1.2.0', node: '>=22.0.0'} - '@prisma/studio-core@0.27.3': - resolution: {integrity: sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==} + '@prisma/studio-core@0.33.0': + resolution: {integrity: sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==} engines: {node: ^20.19 || ^22.12 || >=24.0, pnpm: '8'} peerDependencies: '@types/react': ^18.0.0 || ^19.0.0 @@ -1242,104 +1342,127 @@ packages: '@types/react': optional: true - '@react-email/body@0.2.1': - resolution: {integrity: sha512-ljDiQiJDu/Fq//vSIIP0z5Nuvt4+DX1RqGasstChDGJB/14ogd4VdNS9aacoede/ZjGy3o3Qb+cxyS+XgM6SwQ==} + '@react-email/body@0.3.0': + resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/button@0.2.1': resolution: {integrity: sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-block@0.2.1': resolution: {integrity: sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-inline@0.0.6': resolution: {integrity: sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/column@0.0.14': resolution: {integrity: sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/components@1.0.8': - resolution: {integrity: sha512-zY81ED6o5MWMzBkr9uZFuT24lWarT+xIbOZxI6C9dsFmCWBczM8IE1BgOI8rhpUK4JcYVDy1uKxYAFqsx2Bc4w==} + '@react-email/components@1.0.12': + resolution: {integrity: sha512-tH18JhPDWgE+3jnYkzyB6ZrZdfNnEsFe4PwmuXmlOw4NGIysP8wPY5aXZg++pTG9qUabXg1nzX/FGHGkObH8xQ==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/container@0.0.16': resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/font@0.0.10': resolution: {integrity: sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/head@0.0.13': resolution: {integrity: sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/heading@0.0.16': resolution: {integrity: sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/hr@0.0.12': resolution: {integrity: sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/html@0.0.12': resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/img@0.0.12': resolution: {integrity: sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/link@0.0.13': resolution: {integrity: sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/markdown@0.0.18': resolution: {integrity: sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/preview@0.0.14': resolution: {integrity: sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/render@2.0.6': + resolution: {integrity: sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog==} + engines: {node: '>=20.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@2.0.4': - resolution: {integrity: sha512-kht2oTFQ1SwrLpd882ahTvUtNa9s53CERHstiTbzhm6aR2Hbykp/mQ4tpPvsBGkKAEvKRlDEoooh60Uk6nHK1g==} + '@react-email/render@2.1.0': + resolution: {integrity: sha512-F+zE3O6d6sW6Aj2UjvZAA17R7tJKM7kcq2mgV6k4HCT8jeLLFaVP2txMtH1lgqYFRMZ0Gxsd37q2PRyiXLXXxA==} engines: {node: '>=20.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -1348,30 +1471,33 @@ packages: '@react-email/row@0.0.13': resolution: {integrity: sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/section@0.0.17': resolution: {integrity: sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/tailwind@2.0.5': - resolution: {integrity: sha512-7Ey+kiWliJdxPMCLYsdDts8ffp4idlP//w4Ui3q/A5kokVaLSNKG8DOg/8qAuzWmRiGwNQVOKBk7PXNlK5W+sg==} + '@react-email/tailwind@2.0.7': + resolution: {integrity: sha512-kGw80weVFXikcnCXbigTGXGWQ0MRCSYNCudcdkWxebkWYd0FG6/NPoN3V1p/u68/4+NxZwYPVi2fhnp0x23HdA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: - '@react-email/body': 0.2.1 - '@react-email/button': 0.2.1 - '@react-email/code-block': 0.2.1 - '@react-email/code-inline': 0.0.6 - '@react-email/container': 0.0.16 - '@react-email/heading': 0.0.16 - '@react-email/hr': 0.0.12 - '@react-email/img': 0.0.12 - '@react-email/link': 0.0.13 - '@react-email/preview': 0.0.14 - '@react-email/text': 0.1.6 + '@react-email/body': '>=0' + '@react-email/button': '>=0' + '@react-email/code-block': '>=0' + '@react-email/code-inline': '>=0' + '@react-email/container': '>=0' + '@react-email/heading': '>=0' + '@react-email/hr': '>=0' + '@react-email/img': '>=0' + '@react-email/link': '>=0' + '@react-email/preview': '>=0' + '@react-email/text': '>=0' react: ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@react-email/body': @@ -1398,6 +1524,7 @@ packages: '@react-email/text@0.1.6': resolution: {integrity: sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -1419,141 +1546,141 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.63.5': - resolution: {integrity: sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg==} + '@rollup/rollup-android-arm-eabi@4.64.0': + resolution: {integrity: sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.63.5': - resolution: {integrity: sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg==} + '@rollup/rollup-android-arm64@4.64.0': + resolution: {integrity: sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.63.5': - resolution: {integrity: sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg==} + '@rollup/rollup-darwin-arm64@4.64.0': + resolution: {integrity: sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.63.5': - resolution: {integrity: sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ==} + '@rollup/rollup-darwin-x64@4.64.0': + resolution: {integrity: sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.63.5': - resolution: {integrity: sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg==} + '@rollup/rollup-freebsd-arm64@4.64.0': + resolution: {integrity: sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.63.5': - resolution: {integrity: sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ==} + '@rollup/rollup-freebsd-x64@4.64.0': + resolution: {integrity: sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.63.5': - resolution: {integrity: sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.64.0': + resolution: {integrity: sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.63.5': - resolution: {integrity: sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw==} + '@rollup/rollup-linux-arm-musleabihf@4.64.0': + resolution: {integrity: sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.63.5': - resolution: {integrity: sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw==} + '@rollup/rollup-linux-arm64-gnu@4.64.0': + resolution: {integrity: sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.63.5': - resolution: {integrity: sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ==} + '@rollup/rollup-linux-arm64-musl@4.64.0': + resolution: {integrity: sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.63.5': - resolution: {integrity: sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ==} + '@rollup/rollup-linux-loong64-gnu@4.64.0': + resolution: {integrity: sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.63.5': - resolution: {integrity: sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA==} + '@rollup/rollup-linux-loong64-musl@4.64.0': + resolution: {integrity: sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.63.5': - resolution: {integrity: sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA==} + '@rollup/rollup-linux-ppc64-gnu@4.64.0': + resolution: {integrity: sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.63.5': - resolution: {integrity: sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg==} + '@rollup/rollup-linux-ppc64-musl@4.64.0': + resolution: {integrity: sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.63.5': - resolution: {integrity: sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg==} + '@rollup/rollup-linux-riscv64-gnu@4.64.0': + resolution: {integrity: sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.63.5': - resolution: {integrity: sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ==} + '@rollup/rollup-linux-riscv64-musl@4.64.0': + resolution: {integrity: sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.63.5': - resolution: {integrity: sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w==} + '@rollup/rollup-linux-s390x-gnu@4.64.0': + resolution: {integrity: sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.63.5': - resolution: {integrity: sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA==} + '@rollup/rollup-linux-x64-gnu@4.64.0': + resolution: {integrity: sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.63.5': - resolution: {integrity: sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA==} + '@rollup/rollup-linux-x64-musl@4.64.0': + resolution: {integrity: sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.63.5': - resolution: {integrity: sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig==} + '@rollup/rollup-openbsd-x64@4.64.0': + resolution: {integrity: sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.63.5': - resolution: {integrity: sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g==} + '@rollup/rollup-openharmony-arm64@4.64.0': + resolution: {integrity: sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.63.5': - resolution: {integrity: sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw==} + '@rollup/rollup-win32-arm64-msvc@4.64.0': + resolution: {integrity: sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.63.5': - resolution: {integrity: sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw==} + '@rollup/rollup-win32-ia32-msvc@4.64.0': + resolution: {integrity: sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.63.5': - resolution: {integrity: sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw==} + '@rollup/rollup-win32-x64-gnu@4.64.0': + resolution: {integrity: sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.63.5': - resolution: {integrity: sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw==} + '@rollup/rollup-win32-x64-msvc@4.64.0': + resolution: {integrity: sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg==} cpu: [x64] os: [win32] @@ -1563,56 +1690,59 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} - '@sentry/browser-utils@11.0.0': - resolution: {integrity: sha512-ZvGyfDoVeSuaAF78vSeYOFcibUCpQfYlUVpqE+B0RANzoSCBg3JimRa+cDYB/rq36PjRwINk4u2viK2JdO320w==} + '@sentry/browser-utils@11.4.0': + resolution: {integrity: sha512-RPQefmLDslO8KsRiSuXNXcReAhJy0rEVi2pbez5HpEn2adipuhgebwDMulac6RYoIUa8liFcBueru0ancW1vhA==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/browser@11.0.0': - resolution: {integrity: sha512-a09jUpx4OBebmY0XLYXLhfvkCIken8xoncjmOmbPIa3ibUHI6ZJVyh3jAzMu56LY9xfJRV5bOfp92yf41Yr6dQ==} + '@sentry/browser@11.4.0': + resolution: {integrity: sha512-jhq5t6nw8KdBq8i3Uyq5vBCHXehtEM+F5f/OoVxuPS2TtSbGizGjqzhAi24EmTNs4DRlQgYpKPdTOBDTMv49HA==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/bundler-plugins@11.0.0': - resolution: {integrity: sha512-Oe4/8CbsYrqdjTt91JFMBQza59IWUfSwPrlHGbbJT+qVCyebX2bykxv3mfa6Ty/Gmt8+gva/DI6Kjcyb9EgXBg==} + '@sentry/bundler-plugins@11.4.0': + resolution: {integrity: sha512-THgOMIaA0AMVF4TiCIbugK2jlLYCBIhRF0gclrj7Fte3RMZytkXeH8XeoaR6C6MJTyLCNkEOEccwX9Jj2xOc8g==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} peerDependencies: + '@babel/core': ^7.18.5 rollup: '>=3.2.0' webpack: '>=5.1.0' peerDependenciesMeta: + '@babel/core': + optional: true rollup: optional: true webpack: optional: true - '@sentry/conventions@0.23.0': - resolution: {integrity: sha512-+Euyo7CNVecoOusgUnq27oV97oshifgceKfnGnuKfXrPUVWA/cisvii3E1rlvy2yBrKvl9K1gCEo1Jqog5zjEQ==} + '@sentry/conventions@0.25.0': + resolution: {integrity: sha512-7SouCAX3qx+AzLsxb6gbl3dKVWNhp2wFjdYQ/T90sGwqPPQUoZVA9YwsWwvx07hqcCiSqFW9mUAzyUftASnuhQ==} engines: {node: '>=14'} - '@sentry/core@11.0.0': - resolution: {integrity: sha512-Sdmg21POKmNwZDz8pH/2FtilWeryh3yxhRxbfA1ph8SnX2DkXoqX6G+uXyjCSYagW2pefVklmSNeWHmUlSqioA==} + '@sentry/core@11.4.0': + resolution: {integrity: sha512-kAhK8XVJuzu1IKx9uSu04OISxTU9BcIyDVOWbY/ATUCYr5Z6L7GRBtOGOlYCbxEuPtdZCw5TrbphnBsyO9fuTA==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/feedback@11.0.0': - resolution: {integrity: sha512-ZdUE6qSfMH4zturLHxg8idfI8cA+JdfrisqUQ6hE6MKaIAAZysNIPvvFysE2WQDjFbj3puS1iZm0jg0ym7kA5g==} + '@sentry/feedback@11.4.0': + resolution: {integrity: sha512-vGcTGvNMgaL5J+LCwK+HVpubU/4/3TRAexVaJi0VcZmr9LnoGiZlWAQunjvWNsabvvkXs120WbXYc8nHysRNzw==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/nextjs@11.0.0': - resolution: {integrity: sha512-ZiJrzm0hKf9plrRklaLU1QEGpXwYCPe18AzMEhfSNkorP3qJdi5X9xnZV52ap6zB4ypPru88aXUQtDGBdpl4cQ==} + '@sentry/nextjs@11.4.0': + resolution: {integrity: sha512-MrEb0n9KsLfxjb+nX+zluPQH7ojKt2HqWkBU6CCi7pI5/tBZOXPTuQ74vkuyOEza5IhCmHxwevEdFIcBFPWJ4Q==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} peerDependencies: next: ^14.0 || ^15.0.0-rc.0 || ^16.0.0-0 - '@sentry/node@11.0.0': - resolution: {integrity: sha512-ZHk2wYvnKPfaq05YHnva/a6EzZDLTG0EStcWnQdjSae/OxluwCfOfMqwlsKxaY8KDUkyqokTCUPqJITZpUH1Jw==} + '@sentry/node@11.4.0': + resolution: {integrity: sha512-fW587LsZnsuGt1V++59xFCtZ/7jGO3GQq1F+l/Jx5DS18QApXQ99vlfS9H5bxuWixfi5/22ea9M8XjFpkkuzQg==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/opentelemetry@11.0.0': - resolution: {integrity: sha512-m2EtgppTtN6MxW0dKA45aZMI/xoFZYi2I2cvTIenFGTGLfxyjk6+YCbg0Jf/wdUWoZjR0k6kBBBIbv+zpuvTOA==} + '@sentry/opentelemetry@11.4.0': + resolution: {integrity: sha512-GGDmV+VIGU1p3KGtgR32wrRJejSfEuafbEcSUK7Ww5oxDMYJyHXRSbFjVvGMoAG1ezTl0U6WtMUoWmalsfYHZg==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} peerDependencies: '@opentelemetry/api': ^1.9.0 - '@sentry/react@11.0.0': - resolution: {integrity: sha512-YMF4t9659byD3rYZ9zjE5vdL3NUVFBEQfHzbMJf4kow7h7eyDdQi8GN4K1qsOFBim5C/7/ry0dkP1r4p7qtEWA==} + '@sentry/react@11.4.0': + resolution: {integrity: sha512-8HfNapqjif93dcQ2E6rH/TUyTOSaRe7EOfIKyjcXdkktK2DISbvAHdzVT2ctV9DQ8zBxuUaCzzHcF7s18EYf2w==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} peerDependencies: react: 17.x || 18.x || 19.x @@ -1621,163 +1751,159 @@ packages: react-router: optional: true - '@sentry/replay-canvas@11.0.0': - resolution: {integrity: sha512-cWAx4Fc+T36w4SwN0Ee35xVLrx+nt2kk2An3gKPqgRU0HwxA1w14fKAxcXfKlJcy/K7nnNMgerznvWKgBObenw==} + '@sentry/replay-canvas@11.4.0': + resolution: {integrity: sha512-zfsf90ckrOl/+Z4D4kg4MNY46V4jzUFSZolm+RcrdobS4qikE4xBMvDy1bKZHk+LKfj+K6dxjgZQbasgzHcZ6w==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/replay@11.0.0': - resolution: {integrity: sha512-4/36Iw288SaWEwLjhpLRjCjL2UmSNEyU0TvLrAlzRAoYdksneEe7iQTSe7WD257buSgop0m+lauGRHQf7fD9qg==} + '@sentry/replay@11.4.0': + resolution: {integrity: sha512-DdgAGaFLOyixQEWGuw8kKcGAVZV8RVVX2kMVV5WqSOMI9if7lhvXdhV+3L0JnZRX7X/6RBqYxYH/C1E0hK43qg==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/server-runtime-injection@11.0.0': - resolution: {integrity: sha512-AE39xdGbi8WmcB9LtITuvJkp+sYxkPAR+3lRG4MYrJLVQS+YeYfn/AaZdEXOhnOagdpozsDbpEneGJn6vD8p6Q==} + '@sentry/server-runtime-injection@11.4.0': + resolution: {integrity: sha512-8IaiqIJuJySztjxdC0UXP3lHlBAr8HsI8kUMEDV7REYhgmz2+B4BmKyyvaZfeasJlAvzkvSOLZ/Xco7kPikZdQ==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/server-utils@11.0.0': - resolution: {integrity: sha512-XlKDtLoNqOhI+TgCNywan/tFy0yMRzw5+8mdp96KNHtF/YwO9s6c7qZcBG0x8+XDyC0EoNXDlBPJOMEZo5lD4Q==} + '@sentry/server-utils@11.4.0': + resolution: {integrity: sha512-GA4RvjRXyKFawD7Zi5AlgHPvm9Q8d9VeWer6g9qj4mDavi+VuNFhRmhgsv1qU1l3hlUeMYCpbKhpDOaOH/TcoQ==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/vercel-edge@11.0.0': - resolution: {integrity: sha512-tcDbhQygLnTeU9KNBjO4xYiVt5LIx7zsV7ywKznun6hQc+oWnJYbWrEJd+4ERSN5n9S368rX7Q7CDAmb8ioj+g==} + '@sentry/vercel-edge@11.4.0': + resolution: {integrity: sha512-0C56mHahJLpbIOD7eQxUWtDs+IU8qWLJO0ZsFH2/bIWEIldvEeLAucEyfBmTcEwYW6/Uen/HN0T0SBt/8co1fA==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@smithy/core@3.24.7': - resolution: {integrity: sha512-KoUi4M1f3BG6kzN1FnCwL7oyFptTbyBJKjR6yhSib+JHRdUmM1o+VwsFtJ66NZCkCzVfJMWRHJNo0R0jznp0Pg==} + '@smithy/core@3.35.1': + resolution: {integrity: sha512-i4YPS4B6ts7bjn7UwLnGjiZdprOvHvgGobFZsYK3GIY3E5hIqtj0rReU69BcTpGp+fvtraSNXeG1l+jtJvF55w==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.3.9': - resolution: {integrity: sha512-ZlfJ/4Fa3jYb+3eaohPfG9utX9HmdhFNcFtpoGAhUhdynAOmGXtmigbi7eEiONKM+ykHw8RwKuDEb85Lx7t7fA==} + '@smithy/credential-provider-imds@4.5.2': + resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.4.7': - resolution: {integrity: sha512-NslaM2ir0N2hisDmzXLstPaVINZheh8SokyOC++kzFPloZucL2R7Y7bS57mSzx/1Fc/fqmn7twjkeezTTrV0EA==} + '@smithy/fetch-http-handler@5.8.0': + resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} 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/node-http-handler@4.7.8': - resolution: {integrity: sha512-f+DbsWUwSbtMu1a/j8Y93KiU1SRg9nyzfjereqn1BJ33QOTUXxdlYvVXMhAYl1vuR1Kmna5aIJe09KSIfyFNYw==} + '@smithy/node-http-handler@4.12.1': + resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.4.7': - resolution: {integrity: sha512-LwQZazFayImv+IOm0S0enoLeUJwmAlhGC5O6YCcLWezyu08dF46GOxPOq35OpBIHkgd7OvNvBStIFwVNyrvoBw==} + '@smithy/signature-v4@5.7.4': + resolution: {integrity: sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==} engines: {node: '>=18.0.0'} - '@smithy/types@4.14.4': - resolution: {integrity: sha512-B2S9+UGm1+/pHkcx3ZoLVX1a+pmSk8rqxRR+ZsNqZaJ5q9FWX9AFGQVM4qG5+OBeQUZVy99HY8HqW8gK/wgXzQ==} + '@smithy/types@4.19.0': + resolution: {integrity: sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==} 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-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.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==} - '@supabase/auth-js@2.98.0': - resolution: {integrity: sha512-GBH361T0peHU91AQNzOlIrjUZw9TZbB9YDRiyFgk/3Kvr3/Z1NWUZ2athWTfHhwNNi8IrW00foyFxQD9IO/Trg==} - engines: {node: '>=20.0.0'} + '@supabase/auth-js@2.117.2': + resolution: {integrity: sha512-Z3WnGwrphYZubrLbxp5Iv0tLA9A5GhvKzJ/ZXglxqwd2QEH2R4dXRltjVJ8sIn4xEW2BSFIGT0yALVJIgZeDYw==} + engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.98.0': - resolution: {integrity: sha512-N/xEyiNU5Org+d+PNCpv+TWniAXRzxIURxDYsS/m2I/sfAB/HcM9aM2Dmf5edj5oWb9GxID1OBaZ8NMmPXL+Lg==} - engines: {node: '>=20.0.0'} + '@supabase/functions-js@2.117.2': + resolution: {integrity: sha512-6DT4ZIjmZxa9ANKaBIrjc86AyKrX8M376ynvNeOBBVad3eP7x3UHqWfDR2ynFkHGzCuOpr1A+kObmnahxuwuog==} + engines: {node: '>=22.0.0'} - '@supabase/postgrest-js@2.98.0': - resolution: {integrity: sha512-v6e9WeZuJijzUut8HyXu6gMqWFepIbaeaMIm1uKzei4yLg9bC9OtEW9O14LE/9ezqNbSAnSLO5GtOLFdm7Bpkg==} - engines: {node: '>=20.0.0'} + '@supabase/phoenix@0.4.5': + resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/realtime-js@2.98.0': - resolution: {integrity: sha512-rOWt28uGyFipWOSd+n0WVMr9kUXiWaa7J4hvyLCIHjRFqWm1z9CaaKAoYyfYMC1Exn3WT8WePCgiVhlAtWC2yw==} - engines: {node: '>=20.0.0'} + '@supabase/postgrest-js@2.117.2': + resolution: {integrity: sha512-V1Qhn+M8xzJqCasOxHZ2KG19Fj39PxU7weEAmOK65/KTsWCRXboB4hcQwFvxRvM7ZikeP9IZWNvQkfjvSxzYFw==} + engines: {node: '>=22.0.0'} + + '@supabase/realtime-js@2.117.2': + resolution: {integrity: sha512-lYXSAIg3eAKA58riUED6Vb+TCzF8jtx18uOoiCaVJ7ZMre6FWBpwB1MPeW+B6vykJT/hPdiuqwcSB7ILMS21ew==} + engines: {node: '>=22.0.0'} '@supabase/ssr@0.9.0': resolution: {integrity: sha512-UFY6otYV3yqCgV+AyHj80vNkTvbf1Gas2LW4dpbQ4ap6p6v3eB2oaDfcI99jsuJzwVBCFU4BJI+oDYyhNk1z0Q==} peerDependencies: '@supabase/supabase-js': ^2.97.0 - '@supabase/storage-js@2.98.0': - resolution: {integrity: sha512-tzr2mG+v7ILSAZSfZMSL9OPyIH4z1ikgQ8EcQTKfMRz4EwmlFt3UnJaGzSOxyvF5b+fc9So7qdSUWTqGgeLokQ==} - engines: {node: '>=20.0.0'} + '@supabase/storage-js@2.117.2': + resolution: {integrity: sha512-8gAJoVaxZa/War2kFRfJMxGk4M190Q7lJ70BofwGlRxU/u9pIodd6XyTvmXkxOKXWuIYtkDaYPZrIAUt1T/7FQ==} + engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.98.0': - resolution: {integrity: sha512-Ohc97CtInLwZyiSASz7tT9/Abm/vqnIbO9REp+PivVUII8UZsuI3bngRQnYgJdFoOIwvaEII1fX1qy8x0CyNiw==} - engines: {node: '>=20.0.0'} + '@supabase/supabase-js@2.117.2': + resolution: {integrity: sha512-eSG2VKnHR+Clp1PmidZ1/weJ8PJwoybjva3L2GgKqFG4YDS1Iqmc61psKGZP5xw6OMT2O7ZorPR42PY6q1BOXg==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} - '@tailwindcss/node@4.2.1': - resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - '@tailwindcss/oxide-android-arm64@4.2.1': - resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.1': - resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.1': - resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.1': - resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': - resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': - resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.1': - resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.1': - resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.1': - resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.1': - resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1788,30 +1914,30 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': - resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.1': - resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.1': - resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} engines: {node: '>= 20'} - '@tailwindcss/postcss@4.2.1': - resolution: {integrity: sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==} + '@tailwindcss/postcss@4.3.3': + resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - '@tanstack/query-core@5.101.0': - resolution: {integrity: sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==} + '@tanstack/query-core@5.104.1': + resolution: {integrity: sha512-TiRghcrGkUTE+M4JBxvpPSpBhxvIzQairw+cksrHYGr+2uwC77yW1SY9nYxnYRYOppvucf8rEg6b5byoz4rTpw==} - '@tanstack/react-query@5.101.0': - resolution: {integrity: sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==} + '@tanstack/react-query@5.104.1': + resolution: {integrity: sha512-Zz70EgjahNI7aCz8BZxM3vUGU44ycxEAzMuCUjLeA7SVhhOkg3ZgPjPJPvHjs6Rmky/dtmnu1WzjGHNHmhCoZQ==} peerDependencies: react: ^18 || ^19 @@ -1826,213 +1952,266 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} '@types/bcryptjs@3.0.0': resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.0.3': + resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==} + + '@types/d3-color@3.1.0': + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + + '@types/d3-delaunay@6.0.1': + resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==} + + '@types/d3-format@3.0.1': + resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-interpolate@3.0.1': + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.2': + resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time-format@2.1.0': + resolution: {integrity: sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==} + + '@types/d3-time@3.0.0': + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@25.3.5': - resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==} + '@types/lodash@4.17.25': + resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} + + '@types/node@24.19.1': + resolution: {integrity: sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==} - '@types/pg@8.20.0': - resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} + '@types/node@25.9.9': + resolution: {integrity: sha512-b4e2xxj/yMeT2hNlD6x7zNFiQK2Dlmkb9CQ6v3/1G2RezKN4YS2lcEv8lzGm1mhs4f2lM4yNAMI/zUDBZBBt7w==} - '@types/phoenix@1.6.7': - resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==} + '@types/pg@8.23.1': + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} peerDependencies: - '@types/react': ^19.2.0 + '@types/react': ^19.3.0 - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - - '@types/stylis@4.2.7': - resolution: {integrity: sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==} + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@typescript-eslint/eslint-plugin@8.56.1': - resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.56.1 + '@typescript-eslint/parser': ^8.71.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.56.1': - resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.56.1': - resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.56.1': - resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.56.1': - resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.56.1': - resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.56.1': - resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.56.1': - resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.56.1': - resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.56.1': - resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] os: [android] - '@unrs/resolver-binding-android-arm64@1.11.1': - resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} cpu: [arm64] os: [android] - '@unrs/resolver-binding-darwin-arm64@1.11.1': - resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} cpu: [arm64] os: [darwin] - '@unrs/resolver-binding-darwin-x64@1.11.1': - resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} cpu: [x64] os: [darwin] - '@unrs/resolver-binding-freebsd-x64@1.11.1': - resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} cpu: [x64] os: [freebsd] - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} cpu: [arm64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} cpu: [arm64] os: [linux] libc: [musl] - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} cpu: [riscv64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} cpu: [riscv64] os: [linux] libc: [musl] - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} cpu: [s390x] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} cpu: [x64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} cpu: [x64] os: [linux] libc: [musl] - '@unrs/resolver-binding-wasm32-wasi@1.11.1': - resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} cpu: [arm64] os: [win32] - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} cpu: [ia32] os: [win32] - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} cpu: [x64] os: [win32] @@ -2040,32 +2219,97 @@ packages: resolution: {integrity: sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ==} engines: {node: '>=16.0.0'} - '@upstash/qstash@2.9.0': - resolution: {integrity: sha512-RFvWB98ot5SXUZFIV/Av6hYdS+yu700kg+azUaJqV/fqgylUrWkYnCTOE4DJgdMHUEb0l7tH2Xhl70Zd4Q4zHw==} + '@upstash/qstash@2.12.0': + resolution: {integrity: sha512-LRFuMAqdDlVtCnDRLS00DM4lsE6JeP1A696lUcuUdWEXM9Sb1lhKYSuK2gHkG5VBxreR4QJNs909AGabAfSRdg==} + + '@upstash/ratelimit@2.2.0': + resolution: {integrity: sha512-gSydXspG6TJKTh71/gGGv4IGDjD9rXglQfm2jAue2kh4bcfWtEAsG1NW4OqzZd/hubJPojsbOU6AMdS6Xqh0tQ==} + peerDependencies: + '@upstash/redis': ^1.38.2 + + '@upstash/redis@1.39.0': + resolution: {integrity: sha512-VwkpYTdxSfjSulQS5h16NHtg/1UEheBfvez2AeGKinAujOoGXhXCcohQRzsQ/d2g/XzbjzTVGhG8Ds8uOI0Mxg==} + + '@visx/curve@4.0.1-alpha.0': + resolution: {integrity: sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==} + + '@visx/event@4.0.1-alpha.0': + resolution: {integrity: sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==} + + '@visx/grid@4.0.1-alpha.0': + resolution: {integrity: sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/group@4.0.1-alpha.0': + resolution: {integrity: sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/point@4.0.1-alpha.0': + resolution: {integrity: sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==} + + '@visx/responsive@4.0.1-alpha.0': + resolution: {integrity: sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/scale@4.0.1-alpha.0': + resolution: {integrity: sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==} + + '@visx/shape@4.0.1-alpha.0': + resolution: {integrity: sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/vendor@4.0.0-alpha.0': + resolution: {integrity: sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==} + + '@vitest/expect@3.2.7': + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} - '@upstash/ratelimit@2.0.8': - resolution: {integrity: sha512-YSTMBJ1YIxsoPkUMX/P4DDks/xV5YYCswWMamU8ZIfK9ly6ppjRnVOyBhMDXBmzjODm4UQKcxsJPvaeFAijp5w==} + '@vitest/mocker@3.2.7': + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==} peerDependencies: - '@upstash/redis': ^1.34.3 + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} - '@upstash/redis@1.38.0': - resolution: {integrity: sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg==} + '@vitest/runner@3.2.7': + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==} - '@yr/monotone-cubic-spline@1.0.3': - resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==} + '@vitest/snapshot@3.2.7': + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==} + + '@vitest/spy@3.2.7': + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==} + + '@vitest/utils@3.2.7': + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -2078,11 +2322,12 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - anynum@1.0.0: - resolution: {integrity: sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==} + apex-commons@0.8.1: + resolution: {integrity: sha512-F7fqJVh0smeTu1d86uuRSNNZvaM/l11GNQMbu6oAiSvuXp+K8KCufg0oZTy2VQBEEipRLAdFqkHnphlVpTG0Gw==} - apexcharts@5.10.3: - resolution: {integrity: sha512-wwvkSLsodNOc/rHo5MJsn3GPM4Krc5Gs0zKX4Lfzq4LohcTbyKylYUGEqJFmXXxGR7yLbZQz31sB5RTqT5mv1g==} + apexcharts@7.8.0: + resolution: {integrity: sha512-UxzR7IDXs3HDaOoxTsdn7L2dw9TdYp347uiU7WvjKuzCQqODmO7l63rxMmLwtbYrZPd+0Vd7v5VINzJgPyqmIw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2095,8 +2340,8 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + array-includes@3.2.0: + resolution: {integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==} engines: {node: '>= 0.4'} array.prototype.findlast@1.2.5: @@ -2123,6 +2368,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -2141,12 +2390,12 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} - axe-core@4.11.1: - resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + axe-core@4.13.0: + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} engines: {node: '>=4'} - axios@1.13.6: - resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==} + axios@1.20.0: + resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -2162,8 +2411,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + baseline-browser-mapping@2.11.27: + resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -2241,25 +2490,25 @@ packages: zod: optional: true - better-result@2.9.2: - resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==} + better-result@2.10.0: + resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==} bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + 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==} + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2271,12 +2520,16 @@ packages: magicast: optional: true + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -2291,24 +2544,28 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - camelize@1.0.1: - resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001814: + resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} - caniuse-lite@1.0.30001777: - resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chart.js@4.5.1: - resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} - engines: {pnpm: '>=8'} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -2339,6 +2596,9 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + confbox@0.3.1: + resolution: {integrity: sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2353,18 +2613,56 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-js@4.2.0: - resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - css-color-keywords@1.0.0: - resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} - engines: {node: '>=4'} + d3-array@3.2.1: + resolution: {integrity: sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==} + engines: {node: '>=12'} - css-to-react-native@3.2.0: - resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.2: + resolution: {integrity: sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.0: + resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -2402,6 +2700,10 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2424,6 +2726,9 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -2473,8 +2778,11 @@ packages: effect@3.20.0: resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==} - electron-to-chromium@1.5.307: - resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + electron-to-chromium@1.5.444: + resolution: {integrity: sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==} + + elkjs@0.11.1: + resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2486,8 +2794,8 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} - enhanced-resolve@5.20.0: - resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -2498,8 +2806,12 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -2510,12 +2822,15 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.2.2: - resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + es-iterator-helpers@1.4.0: + resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==} engines: {node: '>= 0.4'} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: @@ -2526,12 +2841,12 @@ packages: resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} engines: {node: '>= 0.4'} - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} hasBin: true @@ -2543,8 +2858,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@16.1.6: - resolution: {integrity: sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==} + eslint-config-next@16.3.8: + resolution: {integrity: sha512-81vovwMe6NGnoFsl0KUJWzlS+y239i3fdsxs49gSEX3pDQW2cuKy+fxUPXNkQqqx6OCPheYPtBlSufc9aZ0k+w==} peerDependencies: eslint: '>=9.0.0' typescript: '>=3.3.1' @@ -2552,8 +2867,8 @@ packages: typescript: optional: true - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} eslint-import-resolver-typescript@3.10.1: resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} @@ -2568,8 +2883,8 @@ packages: eslint-plugin-import-x: optional: true - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + eslint-module-utils@2.14.0: + resolution: {integrity: sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2605,11 +2920,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} engines: {node: '>=18'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -2633,9 +2948,10 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2662,17 +2978,27 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + exsolve@1.1.3: + resolution: {integrity: sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==} fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2686,21 +3012,17 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-sha256@1.3.0: resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} - fast-xml-builder@1.2.0: - resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} - - fast-xml-parser@5.7.3: - resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} - hasBin: true - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -2722,6 +3044,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-my-way@9.7.0: + resolution: {integrity: sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==} + engines: {node: '>=20'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2734,11 +3060,11 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.4: - resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.1: + resolution: {integrity: sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2754,12 +3080,12 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} - framer-motion@12.38.0: - resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} + framer-motion@12.43.0: + resolution: {integrity: sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2780,8 +3106,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -2817,11 +3143,11 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} - giget@3.3.0: - resolution: {integrity: sha512-gzi2D96p+AMfDcmJHGDj3KJ9NRiwvlFAU5yfa3ROwWZmFUjX4P43x3BcyRaOMMLto1vUo7C+86+MFhYTl6Ryiw==} + giget@3.3.1: + resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} hasBin: true glob-parent@5.1.2: @@ -2855,8 +3181,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - grammex@3.1.12: - resolution: {integrity: sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==} + grammex@3.1.13: + resolution: {integrity: sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==} graphmatch@1.1.1: resolution: {integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==} @@ -2884,8 +3210,8 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} hermes-estree@0.25.1: @@ -2894,37 +3220,41 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hono@4.12.25: - resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} + hono@4.13.13: + resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} engines: {node: '>=16.9.0'} html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} + html5parser@3.0.0: + resolution: {integrity: sha512-iNpSopa+4YHX50UOk825tBy7MghmXHo/ZpLskBYN0kAr1xhH8GlIMk5bLRXcZlfP3AnLUcSuFMu8C4MdOUxA8A==} + htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - http-status-codes@2.3.0: - resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} iceberg-js@0.8.1: resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} engines: {node: '>=20.0.0'} - iconify-icon@3.0.2: - resolution: {integrity: sha512-DYPAumiUeUeT/GHT8x2wrAVKn1FqZJqFH0Y5pBefapWRreV1BBvqBVMb0020YQ2njmbR59r/IathL2d2OrDrxA==} + iconify-icon@3.0.3: + resolution: {integrity: sha512-n4ZIChz3N75KqFSWLt9hHGzbrqGQeeoFxCE+nND4WFFa88x8AW8GvmnVZaVNYErhhiZkxJhBfVKMIGrF1wt3hg==} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.12: + resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -2939,6 +3269,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2962,8 +3296,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.17.0: + resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -2974,6 +3308,10 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3062,21 +3400,24 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} - jose@6.2.2: - resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true jsesc@3.1.0: @@ -3112,8 +3453,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kysely@0.29.2: - resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} + kysely@0.29.6: + resolution: {integrity: sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg==} engines: {node: '>=22.0.0'} language-subtag-registry@0.3.23: @@ -3130,78 +3471,78 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} locate-path@5.0.0: @@ -3215,6 +3556,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -3222,6 +3566,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@11.5.3: resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} engines: {node: 20 || >=22} @@ -3229,8 +3576,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru.min@1.1.4: - resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} + lru.min@1.1.5: + resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} lucide-react@0.577.0: @@ -3241,6 +3588,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.2: + resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} + marked@15.0.12: resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} engines: {node: '>= 18'} @@ -3266,8 +3616,8 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -3280,11 +3630,11 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} - motion-dom@12.38.0: - resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} + motion-dom@12.43.0: + resolution: {integrity: sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==} - motion-utils@12.36.0: - resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3297,13 +3647,13 @@ packages: resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} engines: {node: '>=8.0.0'} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanostores@1.3.0: - resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==} + nanostores@1.5.4: + resolution: {integrity: sha512-4UXY7OAJEK0Y6V3SudBU7cXjAzTDc189R78dDOh0Xbm19gpKYto6WizSg58LJf12lTsRxKSLGyE117OiLpM4DQ==} engines: {node: ^20.0.0 || >=22.0.0} napi-postinstall@0.3.4: @@ -3318,30 +3668,14 @@ packages: resolution: {integrity: sha512-iGBUfFB7yPczHHtA8dksKTJ9E8TESNTAx1UQWW6TzMF280vo9jdPYpLUXrMN1BCkPdHFdNG3fxOt2CUad8KhAw==} engines: {node: '>=18'} - next-auth@5.0.0-beta.30: - resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - next: ^14.0.0-0 || ^15.0.0 || ^16.0.0 - nodemailer: ^7.0.7 - react: ^18.2.0 || ^19.0.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.1.6: - resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} + next@16.3.8: + resolution: {integrity: sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -3361,8 +3695,8 @@ packages: sass: optional: true - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + node-exports-info@1.6.2: + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} engines: {node: '>= 0.4'} node-fetch@2.7.0: @@ -3374,11 +3708,9 @@ packages: encoding: optional: true - node-releases@2.0.36: - resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} - - oauth4webapi@3.8.5: - resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -3412,19 +3744,19 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} - oxc-parser@0.143.0: - resolution: {integrity: sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==} + oxc-parser@0.152.0: + resolution: {integrity: sha512-ZztsZgaCWmPeKYMdoTT5e0oV/oxD1NeLWNR3kAtr/G707cUPYaaozRlVDHLCGnszHJRszN9mJwmPNpqgwloC0A==} engines: {node: ^20.19.0 || >=22.12.0} p-limit@2.3.0: @@ -3458,10 +3790,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-expression-matcher@1.5.0: - resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} - engines: {node: '>=14.0.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3476,17 +3804,21 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} - pg-cloudflare@1.4.0: - resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + pg-cloudflare@1.4.1: + resolution: {integrity: sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==} - pg-connection-string@2.13.0: - resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} + pg-connection-string@2.14.1: + resolution: {integrity: sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} @@ -3497,15 +3829,15 @@ packages: peerDependencies: pg: '>=8.0' - pg-protocol@1.14.0: - resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} + pg-protocol@1.16.1: + resolution: {integrity: sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.21.0: - resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} + pg@8.23.1: + resolution: {integrity: sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -3519,19 +3851,19 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - pingram@1.0.1: - resolution: {integrity: sha512-uFgmGvjCtdqp7+7gB1TNaec8GQw6HKNdJQmvygDFy37g6ecvRFaOzX0po7T51hrphLV5+KCZaaEA4ixgCX7b+A==} + pingram@1.0.36: + resolution: {integrity: sha512-ldZzf6m7UvGg0+Kg6WiXpwKRdEaCzQxrlNdWCGkw8ErxsCDJWxaGF17omgZtlhcONk6SYL4AE2F1d9E2JwArIA==} - pkg-types@2.3.1: - resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + pkg-types@2.3.3: + resolution: {integrity: sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==} pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} @@ -3541,22 +3873,15 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postal-mime@2.7.3: - resolution: {integrity: sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} + postal-mime@2.7.6: + resolution: {integrity: sha512-UUlyE2KlxmvwMGq060onF/VWU3zD6dVW31FwokQ5v26jWd35fbs2MoTFzh+jXnPwAyV2MULMKXcBInGOl5TO6Q==} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -3583,8 +3908,8 @@ packages: resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} engines: {node: '>=12'} - posthog-js@1.434.15: - resolution: {integrity: sha512-ZfBkNrQiFr1Q43G3dR0USzTRdfx+6gKPebyoWDmKnLUhG3yRgYjLH914J6ifEDRnH+RaGOx7rsWKxKcvaSJXHg==} + posthog-js@1.435.8: + resolution: {integrity: sha512-rUrv0kKhD72Wfu++pBlupvrpt1Z60uTAgNUChAH232a05bbRBzV0P4JqbuZEAaaUXumhuB3a8r+iFMzm/2l1Ow==} peerDependencies: '@types/react': '>=16.8.0' react: '>=16.8.0' @@ -3594,14 +3919,6 @@ packages: react: optional: true - preact-render-to-string@6.5.11: - resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} - peerDependencies: - preact: '>=10' - - preact@10.24.3: - resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} - preact@10.29.8: resolution: {integrity: sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==} peerDependencies: @@ -3614,13 +3931,13 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} hasBin: true - prisma@7.8.0: - resolution: {integrity: sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==} + prisma@7.10.0: + resolution: {integrity: sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==} engines: {node: ^20.19 || ^22.12 || >=24.0} hasBin: true peerDependencies: @@ -3642,8 +3959,9 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -3663,29 +3981,29 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - rc9@3.0.1: - resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} + rc9@3.1.0: + resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==} - react-apexcharts@2.1.0: - resolution: {integrity: sha512-xrmeTKRKHh3cvvLc8SasqFjlOgIqGpyHc81qjnRtcjUM0Fu7qEjgVRWGPokGFjqhwRZVgEym8zmuEyYr5LMYIg==} + react-apexcharts@2.1.1: + resolution: {integrity: sha512-aV25n+1ZdAzRG0KhODVC48K8WbhMKBAr3yWSeaaBqM1ySn22kSSKblsNCUvlwegsT+cdMLuOrK9xGZb+IK+OkQ==} peerDependencies: apexcharts: '>=5.10.1' react: '>=16.8.0' - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: - react: ^19.2.4 + react: ^19.3.0 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} engines: {node: '>=0.10.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} reflect.getprototypeof@1.0.10: @@ -3710,8 +4028,8 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - resend@6.9.3: - resolution: {integrity: sha512-GRXjH9XZBJA+daH7bBVDuTShr22iWCxXA8P7t495G4dM/RC+d+3gHBK/6bz9K6Vpcq11zRQKmD+B+jECwQlyGQ==} + resend@6.32.0: + resolution: {integrity: sha512-bZZUeKlpKINFT+zGG2iIzfDewkyEHRaE7Xk1haseVz3Gfw6ekkizN6KdKgZSMjt5ywE8vMwr3wTdenlF8P475Q==} engines: {node: '>=20'} peerDependencies: '@react-email/render': '*' @@ -3726,15 +4044,14 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} engines: {node: '>= 0.4'} hasBin: true - resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} - engines: {node: '>= 0.4'} - hasBin: true + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} @@ -3744,8 +4061,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.63.5: - resolution: {integrity: sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + rollup@4.64.0: + resolution: {integrity: sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3755,8 +4075,8 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-push-apply@1.0.0: @@ -3767,11 +4087,15 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -3780,13 +4104,13 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true - sentry@0.44.1: - resolution: {integrity: sha512-LK+RJ2PFsJKM+P0D/zYC+M5gFB1ytfVONU9DVUqJu67YFlwnj3wGJkUzBhCS8R4iUqRdRTAYbFbRKTAe03rW/Q==} + sentry@0.45.0: + resolution: {integrity: sha512-kgmEbLlC22lL7uneeUm9Sjz65rp5cvwYOMTxXbZCmBum4sbVre/lLT3zTV3kwXHJ9/BM8ZS0/GhZe9Cl1t6WyA==} engines: {node: '>=20.0'} hasBin: true @@ -3796,8 +4120,8 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-cookie-parser@3.1.2: + resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -3811,12 +4135,14 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - shallowequal@1.1.0: - resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.5: + resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -3826,8 +4152,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -3838,10 +4164,13 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3849,8 +4178,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} split2@4.2.0: @@ -3864,12 +4193,15 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.11: resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} engines: {node: '>=6'} - standardwebhooks@1.0.0: - resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + standardwebhooks@1.1.1: + resolution: {integrity: sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -3886,19 +4218,19 @@ packages: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + string.prototype.matchall@4.1.0: + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: @@ -3917,18 +4249,24 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strnum@2.4.0: - resolution: {integrity: sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - styled-components@6.3.11: - resolution: {integrity: sha512-opzgceGlQ5rdZdGwf9ddLW7EM2F4L7tgsgLn6fFzQ2JgE5EVQ4HZwNkcgB1p8WfOBx1GEZP3fa66ajJmtXhSrA==} + styled-components@6.5.3: + resolution: {integrity: sha512-vAX79sfpmUerP9fsTTxoTrBDE0RuO4ahjInyWYoohNgqrdg63Ms4q6FJ/o2Fyity82NU3cujOT8Ewl9TThBdwg==} engines: {node: '>= 16'} peerDependencies: + css-to-react-native: '>= 3.2.0' react: '>= 16.8.0' react-dom: '>= 16.8.0' + react-native: '>= 0.68.0' peerDependenciesMeta: + css-to-react-native: + optional: true react-dom: optional: true + react-native: + optional: true styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} @@ -3958,20 +4296,35 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svix@1.84.1: - resolution: {integrity: sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==} - - tailwindcss@4.2.1: - resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.6: + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3979,8 +4332,8 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -3991,8 +4344,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} hasBin: true @@ -4012,20 +4365,20 @@ packages: resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + typed-array-byte-offset@1.0.5: + resolution: {integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.56.1: - resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -4039,14 +4392,14 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - unrs-resolver@1.11.1: - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -4054,18 +4407,87 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: typescript: optional: true + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + 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@3.2.7: + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.7 + '@vitest/ui': 3.2.7 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + web-vitals@6.2.1: resolution: {integrity: sha512-rLcLXA2sx6+9dE88NHFubwTtGxpK4yYBLj6qHPdFoCaLr0cXGb4efOqtKLlm4loGA4OEKHIQKMKzZkKyOh5ctw==} @@ -4093,8 +4515,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + which-typed-array@1.1.24: + resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==} engines: {node: '>= 0.4'} which@2.0.2: @@ -4102,6 +4524,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4110,22 +4537,6 @@ packages: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - 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 - - xml-naming@0.1.0: - resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} - engines: {node: '>=16.0.0'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4157,438 +4568,376 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} snapshots: - '@alloc/quick-lru@5.2.0': {} + '@alloc/quick-lru@5.3.0': {} - '@auth/core@0.41.0': + '@aws-sdk/checksums@3.1001.1': dependencies: - '@panva/hkdf': 1.2.1 - jose: 6.2.2 - oauth4webapi: 3.8.5 - preact: 10.24.3 - preact-render-to-string: 6.5.11(preact@10.24.3) - - '@aws-crypto/crc32@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.12 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-crypto/crc32c@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.12 + '@aws-sdk/client-s3@3.1146.0': + dependencies: + '@aws-sdk/checksums': 3.1001.1 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/middleware-sdk-s3': 3.972.77 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-crypto/sha1-browser@5.2.0': + '@aws-sdk/core@3.978.1': dependencies: - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.12 - '@aws-sdk/util-locate-window': 3.965.7 - '@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.12 - '@aws-sdk/util-locate-window': 3.965.7 - '@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.12 - 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.12 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-sdk/checksums@3.1000.5': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@aws-crypto/crc32c': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.974.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 - tslib: 2.8.1 - - '@aws-sdk/client-s3@3.1068.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.974.20 - '@aws-sdk/credential-provider-node': 3.972.55 - '@aws-sdk/middleware-flexible-checksums': 3.974.30 - '@aws-sdk/middleware-sdk-s3': 3.972.51 - '@aws-sdk/signature-v4-multi-region': 3.996.34 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/fetch-http-handler': 5.4.7 - '@smithy/node-http-handler': 4.7.8 - '@smithy/types': 4.14.4 - tslib: 2.8.1 - - '@aws-sdk/core@3.974.20': - dependencies: - '@aws-sdk/types': 3.973.12 - '@aws-sdk/xml-builder': 3.972.29 - '@aws/lambda-invoke-store': 0.2.4 - '@smithy/core': 3.24.7 - '@smithy/signature-v4': 5.4.7 - '@smithy/types': 4.14.4 + '@aws-sdk/types': 3.974.6 + '@aws-sdk/xml-builder': 3.972.41 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.35.1 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.46': + '@aws-sdk/credential-provider-env@3.972.72': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.48': + '@aws-sdk/credential-provider-http@3.972.74': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/fetch-http-handler': 5.4.7 - '@smithy/node-http-handler': 4.7.8 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.972.53': - dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/credential-provider-env': 3.972.46 - '@aws-sdk/credential-provider-http': 3.972.48 - '@aws-sdk/credential-provider-login': 3.972.52 - '@aws-sdk/credential-provider-process': 3.972.46 - '@aws-sdk/credential-provider-sso': 3.972.52 - '@aws-sdk/credential-provider-web-identity': 3.972.52 - '@aws-sdk/nested-clients': 3.997.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/credential-provider-imds': 4.3.9 - '@smithy/types': 4.14.4 + '@aws-sdk/credential-provider-ini@3.973.17': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/credential-provider-env': 3.972.72 + '@aws-sdk/credential-provider-http': 3.972.74 + '@aws-sdk/credential-provider-login': 3.972.79 + '@aws-sdk/credential-provider-process': 3.972.72 + '@aws-sdk/credential-provider-sso': 3.973.16 + '@aws-sdk/credential-provider-web-identity': 3.972.78 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.52': + '@aws-sdk/credential-provider-login@3.972.79': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/nested-clients': 3.997.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.55': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.46 - '@aws-sdk/credential-provider-http': 3.972.48 - '@aws-sdk/credential-provider-ini': 3.972.53 - '@aws-sdk/credential-provider-process': 3.972.46 - '@aws-sdk/credential-provider-sso': 3.972.52 - '@aws-sdk/credential-provider-web-identity': 3.972.52 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/credential-provider-imds': 4.3.9 - '@smithy/types': 4.14.4 + '@aws-sdk/credential-provider-node@3.972.84': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.72 + '@aws-sdk/credential-provider-http': 3.972.74 + '@aws-sdk/credential-provider-ini': 3.973.17 + '@aws-sdk/credential-provider-process': 3.972.72 + '@aws-sdk/credential-provider-sso': 3.973.16 + '@aws-sdk/credential-provider-web-identity': 3.972.78 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.46': + '@aws-sdk/credential-provider-process@3.972.72': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.52': + '@aws-sdk/credential-provider-sso@3.973.16': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/nested-clients': 3.997.20 - '@aws-sdk/token-providers': 3.1066.0 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/token-providers': 3.1138.0 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.52': + '@aws-sdk/credential-provider-web-identity@3.972.78': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/nested-clients': 3.997.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.974.30': + '@aws-sdk/middleware-sdk-s3@3.972.77': dependencies: - '@aws-sdk/checksums': 3.1000.5 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.51': + '@aws-sdk/nested-clients@3.997.46': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/signature-v4-multi-region': 3.996.34 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.20': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.20 - '@aws-sdk/signature-v4-multi-region': 3.996.34 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/fetch-http-handler': 5.4.7 - '@smithy/node-http-handler': 4.7.8 - '@smithy/types': 4.14.4 - tslib: 2.8.1 - - '@aws-sdk/s3-request-presigner@3.1068.0': + '@aws-sdk/s3-request-presigner@3.1146.0': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/signature-v4-multi-region': 3.996.34 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.34': + '@aws-sdk/signature-v4-multi-region@3.996.47': dependencies: - '@aws-sdk/types': 3.973.12 - '@smithy/signature-v4': 5.4.7 - '@smithy/types': 4.14.4 + '@aws-sdk/types': 3.974.6 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1066.0': + '@aws-sdk/token-providers@3.1138.0': dependencies: - '@aws-sdk/core': 3.974.20 - '@aws-sdk/nested-clients': 3.997.20 - '@aws-sdk/types': 3.973.12 - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/types@3.973.12': + '@aws-sdk/types@3.974.6': dependencies: - '@smithy/types': 4.14.4 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.965.7': + '@aws-sdk/xml-builder@3.972.41': dependencies: + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.29': - dependencies: - '@smithy/types': 4.14.4 - fast-xml-parser: 5.7.3 - tslib: 2.8.1 + '@aws/lambda-invoke-store@0.3.0': {} - '@aws/lambda-invoke-store@0.2.4': {} - - '@babel/code-frame@7.29.0': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.0(supports-color@8.1.1)': + '@babel/core@7.29.7': 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)(supports-color@8.1.1) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0(supports-color@8.1.1) - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + 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': + '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.28.6': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.3 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.28.6(supports-color@8.1.1)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.0(supports-color@8.1.1) - '@babel/types': 7.29.0 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)(supports-color@8.1.1)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0(supports-color@8.1.1) - '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0(supports-color@8.1.1) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} - '@babel/helpers@7.28.6': + '@babel/helpers@7.29.7': dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 - '@babel/parser@7.29.0': + '@babel/parser@7.29.9': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.8 - '@babel/template@7.28.6': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.0(supports-color@8.1.1)': + '@babel/traverse@7.29.8': 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(supports-color@8.1.1) + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.29.0': + '@babel/types@7.29.8': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 - '@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0)': + '@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4)': dependencies: '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.3.0 - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/semantic-conventions': 1.43.0 '@standard-schema/spec': 1.1.0 - better-call: 1.3.6(zod@4.4.3) - jose: 6.2.2 - kysely: 0.29.2 - nanostores: 1.3.0 - zod: 4.4.3 + better-call: 1.3.6(zod@4.6.5) + jose: 6.2.12 + kysely: 0.29.6 + nanostores: 1.5.4 + zod: 4.6.5 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/drizzle-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) '@better-auth/utils': 0.4.1 - '@better-auth/kysely-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)': + '@better-auth/kysely-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)(kysely@0.29.6)': dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) '@better-auth/utils': 0.4.1 optionalDependencies: - kysely: 0.29.2 + kysely: 0.29.6 - '@better-auth/memory-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/memory-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) '@better-auth/utils': 0.4.1 - '@better-auth/mongo-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/mongo-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) '@better-auth/utils': 0.4.1 - '@better-auth/prisma-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))': + '@better-auth/prisma-adapter@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)(@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))': dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) '@better-auth/utils': 0.4.1 optionalDependencies: - '@prisma/client': 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) - prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + '@prisma/client': 7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3) + prisma: 7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3) - '@better-auth/telemetry@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)': + '@better-auth/telemetry@1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)': dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.3.0 '@better-auth/utils@0.4.1': dependencies: - '@noble/hashes': 2.2.0 + '@noble/hashes': 2.4.0 + + '@better-auth/utils@0.4.2': + dependencies: + '@noble/hashes': 2.4.0 '@better-fetch/fetch@1.3.0': {} - '@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)': + '@better-fetch/fetch@1.3.2': {} + + '@electric-sql/pglite-socket@0.1.3(@electric-sql/pglite@0.4.3)': dependencies: - '@electric-sql/pglite': 0.4.1 + '@electric-sql/pglite': 0.4.3 - '@electric-sql/pglite-tools@0.3.1(@electric-sql/pglite@0.4.1)': + '@electric-sql/pglite-tools@0.3.3(@electric-sql/pglite@0.4.3)': dependencies: - '@electric-sql/pglite': 0.4.1 + '@electric-sql/pglite': 0.4.3 + + '@electric-sql/pglite@0.4.3': {} - '@electric-sql/pglite@0.4.1': {} + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true - '@emnapi/core@1.8.1': + '@emnapi/runtime@1.10.0': dependencies: - '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.8.1': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true @@ -4599,89 +4948,92 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/unitless@0.10.0': {} - - '@esbuild/aix-ppc64@0.28.1': + '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.28.1': + '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.28.1': + '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.28.1': + '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.28.1': + '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.28.1': + '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': + '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.28.1': + '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.28.1': + '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.28.1': + '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.28.1': + '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.28.1': + '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.28.1': + '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.28.1': + '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.28.1': + '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': + '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.28.1': + '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': + '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.28.1': + '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': + '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.28.1': + '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': + '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.28.1': + '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': + '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/win32-ia32@0.28.1': + '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-x64@0.28.1': + '@esbuild/win32-x64@0.28.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))': + dependencies: + eslint: 9.39.5(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.5(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -4689,7 +5041,7 @@ snapshots: '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -4702,21 +5054,21 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': + '@eslint/eslintrc@3.3.7': dependencies: - ajv: 6.14.0 - debug: 4.4.3(supports-color@8.1.1) + ajv: 6.15.0 + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.2 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.39.4': {} + '@eslint/js@9.39.5': {} '@eslint/object-schema@2.1.7': {} @@ -4725,135 +5077,151 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@hono/node-server@1.19.11(hono@4.12.25)': + '@hono/node-server@1.19.17(hono@4.13.13)': dependencies: - hono: 4.12.25 + hono: 4.13.13 - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} - '@iconify-icon/react@3.0.3(react@19.2.4)': + '@iconify-icon/react@3.0.4(react@19.3.0)': dependencies: - iconify-icon: 3.0.2 - react: 19.2.4 + iconify-icon: 3.0.3 + react: 19.3.0 '@iconify/types@2.0.0': {} '@img/colour@1.1.0': optional: true - '@img/sharp-darwin-arm64@0.34.5': + '@img/sharp-darwin-arm64@0.35.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.3.4 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.35.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-freebsd-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.3.4': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.3.4': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm64@1.3.4': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-arm@1.3.4': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.3.4': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-riscv64@1.3.4': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linux-s390x@1.3.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linux-x64@1.3.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': optional: true - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + optional: true + + '@img/sharp-linux-arm64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.3.4 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-ppc64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-riscv64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linux-s390x@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linux-x64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.3.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + '@img/sharp-linuxmusl-arm64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 optional: true - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.35.5': dependencies: - '@emnapi/runtime': 1.8.1 + '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-webcontainers-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-arm64@0.35.5': optional: true - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-ia32@0.35.5': + optional: true + + '@img/sharp-win32-x64@0.35.5': optional: true '@infobip-api/sdk@0.3.2': dependencies: - axios: 1.13.6 - form-data: 4.0.5 + axios: 1.20.0 + form-data: 4.0.6 transitivePeerDependencies: - debug + - supports-color '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': @@ -4863,60 +5231,59 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@kurkle/color@0.3.4': {} + '@jridgewell/sourcemap-codec': 1.6.0 '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true - '@napi-rs/wasm-runtime@0.2.12': + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 - '@tybys/wasm-util': 0.10.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.4 optional: true - '@next/env@16.1.6': {} + '@next/env@16.3.8': {} - '@next/eslint-plugin-next@16.1.6': + '@next/eslint-plugin-next@16.3.8(eslint@9.39.5(jiti@2.7.0))': dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) fast-glob: 3.3.1 + transitivePeerDependencies: + - eslint - '@next/swc-darwin-arm64@16.1.6': + '@next/swc-darwin-arm64@16.3.8': optional: true - '@next/swc-darwin-x64@16.1.6': + '@next/swc-darwin-x64@16.3.8': optional: true - '@next/swc-linux-arm64-gnu@16.1.6': + '@next/swc-linux-arm64-gnu@16.3.8': optional: true - '@next/swc-linux-arm64-musl@16.1.6': + '@next/swc-linux-arm64-musl@16.3.8': optional: true - '@next/swc-linux-x64-gnu@16.1.6': + '@next/swc-linux-x64-gnu@16.3.8': optional: true - '@next/swc-linux-x64-musl@16.1.6': + '@next/swc-linux-x64-musl@16.3.8': optional: true - '@next/swc-win32-arm64-msvc@16.1.6': + '@next/swc-win32-arm64-msvc@16.3.8': optional: true - '@next/swc-win32-x64-msvc@16.1.6': + '@next/swc-win32-x64-msvc@16.3.8': optional: true - '@noble/ciphers@2.2.0': {} + '@noble/ciphers@2.4.0': {} - '@noble/hashes@2.2.0': {} - - '@nodable/entities@2.2.0': {} + '@noble/hashes@2.4.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -4928,105 +5295,103 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + fastq: 1.20.3 '@nolyfill/is-core-module@1.0.39': {} '@opentelemetry/api@1.9.1': {} - '@opentelemetry/semantic-conventions@1.41.1': {} + '@opentelemetry/semantic-conventions@1.43.0': {} - '@oxc-parser/binding-android-arm-eabi@0.143.0': + '@oxc-parser/binding-android-arm-eabi@0.152.0': optional: true - '@oxc-parser/binding-android-arm64@0.143.0': + '@oxc-parser/binding-android-arm64@0.152.0': optional: true - '@oxc-parser/binding-darwin-arm64@0.143.0': + '@oxc-parser/binding-darwin-arm64@0.152.0': optional: true - '@oxc-parser/binding-darwin-x64@0.143.0': + '@oxc-parser/binding-darwin-x64@0.152.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.143.0': + '@oxc-parser/binding-freebsd-x64@0.152.0': optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': optional: true - '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.143.0': + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': optional: true - '@oxc-parser/binding-linux-arm64-musl@0.143.0': + '@oxc-parser/binding-linux-arm64-musl@0.152.0': optional: true - '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': optional: true - '@oxc-parser/binding-linux-riscv64-musl@0.143.0': + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.143.0': + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': optional: true - '@oxc-parser/binding-linux-x64-gnu@0.143.0': + '@oxc-parser/binding-linux-x64-gnu@0.152.0': optional: true - '@oxc-parser/binding-linux-x64-musl@0.143.0': + '@oxc-parser/binding-linux-x64-musl@0.152.0': optional: true - '@oxc-parser/binding-openharmony-arm64@0.143.0': + '@oxc-parser/binding-openharmony-arm64@0.152.0': optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.143.0': + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': optional: true - '@oxc-parser/binding-win32-ia32-msvc@0.143.0': + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': optional: true - '@oxc-parser/binding-win32-x64-msvc@0.143.0': + '@oxc-parser/binding-win32-x64-msvc@0.152.0': optional: true - '@oxc-project/types@0.143.0': {} - - '@panva/hkdf@1.2.1': {} + '@oxc-project/types@0.152.0': {} - '@posthog/browser-common@0.9.0': + '@posthog/browser-common@0.9.1': dependencies: - '@posthog/core': 1.55.2 - '@posthog/types': 1.412.4 + '@posthog/core': 1.55.3 + '@posthog/types': 1.413.0 - '@posthog/core@1.55.2': + '@posthog/core@1.55.3': dependencies: - '@posthog/types': 1.412.4 + '@posthog/types': 1.413.0 - '@posthog/types@1.412.4': {} + '@posthog/types@1.413.0': {} - '@prisma/adapter-pg@7.8.0': + '@prisma/adapter-pg@7.10.0': dependencies: - '@prisma/driver-adapter-utils': 7.8.0 - '@types/pg': 8.20.0 - pg: 8.21.0 + '@prisma/driver-adapter-utils': 7.10.0 + '@types/pg': 8.23.1 + pg: 8.23.1 postgres-array: 3.0.4 transitivePeerDependencies: - pg-native - '@prisma/client-runtime-utils@7.8.0': {} + '@prisma/client-runtime-utils@7.10.0': {} - '@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)': + '@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3)': dependencies: - '@prisma/client-runtime-utils': 7.8.0 + '@prisma/client-runtime-utils': 7.10.0 optionalDependencies: - prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + prisma: 7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3) typescript: 5.9.3 - '@prisma/config@7.8.0': + '@prisma/config@7.10.0': dependencies: c12: 3.3.4 deepmerge-ts: 7.1.5 @@ -5035,351 +5400,367 @@ snapshots: transitivePeerDependencies: - magicast - '@prisma/debug@7.2.0': {} + '@prisma/debug@7.10.0': {} - '@prisma/debug@7.8.0': {} + '@prisma/debug@7.2.0': {} - '@prisma/dev@0.24.3(typescript@5.9.3)': + '@prisma/dev@0.24.17(typescript@5.9.3)': dependencies: - '@electric-sql/pglite': 0.4.1 - '@electric-sql/pglite-socket': 0.1.1(@electric-sql/pglite@0.4.1) - '@electric-sql/pglite-tools': 0.3.1(@electric-sql/pglite@0.4.1) - '@hono/node-server': 1.19.11(hono@4.12.25) + '@electric-sql/pglite': 0.4.3 + '@electric-sql/pglite-socket': 0.1.3(@electric-sql/pglite@0.4.3) + '@electric-sql/pglite-tools': 0.3.3(@electric-sql/pglite@0.4.3) '@prisma/get-platform': 7.2.0 '@prisma/query-plan-executor': 7.2.0 - '@prisma/streams-local': 0.1.2 + '@prisma/streams-local': 0.1.11 + find-my-way: 9.7.0 foreground-child: 3.3.1 get-port-please: 3.2.0 - hono: 4.12.25 - http-status-codes: 2.3.0 pathe: 2.0.3 proper-lockfile: 4.1.2 remeda: 2.33.4 std-env: 3.10.0 - valibot: 1.2.0(typescript@5.9.3) + valibot: 1.4.2(typescript@5.9.3) zeptomatch: 2.1.0 transitivePeerDependencies: - typescript - '@prisma/driver-adapter-utils@7.8.0': + '@prisma/driver-adapter-utils@7.10.0': dependencies: - '@prisma/debug': 7.8.0 + '@prisma/debug': 7.10.0 - '@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a': {} + '@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3': {} - '@prisma/engines@7.8.0': + '@prisma/engines@7.10.0': dependencies: - '@prisma/debug': 7.8.0 - '@prisma/engines-version': 7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a - '@prisma/fetch-engine': 7.8.0 - '@prisma/get-platform': 7.8.0 + '@prisma/debug': 7.10.0 + '@prisma/engines-version': 7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3 + '@prisma/fetch-engine': 7.10.0 + '@prisma/get-platform': 7.10.0 - '@prisma/fetch-engine@7.8.0': + '@prisma/fetch-engine@7.10.0': dependencies: - '@prisma/debug': 7.8.0 - '@prisma/engines-version': 7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a - '@prisma/get-platform': 7.8.0 + '@prisma/debug': 7.10.0 + '@prisma/engines-version': 7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3 + '@prisma/get-platform': 7.10.0 - '@prisma/get-platform@7.2.0': + '@prisma/get-platform@7.10.0': dependencies: - '@prisma/debug': 7.2.0 + '@prisma/debug': 7.10.0 - '@prisma/get-platform@7.8.0': + '@prisma/get-platform@7.2.0': dependencies: - '@prisma/debug': 7.8.0 + '@prisma/debug': 7.2.0 '@prisma/query-plan-executor@7.2.0': {} - '@prisma/streams-local@0.1.2': + '@prisma/streams-local@0.1.11': dependencies: ajv: 8.20.0 - better-result: 2.9.2 + better-result: 2.10.0 env-paths: 3.0.0 proper-lockfile: 4.1.2 - '@prisma/studio-core@0.27.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@types/react': 19.2.14 - chart.js: 4.5.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@prisma/studio-core@0.33.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@types/react': 19.3.0 + '@visx/curve': 4.0.1-alpha.0 + '@visx/event': 4.0.1-alpha.0 + '@visx/grid': 4.0.1-alpha.0(react@19.3.0) + '@visx/group': 4.0.1-alpha.0(react@19.3.0) + '@visx/responsive': 4.0.1-alpha.0(react@19.3.0) + '@visx/scale': 4.0.1-alpha.0 + '@visx/shape': 4.0.1-alpha.0(react@19.3.0) + d3-array: 3.2.4 + d3-shape: 3.2.0 + elkjs: 0.11.1 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) transitivePeerDependencies: - '@types/react-dom' '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.3.0 - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-slot@1.2.3(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.3.0 - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.3.0 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.3.0 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.3.0 - '@react-email/body@0.2.1(react@19.2.4)': + '@react-email/body@0.3.0(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/button@0.2.1(react@19.2.4)': + '@react-email/button@0.2.1(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/code-block@0.2.1(react@19.2.4)': + '@react-email/code-block@0.2.1(react@19.3.0)': dependencies: prismjs: 1.30.0 - react: 19.2.4 - - '@react-email/code-inline@0.0.6(react@19.2.4)': - dependencies: - react: 19.2.4 - - '@react-email/column@0.0.14(react@19.2.4)': - dependencies: - react: 19.2.4 - - '@react-email/components@1.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@react-email/body': 0.2.1(react@19.2.4) - '@react-email/button': 0.2.1(react@19.2.4) - '@react-email/code-block': 0.2.1(react@19.2.4) - '@react-email/code-inline': 0.0.6(react@19.2.4) - '@react-email/column': 0.0.14(react@19.2.4) - '@react-email/container': 0.0.16(react@19.2.4) - '@react-email/font': 0.0.10(react@19.2.4) - '@react-email/head': 0.0.13(react@19.2.4) - '@react-email/heading': 0.0.16(react@19.2.4) - '@react-email/hr': 0.0.12(react@19.2.4) - '@react-email/html': 0.0.12(react@19.2.4) - '@react-email/img': 0.0.12(react@19.2.4) - '@react-email/link': 0.0.13(react@19.2.4) - '@react-email/markdown': 0.0.18(react@19.2.4) - '@react-email/preview': 0.0.14(react@19.2.4) - '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-email/row': 0.0.13(react@19.2.4) - '@react-email/section': 0.0.17(react@19.2.4) - '@react-email/tailwind': 2.0.5(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4) - '@react-email/text': 0.1.6(react@19.2.4) - react: 19.2.4 + react: 19.3.0 + + '@react-email/code-inline@0.0.6(react@19.3.0)': + dependencies: + react: 19.3.0 + + '@react-email/column@0.0.14(react@19.3.0)': + dependencies: + react: 19.3.0 + + '@react-email/components@1.0.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@react-email/body': 0.3.0(react@19.3.0) + '@react-email/button': 0.2.1(react@19.3.0) + '@react-email/code-block': 0.2.1(react@19.3.0) + '@react-email/code-inline': 0.0.6(react@19.3.0) + '@react-email/column': 0.0.14(react@19.3.0) + '@react-email/container': 0.0.16(react@19.3.0) + '@react-email/font': 0.0.10(react@19.3.0) + '@react-email/head': 0.0.13(react@19.3.0) + '@react-email/heading': 0.0.16(react@19.3.0) + '@react-email/hr': 0.0.12(react@19.3.0) + '@react-email/html': 0.0.12(react@19.3.0) + '@react-email/img': 0.0.12(react@19.3.0) + '@react-email/link': 0.0.13(react@19.3.0) + '@react-email/markdown': 0.0.18(react@19.3.0) + '@react-email/preview': 0.0.14(react@19.3.0) + '@react-email/render': 2.0.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@react-email/row': 0.0.13(react@19.3.0) + '@react-email/section': 0.0.17(react@19.3.0) + '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.3.0))(@react-email/button@0.2.1(react@19.3.0))(@react-email/code-block@0.2.1(react@19.3.0))(@react-email/code-inline@0.0.6(react@19.3.0))(@react-email/container@0.0.16(react@19.3.0))(@react-email/heading@0.0.16(react@19.3.0))(@react-email/hr@0.0.12(react@19.3.0))(@react-email/img@0.0.12(react@19.3.0))(@react-email/link@0.0.13(react@19.3.0))(@react-email/preview@0.0.14(react@19.3.0))(@react-email/text@0.1.6(react@19.3.0))(react@19.3.0) + '@react-email/text': 0.1.6(react@19.3.0) + react: 19.3.0 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.16(react@19.2.4)': + '@react-email/container@0.0.16(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/font@0.0.10(react@19.2.4)': + '@react-email/font@0.0.10(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/head@0.0.13(react@19.2.4)': + '@react-email/head@0.0.13(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/heading@0.0.16(react@19.2.4)': + '@react-email/heading@0.0.16(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/hr@0.0.12(react@19.2.4)': + '@react-email/hr@0.0.12(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/html@0.0.12(react@19.2.4)': + '@react-email/html@0.0.12(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/img@0.0.12(react@19.2.4)': + '@react-email/img@0.0.12(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/link@0.0.13(react@19.2.4)': + '@react-email/link@0.0.13(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/markdown@0.0.18(react@19.2.4)': + '@react-email/markdown@0.0.18(react@19.3.0)': dependencies: marked: 15.0.12 - react: 19.2.4 + react: 19.3.0 - '@react-email/preview@0.0.14(react@19.2.4)': + '@react-email/preview@0.0.14(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-email/render@2.0.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: html-to-text: 9.0.5 - prettier: 3.8.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + prettier: 3.9.9 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - '@react-email/row@0.0.13(react@19.2.4)': + '@react-email/render@2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - react: 19.2.4 + entities: 4.5.0 + html-to-text: 9.0.5 + html5parser: 3.0.0 + prettier: 3.9.9 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - '@react-email/section@0.0.17(react@19.2.4)': + '@react-email/row@0.0.13(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@react-email/tailwind@2.0.5(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)': + '@react-email/section@0.0.17(react@19.3.0)': dependencies: - '@react-email/text': 0.1.6(react@19.2.4) - react: 19.2.4 - tailwindcss: 4.2.1 + react: 19.3.0 + + '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.3.0))(@react-email/button@0.2.1(react@19.3.0))(@react-email/code-block@0.2.1(react@19.3.0))(@react-email/code-inline@0.0.6(react@19.3.0))(@react-email/container@0.0.16(react@19.3.0))(@react-email/heading@0.0.16(react@19.3.0))(@react-email/hr@0.0.12(react@19.3.0))(@react-email/img@0.0.12(react@19.3.0))(@react-email/link@0.0.13(react@19.3.0))(@react-email/preview@0.0.14(react@19.3.0))(@react-email/text@0.1.6(react@19.3.0))(react@19.3.0)': + dependencies: + '@react-email/text': 0.1.6(react@19.3.0) + react: 19.3.0 + tailwindcss: 4.3.3 optionalDependencies: - '@react-email/body': 0.2.1(react@19.2.4) - '@react-email/button': 0.2.1(react@19.2.4) - '@react-email/code-block': 0.2.1(react@19.2.4) - '@react-email/code-inline': 0.0.6(react@19.2.4) - '@react-email/container': 0.0.16(react@19.2.4) - '@react-email/heading': 0.0.16(react@19.2.4) - '@react-email/hr': 0.0.12(react@19.2.4) - '@react-email/img': 0.0.12(react@19.2.4) - '@react-email/link': 0.0.13(react@19.2.4) - '@react-email/preview': 0.0.14(react@19.2.4) + '@react-email/body': 0.3.0(react@19.3.0) + '@react-email/button': 0.2.1(react@19.3.0) + '@react-email/code-block': 0.2.1(react@19.3.0) + '@react-email/code-inline': 0.0.6(react@19.3.0) + '@react-email/container': 0.0.16(react@19.3.0) + '@react-email/heading': 0.0.16(react@19.3.0) + '@react-email/hr': 0.0.12(react@19.3.0) + '@react-email/img': 0.0.12(react@19.3.0) + '@react-email/link': 0.0.13(react@19.3.0) + '@react-email/preview': 0.0.14(react@19.3.0) - '@react-email/text@0.1.6(react@19.2.4)': + '@react-email/text@0.1.6(react@19.3.0)': dependencies: - react: 19.2.4 + react: 19.3.0 - '@rollup/plugin-commonjs@28.0.1(rollup@4.63.5)': + '@rollup/plugin-commonjs@28.0.1(rollup@4.64.0)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.63.5) + '@rollup/pluginutils': 5.4.0(rollup@4.64.0) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.7) is-reference: 1.2.1 magic-string: 0.30.21 - picomatch: 4.0.3 + picomatch: 4.0.7 optionalDependencies: - rollup: 4.63.5 + rollup: 4.64.0 - '@rollup/pluginutils@5.4.0(rollup@4.63.5)': + '@rollup/pluginutils@5.4.0(rollup@4.64.0)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.3 + picomatch: 4.0.7 optionalDependencies: - rollup: 4.63.5 + rollup: 4.64.0 - '@rollup/rollup-android-arm-eabi@4.63.5': + '@rollup/rollup-android-arm-eabi@4.64.0': optional: true - '@rollup/rollup-android-arm64@4.63.5': + '@rollup/rollup-android-arm64@4.64.0': optional: true - '@rollup/rollup-darwin-arm64@4.63.5': + '@rollup/rollup-darwin-arm64@4.64.0': optional: true - '@rollup/rollup-darwin-x64@4.63.5': + '@rollup/rollup-darwin-x64@4.64.0': optional: true - '@rollup/rollup-freebsd-arm64@4.63.5': + '@rollup/rollup-freebsd-arm64@4.64.0': optional: true - '@rollup/rollup-freebsd-x64@4.63.5': + '@rollup/rollup-freebsd-x64@4.64.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.63.5': + '@rollup/rollup-linux-arm-gnueabihf@4.64.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.63.5': + '@rollup/rollup-linux-arm-musleabihf@4.64.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.63.5': + '@rollup/rollup-linux-arm64-gnu@4.64.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.63.5': + '@rollup/rollup-linux-arm64-musl@4.64.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.63.5': + '@rollup/rollup-linux-loong64-gnu@4.64.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.63.5': + '@rollup/rollup-linux-loong64-musl@4.64.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.63.5': + '@rollup/rollup-linux-ppc64-gnu@4.64.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.63.5': + '@rollup/rollup-linux-ppc64-musl@4.64.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.63.5': + '@rollup/rollup-linux-riscv64-gnu@4.64.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.63.5': + '@rollup/rollup-linux-riscv64-musl@4.64.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.63.5': + '@rollup/rollup-linux-s390x-gnu@4.64.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.63.5': + '@rollup/rollup-linux-x64-gnu@4.64.0': optional: true - '@rollup/rollup-linux-x64-musl@4.63.5': + '@rollup/rollup-linux-x64-musl@4.64.0': optional: true - '@rollup/rollup-openbsd-x64@4.63.5': + '@rollup/rollup-openbsd-x64@4.64.0': optional: true - '@rollup/rollup-openharmony-arm64@4.63.5': + '@rollup/rollup-openharmony-arm64@4.64.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.63.5': + '@rollup/rollup-win32-arm64-msvc@4.64.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.63.5': + '@rollup/rollup-win32-ia32-msvc@4.64.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.63.5': + '@rollup/rollup-win32-x64-gnu@4.64.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.63.5': + '@rollup/rollup-win32-x64-msvc@4.64.0': optional: true '@rtsao/scc@1.1.0': {} @@ -5389,303 +5770,286 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 - '@sentry/browser-utils@11.0.0': + '@sentry/browser-utils@11.4.0': dependencies: - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 web-vitals: 6.2.2 - '@sentry/browser@11.0.0': + '@sentry/browser@11.4.0': dependencies: - '@sentry/browser-utils': 11.0.0 - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 - '@sentry/feedback': 11.0.0 - '@sentry/replay': 11.0.0 - '@sentry/replay-canvas': 11.0.0 + '@sentry/browser-utils': 11.4.0 + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 + '@sentry/feedback': 11.4.0 + '@sentry/replay': 11.4.0 + '@sentry/replay-canvas': 11.4.0 - '@sentry/bundler-plugins@11.0.0(rollup@4.63.5)': + '@sentry/bundler-plugins@11.4.0(@babel/core@7.29.7)(rollup@4.64.0)': dependencies: - '@babel/core': 7.29.0(supports-color@8.1.1) - '@sentry/core': 11.0.0 + '@sentry/core': 11.4.0 dotenv: 17.4.2 glob: 13.0.6 - magic-string: 0.30.21 - oxc-parser: 0.143.0 - sentry: 0.44.1 + magic-string: 1.4.2 + oxc-parser: 0.152.0 + sentry: 0.45.0 supports-color: 8.1.1 optionalDependencies: - rollup: 4.63.5 + '@babel/core': 7.29.7 + rollup: 4.64.0 - '@sentry/conventions@0.23.0': {} + '@sentry/conventions@0.25.0': {} - '@sentry/core@11.0.0': + '@sentry/core@11.4.0': dependencies: - '@sentry/conventions': 0.23.0 + '@sentry/conventions': 0.25.0 - '@sentry/feedback@11.0.0': + '@sentry/feedback@11.4.0': dependencies: - '@sentry/core': 11.0.0 + '@sentry/core': 11.4.0 - '@sentry/nextjs@11.0.0(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': + '@sentry/nextjs@11.4.0(@babel/core@7.29.7)(next@16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0)': dependencies: '@opentelemetry/api': 1.9.1 - '@rollup/plugin-commonjs': 28.0.1(rollup@4.63.5) - '@sentry/browser-utils': 11.0.0 - '@sentry/bundler-plugins': 11.0.0(rollup@4.63.5) - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 - '@sentry/node': 11.0.0(rollup@4.63.5) - '@sentry/opentelemetry': 11.0.0(@opentelemetry/api@1.9.1) - '@sentry/react': 11.0.0(react@19.2.4) - '@sentry/server-runtime-injection': 11.0.0 - '@sentry/server-utils': 11.0.0 - '@sentry/vercel-edge': 11.0.0 - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - rollup: 4.63.5 + '@rollup/plugin-commonjs': 28.0.1(rollup@4.64.0) + '@sentry/browser-utils': 11.4.0 + '@sentry/bundler-plugins': 11.4.0(@babel/core@7.29.7)(rollup@4.64.0) + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 + '@sentry/node': 11.4.0(@babel/core@7.29.7)(rollup@4.64.0) + '@sentry/opentelemetry': 11.4.0(@opentelemetry/api@1.9.1) + '@sentry/react': 11.4.0(react@19.3.0) + '@sentry/server-runtime-injection': 11.4.0 + '@sentry/server-utils': 11.4.0 + '@sentry/vercel-edge': 11.4.0 + next: 16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + rollup: 4.64.0 stacktrace-parser: 0.1.11 transitivePeerDependencies: + - '@babel/core' - react - react-router - webpack - '@sentry/node@11.0.0(rollup@4.63.5)': + '@sentry/node@11.4.0(@babel/core@7.29.7)(rollup@4.64.0)': dependencies: '@opentelemetry/api': 1.9.1 - '@sentry/bundler-plugins': 11.0.0(rollup@4.63.5) - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 - '@sentry/opentelemetry': 11.0.0(@opentelemetry/api@1.9.1) - '@sentry/server-runtime-injection': 11.0.0 - '@sentry/server-utils': 11.0.0 + '@sentry/bundler-plugins': 11.4.0(@babel/core@7.29.7)(rollup@4.64.0) + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 + '@sentry/opentelemetry': 11.4.0(@opentelemetry/api@1.9.1) + '@sentry/server-runtime-injection': 11.4.0 + '@sentry/server-utils': 11.4.0 transitivePeerDependencies: + - '@babel/core' - rollup - webpack - '@sentry/opentelemetry@11.0.0(@opentelemetry/api@1.9.1)': + '@sentry/opentelemetry@11.4.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 - '@sentry/react@11.0.0(react@19.2.4)': + '@sentry/react@11.4.0(react@19.3.0)': dependencies: - '@sentry/browser': 11.0.0 - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 - react: 19.2.4 + '@sentry/browser': 11.4.0 + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 + react: 19.3.0 - '@sentry/replay-canvas@11.0.0': + '@sentry/replay-canvas@11.4.0': dependencies: - '@sentry/core': 11.0.0 - '@sentry/replay': 11.0.0 + '@sentry/core': 11.4.0 + '@sentry/replay': 11.4.0 - '@sentry/replay@11.0.0': + '@sentry/replay@11.4.0': dependencies: - '@sentry/browser-utils': 11.0.0 - '@sentry/core': 11.0.0 + '@sentry/browser-utils': 11.4.0 + '@sentry/core': 11.4.0 - '@sentry/server-runtime-injection@11.0.0': + '@sentry/server-runtime-injection@11.4.0': dependencies: - '@sentry/core': 11.0.0 - '@sentry/server-utils': 11.0.0 + '@sentry/core': 11.4.0 + '@sentry/server-utils': 11.4.0 - '@sentry/server-utils@11.0.0': + '@sentry/server-utils@11.4.0': dependencies: '@opentelemetry/api': 1.9.1 - '@sentry/conventions': 0.23.0 - '@sentry/core': 11.0.0 + '@sentry/conventions': 0.25.0 + '@sentry/core': 11.4.0 - '@sentry/vercel-edge@11.0.0': + '@sentry/vercel-edge@11.4.0': dependencies: '@opentelemetry/api': 1.9.1 - '@sentry/core': 11.0.0 - '@sentry/server-utils': 11.0.0 - - '@smithy/core@3.24.7': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.14.4 - tslib: 2.8.1 - - '@smithy/credential-provider-imds@4.3.9': - dependencies: - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@5.4.7': - dependencies: - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 - tslib: 2.8.1 + '@sentry/core': 11.4.0 + '@sentry/server-utils': 11.4.0 - '@smithy/is-array-buffer@2.2.0': + '@smithy/core@3.35.1': dependencies: + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@smithy/node-http-handler@4.7.8': + '@smithy/credential-provider-imds@4.5.2': dependencies: - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@smithy/signature-v4@5.4.7': + '@smithy/fetch-http-handler@5.8.0': dependencies: - '@smithy/core': 3.24.7 - '@smithy/types': 4.14.4 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@smithy/types@4.14.4': + '@smithy/node-http-handler@4.12.1': dependencies: + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@smithy/util-buffer-from@2.2.0': + '@smithy/signature-v4@5.7.4': dependencies: - '@smithy/is-array-buffer': 2.2.0 + '@smithy/core': 3.35.1 + '@smithy/types': 4.19.0 tslib: 2.8.1 - '@smithy/util-utf8@2.3.0': + '@smithy/types@4.19.0': dependencies: - '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 '@stablelib/base64@1.0.1': {} '@standard-schema/spec@1.1.0': {} - '@supabase/auth-js@2.98.0': + '@supabase/auth-js@2.117.2': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.98.0': + '@supabase/functions-js@2.117.2': dependencies: tslib: 2.8.1 - '@supabase/postgrest-js@2.98.0': + '@supabase/phoenix@0.4.5': {} + + '@supabase/postgrest-js@2.117.2': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.98.0': + '@supabase/realtime-js@2.117.2': dependencies: - '@types/phoenix': 1.6.7 - '@types/ws': 8.18.1 + '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - ws: 8.19.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - '@supabase/ssr@0.9.0(@supabase/supabase-js@2.98.0)': + '@supabase/ssr@0.9.0(@supabase/supabase-js@2.117.2(@opentelemetry/api@1.9.1))': dependencies: - '@supabase/supabase-js': 2.98.0 + '@supabase/supabase-js': 2.117.2(@opentelemetry/api@1.9.1) cookie: 1.1.1 - '@supabase/storage-js@2.98.0': + '@supabase/storage-js@2.117.2': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.98.0': + '@supabase/supabase-js@2.117.2(@opentelemetry/api@1.9.1)': dependencies: - '@supabase/auth-js': 2.98.0 - '@supabase/functions-js': 2.98.0 - '@supabase/postgrest-js': 2.98.0 - '@supabase/realtime-js': 2.98.0 - '@supabase/storage-js': 2.98.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate + '@supabase/auth-js': 2.117.2 + '@supabase/functions-js': 2.117.2 + '@supabase/postgrest-js': 2.117.2 + '@supabase/realtime-js': 2.117.2 + '@supabase/storage-js': 2.117.2 + optionalDependencies: + '@opentelemetry/api': 1.9.1 - '@swc/helpers@0.5.15': + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 - '@tailwindcss/node@4.2.1': + '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.0 - jiti: 2.6.1 - lightningcss: 1.31.1 + enhanced-resolve: 5.26.0 + jiti: 2.7.0 + lightningcss: 1.32.0 magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.1 + source-map-js: 1.2.2 + tailwindcss: 4.3.3 - '@tailwindcss/oxide-android-arm64@4.2.1': + '@tailwindcss/oxide-android-arm64@4.3.3': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.1': + '@tailwindcss/oxide-darwin-arm64@4.3.3': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.1': + '@tailwindcss/oxide-darwin-x64@4.3.3': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.1': + '@tailwindcss/oxide-freebsd-x64@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.1': + '@tailwindcss/oxide-linux-x64-musl@4.3.3': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.1': + '@tailwindcss/oxide-wasm32-wasi@4.3.3': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': optional: true - '@tailwindcss/oxide@4.2.1': + '@tailwindcss/oxide@4.3.3': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.1 - '@tailwindcss/oxide-darwin-arm64': 4.2.1 - '@tailwindcss/oxide-darwin-x64': 4.2.1 - '@tailwindcss/oxide-freebsd-x64': 4.2.1 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 - '@tailwindcss/oxide-linux-x64-musl': 4.2.1 - '@tailwindcss/oxide-wasm32-wasi': 4.2.1 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/postcss@4.2.1': + '@tailwindcss/postcss@4.3.3': dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.2.1 - '@tailwindcss/oxide': 4.2.1 - postcss: 8.5.8 - tailwindcss: 4.2.1 + '@alloc/quick-lru': 5.3.0 + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + postcss: 8.5.28 + tailwindcss: 4.3.3 - '@tanstack/query-core@5.101.0': {} + '@tanstack/query-core@5.104.1': {} - '@tanstack/react-query@5.101.0(react@19.2.4)': + '@tanstack/react-query@5.104.1(react@19.3.0)': dependencies: - '@tanstack/query-core': 5.101.0 - react: 19.2.4 + '@tanstack/query-core': 5.104.1 + react: 19.3.0 - '@tanstack/react-table@8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-table@8.21.3(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/table-core': 8.21.3 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) '@tanstack/table-core@8.21.3': {} - '@tybys/wasm-util@0.10.1': + '@tybys/wasm-util@0.10.4': dependencies: tslib: 2.8.1 optional: true @@ -5694,225 +6058,394 @@ snapshots: dependencies: bcryptjs: 3.0.3 - '@types/estree@1.0.8': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.0.3': {} + + '@types/d3-color@3.1.0': {} + + '@types/d3-delaunay@6.0.1': {} + + '@types/d3-format@3.0.1': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-interpolate@3.0.1': + dependencies: + '@types/d3-color': 3.1.0 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.2': + dependencies: + '@types/d3-time': 3.0.0 + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@2.1.0': {} + + '@types/d3-time@3.0.0': {} + + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} - '@types/node@25.3.5': + '@types/lodash@4.17.25': {} + + '@types/node@24.19.1': dependencies: - undici-types: 7.18.2 + undici-types: 7.24.6 - '@types/pg@8.20.0': + '@types/node@25.9.9': dependencies: - '@types/node': 25.3.5 - pg-protocol: 1.14.0 - pg-types: 2.2.0 + undici-types: 7.24.6 - '@types/phoenix@1.6.7': {} + '@types/pg@8.23.1': + dependencies: + '@types/node': 25.9.9 + pg-protocol: 1.16.1 + pg-types: 2.2.0 '@types/qrcode@1.5.6': dependencies: - '@types/node': 25.3.5 + '@types/node': 25.9.9 - '@types/react-dom@19.2.3(@types/react@19.2.14)': + '@types/react-dom@19.3.0(@types/react@19.3.0)': dependencies: - '@types/react': 19.2.14 + '@types/react': 19.3.0 - '@types/react@19.2.14': + '@types/react@19.3.0': dependencies: csstype: 3.2.3 - '@types/stylis@4.2.7': {} - '@types/trusted-types@2.0.7': optional: true - '@types/ws@8.18.1': - dependencies: - '@types/node': 25.3.5 - - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.1 - eslint: 9.39.4(jiti@2.6.1) - ignore: 7.0.5 + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 9.39.5(jiti@2.7.0) + ignore: 7.0.12 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.1 - debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3 + eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.71.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 - debug: 4.4.3(supports-color@8.1.1) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 + debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.56.1': + '@typescript-eslint/scope-manager@8.71.0': dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 - '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.4(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.5(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/types@8.71.0': {} - '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.71.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 - debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.71.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.56.1': + '@typescript-eslint/visitor-keys@8.71.0': dependencies: - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 - '@unrs/resolver-binding-android-arm-eabi@1.11.1': + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true - '@unrs/resolver-binding-android-arm64@1.11.1': + '@unrs/resolver-binding-android-arm64@1.12.2': optional: true - '@unrs/resolver-binding-darwin-arm64@1.11.1': + '@unrs/resolver-binding-darwin-arm64@1.12.2': optional: true - '@unrs/resolver-binding-darwin-x64@1.11.1': + '@unrs/resolver-binding-darwin-x64@1.12.2': optional: true - '@unrs/resolver-binding-freebsd-x64@1.11.1': + '@unrs/resolver-binding-freebsd-x64@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': optional: true - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': optional: true - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': optional: true - '@unrs/resolver-binding-linux-x64-musl@1.11.1': + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': optional: true - '@unrs/resolver-binding-wasm32-wasi@1.11.1': + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': dependencies: - '@napi-rs/wasm-runtime': 0.2.12 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true '@upstash/core-analytics@0.0.10': dependencies: - '@upstash/redis': 1.38.0 + '@upstash/redis': 1.39.0 - '@upstash/qstash@2.9.0': + '@upstash/qstash@2.12.0': dependencies: - crypto-js: 4.2.0 jose: 5.10.0 neverthrow: 7.2.0 + uncrypto: 0.1.3 - '@upstash/ratelimit@2.0.8(@upstash/redis@1.38.0)': + '@upstash/ratelimit@2.2.0(@upstash/redis@1.39.0)': dependencies: '@upstash/core-analytics': 0.0.10 - '@upstash/redis': 1.38.0 + '@upstash/redis': 1.39.0 - '@upstash/redis@1.38.0': + '@upstash/redis@1.39.0': dependencies: uncrypto: 0.1.3 - '@yr/monotone-cubic-spline@1.0.3': {} + '@visx/curve@4.0.1-alpha.0': + dependencies: + '@visx/vendor': 4.0.0-alpha.0 + + '@visx/event@4.0.1-alpha.0': + dependencies: + '@types/react': 19.3.0 + '@visx/point': 4.0.1-alpha.0 + + '@visx/grid@4.0.1-alpha.0(react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + '@visx/curve': 4.0.1-alpha.0 + '@visx/group': 4.0.1-alpha.0(react@19.3.0) + '@visx/point': 4.0.1-alpha.0 + '@visx/scale': 4.0.1-alpha.0 + '@visx/shape': 4.0.1-alpha.0(react@19.3.0) + classnames: 2.5.1 + react: 19.3.0 + + '@visx/group@4.0.1-alpha.0(react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + classnames: 2.5.1 + react: 19.3.0 + + '@visx/point@4.0.1-alpha.0': {} + + '@visx/responsive@4.0.1-alpha.0(react@19.3.0)': + dependencies: + '@types/lodash': 4.17.25 + '@types/react': 19.3.0 + lodash: 4.18.1 + react: 19.3.0 + + '@visx/scale@4.0.1-alpha.0': + dependencies: + '@visx/vendor': 4.0.0-alpha.0 + + '@visx/shape@4.0.1-alpha.0(react@19.3.0)': + dependencies: + '@types/lodash': 4.17.25 + '@types/react': 19.3.0 + '@visx/curve': 4.0.1-alpha.0 + '@visx/group': 4.0.1-alpha.0(react@19.3.0) + '@visx/scale': 4.0.1-alpha.0 + '@visx/vendor': 4.0.0-alpha.0 + classnames: 2.5.1 + lodash: 4.18.1 + react: 19.3.0 + + '@visx/vendor@4.0.0-alpha.0': + dependencies: + '@types/d3-array': 3.0.3 + '@types/d3-color': 3.1.0 + '@types/d3-delaunay': 6.0.1 + '@types/d3-format': 3.0.1 + '@types/d3-geo': 3.1.0 + '@types/d3-interpolate': 3.0.1 + '@types/d3-path': 3.1.1 + '@types/d3-scale': 4.0.2 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.0 + '@types/d3-time-format': 2.1.0 + d3-array: 3.2.1 + d3-color: 3.1.0 + d3-delaunay: 6.0.2 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + internmap: 2.0.3 + + '@vitest/expect@3.2.7': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.7(vite@7.3.6(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15))': + dependencies: + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.6(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15) + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.7': + dependencies: + '@vitest/utils': 3.2.7 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.7': + dependencies: + tinyspy: 4.0.6 + + '@vitest/utils@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + loupe: 3.2.1 + tinyrainbow: 2.0.0 - acorn-jsx@5.3.2(acorn@8.16.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.16.0 + acorn: 8.18.0 - acorn@8.16.0: {} + acorn@8.18.0: {} - ajv@6.14.0: + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -5922,7 +6455,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 3.1.8 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -5932,11 +6465,11 @@ snapshots: dependencies: color-convert: 2.0.1 - anynum@1.0.0: {} + apex-commons@0.8.1: {} - apexcharts@5.10.3: + apexcharts@7.8.0: dependencies: - '@yr/monotone-cubic-spline': 1.0.3 + apex-commons: 0.8.1 argparse@2.0.1: {} @@ -5947,68 +6480,70 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 - array-includes@3.1.9: + array-includes@3.2.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 is-string: 1.1.1 math-intrinsics: 1.1.0 array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 array.prototype.findlastindex@1.2.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} @@ -6021,80 +6556,83 @@ snapshots: aws-ssl-profiles@1.1.2: {} - axe-core@4.11.1: {} + axe-core@4.13.0: {} - axios@1.13.6: + axios@1.20.0: dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.5 - proxy-from-env: 1.1.0 + follow-redirects: 1.16.1 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug + - supports-color axobject-query@4.1.0: {} babel-plugin-react-compiler@1.0.0: dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.8 balanced-match@1.0.2: {} balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.0: {} + baseline-browser-mapping@2.11.27: {} bcryptjs@3.0.3: {} - better-auth@1.6.18(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(mysql2@3.15.3)(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.21.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + better-auth@1.6.18(@opentelemetry/api@1.9.1)(@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3))(mysql2@3.15.3)(next@16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.1)(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@3.2.7(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)): dependencies: - '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0) - '@better-auth/drizzle-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/kysely-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2) - '@better-auth/memory-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/mongo-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/prisma-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) - '@better-auth/telemetry': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.2)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0) + '@better-auth/core': 1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4) + '@better-auth/drizzle-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1) + '@better-auth/kysely-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)(kysely@0.29.6) + '@better-auth/memory-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1) + '@better-auth/mongo-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1) + '@better-auth/prisma-adapter': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)(@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3)) + '@better-auth/telemetry': 1.6.18(@better-auth/core@1.6.18(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.3.0) '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.3.0 - '@noble/ciphers': 2.2.0 - '@noble/hashes': 2.2.0 - better-call: 1.3.6(zod@4.4.3) + '@noble/ciphers': 2.4.0 + '@noble/hashes': 2.4.0 + better-call: 1.3.6(zod@4.6.5) defu: 6.1.7 - jose: 6.2.2 - kysely: 0.29.2 - nanostores: 1.3.0 - zod: 4.4.3 + jose: 6.2.12 + kysely: 0.29.6 + nanostores: 1.5.4 + zod: 4.6.5 optionalDependencies: - '@prisma/client': 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) + '@prisma/client': 7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3))(typescript@5.9.3) mysql2: 3.15.3 - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - pg: 8.21.0 - prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + next: 16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + pg: 8.23.1 + prisma: 7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + vitest: 3.2.7(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' - better-call@1.3.6(zod@4.4.3): + better-call@1.3.6(zod@4.6.5): dependencies: - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.3.0 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.2 rou3: 0.7.12 - set-cookie-parser: 3.1.0 + set-cookie-parser: 3.1.2 optionalDependencies: - zod: 4.4.3 + zod: 4.6.5 - better-result@2.9.2: {} + better-result@2.10.0: {} bowser@2.14.1: {} - brace-expansion@1.1.12: + brace-expansion@1.1.21: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.4: + brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 @@ -6102,13 +6640,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: + browserslist@4.29.3: dependencies: - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001777 - electron-to-chromium: 1.5.307 - node-releases: 2.0.36 - update-browserslist-db: 1.2.3(browserslist@4.28.1) + baseline-browser-mapping: 2.11.27 + caniuse-lite: 1.0.30001814 + electron-to-chromium: 1.5.444 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.3) c12@3.3.4: dependencies: @@ -6116,21 +6654,23 @@ snapshots: confbox: 0.2.4 defu: 6.1.7 dotenv: 17.4.2 - exsolve: 1.0.8 - giget: 3.3.0 - jiti: 2.6.1 - ohash: 2.0.11 + exsolve: 1.1.3 + giget: 3.3.1 + jiti: 2.7.0 + ohash: 2.0.12 pathe: 2.0.3 perfect-debounce: 2.1.0 - pkg-types: 2.3.1 - rc9: 3.0.1 + pkg-types: 2.3.3 + rc9: 3.1.0 + + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -6146,22 +6686,28 @@ snapshots: camelcase@5.3.1: {} - camelize@1.0.1: {} + caniuse-lite@1.0.30001814: {} - caniuse-lite@1.0.30001777: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chart.js@4.5.1: - dependencies: - '@kurkle/color': 0.3.4 + check-error@2.1.3: {} chokidar@5.0.0: dependencies: - readdirp: 5.0.0 + readdirp: 5.1.1 + + classnames@2.5.1: {} client-only@0.0.1: {} @@ -6189,6 +6735,8 @@ snapshots: confbox@0.2.4: {} + confbox@0.3.1: {} + convert-source-map@2.0.0: {} cookie@1.1.1: {} @@ -6201,17 +6749,53 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-js@4.2.0: {} + csstype@3.2.3: {} - css-color-keywords@1.0.0: {} + d3-array@3.2.1: + dependencies: + internmap: 2.0.3 - css-to-react-native@3.2.0: + d3-array@3.2.4: dependencies: - camelize: 1.0.1 - css-color-keywords: 1.0.0 - postcss-value-parser: 4.2.0 + internmap: 2.0.3 - csstype@3.2.3: {} + d3-color@3.1.0: {} + + d3-delaunay@6.0.2: + dependencies: + delaunator: 5.1.0 + + d3-format@3.1.0: {} + + d3-geo@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 damerau-levenshtein@1.0.8: {} @@ -6237,14 +6821,14 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.3(supports-color@8.1.1): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 decamelize@1.2.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} deepmerge-ts@7.1.5: {} @@ -6265,6 +6849,10 @@ snapshots: defu@6.1.7: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} denque@2.1.0: {} @@ -6314,7 +6902,9 @@ snapshots: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - electron-to-chromium@1.5.307: {} + electron-to-chromium@1.5.444: {} + + elkjs@0.11.1: {} emoji-regex@8.0.0: {} @@ -6322,31 +6912,38 @@ snapshots: empathic@2.0.0: {} - enhanced-resolve@5.20.0: + enhanced-resolve@5.26.0: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.3 entities@4.5.0: {} env-paths@3.0.0: {} - es-abstract@1.24.1: + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 @@ -6355,7 +6952,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -6371,33 +6968,33 @@ snapshots: object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 - own-keys: 1.0.1 + own-keys: 1.0.2 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 + typed-array-byte-offset: 1.0.5 + typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 + which-typed-array: 1.1.24 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-iterator-helpers@1.2.2: + es-iterator-helpers@1.4.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -6409,9 +7006,11 @@ snapshots: has-symbols: 1.1.0 internal-slot: 1.1.0 iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 + math-intrinsics: 1.1.0 - es-object-atoms@1.1.1: + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -6420,63 +7019,66 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 - es-to-primitive@1.3.0: + es-to-primitive@1.3.4: dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.28.1: + esbuild@0.28.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-config-next@16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.3.8(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 16.1.6 - eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + '@next/eslint-plugin-next': 16.3.8(eslint@9.39.5(jiti@2.7.0)) + eslint: 9.39.5(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0)) globals: 16.4.0 - typescript-eslint: 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -6485,81 +7087,81 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-import-resolver-node@0.3.9: + eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.11 + is-core-module: 2.17.0 + resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.4(jiti@2.6.1) - get-tsconfig: 4.13.6 + debug: 4.4.3 + eslint: 9.39.5(jiti@2.7.0) + get-tsconfig: 4.14.3 is-bun-module: 2.0.0 stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 + tinyglobby: 0.2.17 + unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) - hasown: 2.0.2 - is-core-module: 2.16.1 + eslint: 9.39.5(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + hasown: 2.0.4 + is-core-module: 2.17.0 is-glob: 4.0.3 minimatch: 3.1.5 object.fromentries: 2.0.8 object.groupby: 1.0.3 object.values: 1.2.1 semver: 6.3.1 - string.prototype.trimend: 1.0.9 + string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)): dependencies: aria-query: 5.3.2 - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.11.1 + axe-core: 4.13.0 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.4(jiti@2.6.1) - hasown: 2.0.2 + eslint: 9.39.5(jiti@2.7.0) + hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 minimatch: 3.1.5 @@ -6567,37 +7169,37 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)): dependencies: - '@babel/core': 7.29.0(supports-color@8.1.1) - '@babel/parser': 7.29.0 - eslint: 9.39.4(jiti@2.6.1) + '@babel/core': 7.29.7 + '@babel/parser': 7.29.9 + eslint: 9.39.5(jiti@2.7.0) hermes-parser: 0.25.1 - zod: 4.4.3 - zod-validation-error: 4.0.2(zod@4.4.3) + zod: 4.6.5 + zod-validation-error: 4.0.2(zod@4.6.5) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)): dependencies: - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.2.2 - eslint: 9.39.4(jiti@2.6.1) + es-iterator-helpers: 1.4.0 + eslint: 9.39.5(jiti@2.7.0) estraverse: 5.3.0 - hasown: 2.0.2 + hasown: 2.0.4 jsx-ast-utils: 3.3.5 minimatch: 3.1.5 object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 - resolve: 2.0.0-next.6 + resolve: 2.0.0-next.7 semver: 6.3.1 - string.prototype.matchall: 4.0.12 + string.prototype.matchall: 4.1.0 string.prototype.repeat: 1.0.0 eslint-scope@8.4.0: @@ -6611,24 +7213,24 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.6.1): + eslint@9.39.5(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 + '@eslint/eslintrc': 3.3.7 + '@eslint/js': 9.39.5 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 + '@types/estree': 1.0.9 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -6648,14 +7250,14 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color espree@10.4.0: dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 4.2.1 esquery@1.7.0: @@ -6670,14 +7272,22 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} - exsolve@1.0.8: {} + expect-type@1.4.0: {} + + exsolve@1.1.3: {} fast-check@3.23.2: dependencies: pure-rand: 6.1.0 + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -6692,29 +7302,21 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-sha256@1.3.0: {} - - fast-uri@3.1.2: {} - - fast-xml-builder@1.2.0: + fast-querystring@1.1.2: dependencies: - path-expression-matcher: 1.5.0 - xml-naming: 0.1.0 + fast-decode-uri-component: 1.0.1 - fast-xml-parser@5.7.3: - dependencies: - '@nodable/entities': 2.2.0 - fast-xml-builder: 1.2.0 - path-expression-matcher: 1.5.0 - strnum: 2.4.0 + fast-sha256@1.3.0: {} - fastq@1.20.1: + fast-uri@3.1.8: {} + + fastq@1.20.3: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.7 fflate@0.4.9: {} @@ -6726,6 +7328,12 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-my-way@9.7.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.1 + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -6738,12 +7346,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.4 + flatted: 3.4.4 keyv: 4.5.4 - flatted@3.3.4: {} + flatted@3.4.4: {} - follow-redirects@1.15.11: {} + follow-redirects@1.16.1: {} for-each@0.3.5: dependencies: @@ -6754,37 +7362,40 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.4 mime-types: 2.1.35 - framer-motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + framer-motion@12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - motion-dom: 12.38.0 - motion-utils: 12.36.0 + motion-dom: 12.43.0 + motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.4.0 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) fsevents@2.3.3: optional: true function-bind@1.1.2: {} - function.prototype.name@1.1.8: + function.prototype.name@1.2.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 - define-properties: 1.2.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 functions-have-names: 1.2.3 - hasown: 2.0.2 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 is-callable: 1.2.7 + is-document.all: 1.0.0 functions-have-names@1.2.3: {} @@ -6803,12 +7414,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-port-please@3.2.0: {} @@ -6816,7 +7427,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-symbol-description@1.1.0: dependencies: @@ -6824,11 +7435,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.13.6: + get-tsconfig@4.14.3: dependencies: resolve-pkg-maps: 1.0.0 - giget@3.3.0: {} + giget@3.3.1: {} glob-parent@5.1.2: dependencies: @@ -6840,7 +7451,7 @@ snapshots: glob@13.0.6: dependencies: - minimatch: 10.2.4 + minimatch: 10.2.6 minipass: 7.1.3 path-scurry: 2.0.2 @@ -6857,7 +7468,7 @@ snapshots: graceful-fs@4.2.11: {} - grammex@3.1.12: {} + grammex@3.1.13: {} graphmatch@1.1.1: {} @@ -6879,7 +7490,7 @@ snapshots: dependencies: has-symbols: 1.1.0 - hasown@2.0.2: + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -6889,7 +7500,7 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hono@4.12.25: {} + hono@4.13.13: {} html-to-text@9.0.5: dependencies: @@ -6899,6 +7510,8 @@ snapshots: htmlparser2: 8.0.2 selderee: 0.11.0 + html5parser@3.0.0: {} + htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 @@ -6906,21 +7519,26 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 - http-status-codes@2.3.0: {} + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color iceberg-js@0.8.1: {} - iconify-icon@3.0.2: + iconify-icon@3.0.3: dependencies: '@iconify/types': 2.0.0 - iconv-lite@0.7.2: + iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.12: {} import-fresh@3.3.1: dependencies: @@ -6932,12 +7550,14 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 + hasown: 2.0.4 + side-channel: 1.1.1 + + internmap@2.0.3: {} is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -6960,13 +7580,13 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.5 is-callable@1.2.7: {} - is-core-module@2.16.1: + is-core-module@2.17.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 is-data-view@1.0.2: dependencies: @@ -6979,6 +7599,10 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -7014,14 +7638,14 @@ snapshots: is-reference@1.2.1: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 is-regex@1.2.1: dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 is-set@2.0.3: {} @@ -7042,7 +7666,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.20 + which-typed-array: 1.1.24 is-weakmap@2.0.2: {} @@ -7062,21 +7686,23 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 has-symbols: 1.1.0 set-function-name: 2.0.2 - jiti@2.6.1: {} + jiti@2.7.0: {} jose@5.10.0: {} - jose@6.2.2: {} + jose@6.2.12: {} js-tokens@4.0.0: {} - js-yaml@4.1.1: + js-tokens@9.0.1: {} + + js-yaml@4.3.2: dependencies: argparse: 2.0.1 @@ -7098,7 +7724,7 @@ snapshots: jsx-ast-utils@3.3.5: dependencies: - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.flat: 1.3.3 object.assign: 4.1.7 object.values: 1.2.1 @@ -7107,7 +7733,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - kysely@0.29.2: {} + kysely@0.29.6: {} language-subtag-registry@0.3.23: {} @@ -7122,54 +7748,54 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.31.1: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-arm64@1.31.1: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.31.1: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-freebsd-x64@1.31.1: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.31.1: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.31.1: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.31.1: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.31.1: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-linux-x64-musl@1.31.1: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.31.1: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.31.1: + lightningcss-win32-x64-msvc@1.32.0: optional: true - lightningcss@1.31.1: + lightningcss@1.32.0: 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 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 locate-path@5.0.0: dependencies: @@ -7181,27 +7807,35 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.18.1: {} + long@5.3.2: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@11.5.3: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lru.min@1.1.4: {} + lru.min@1.1.5: {} - lucide-react@0.577.0(react@19.2.4): + lucide-react@0.577.0(react@19.3.0): dependencies: - react: 19.2.4 + react: 19.3.0 magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 + + magic-string@1.4.2: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 marked@15.0.12: {} @@ -7212,7 +7846,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime-db@1.52.0: {} @@ -7220,23 +7854,23 @@ snapshots: dependencies: mime-db: 1.52.0 - minimatch@10.2.4: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.4 + brace-expansion: 5.0.12 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.21 minimist@1.2.8: {} minipass@7.1.3: {} - motion-dom@12.38.0: + motion-dom@12.43.0: dependencies: - motion-utils: 12.36.0 + motion-utils: 12.39.0 - motion-utils@12.36.0: {} + motion-utils@12.39.0: {} ms@2.1.3: {} @@ -7245,20 +7879,20 @@ snapshots: aws-ssl-profiles: 1.1.2 denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 long: 5.3.2 - lru.min: 1.1.4 + lru.min: 1.1.5 named-placeholders: 1.1.6 seq-queue: 0.0.5 sqlstring: 2.3.3 named-placeholders@1.1.6: dependencies: - lru.min: 1.1.4 + lru.min: 1.1.5 - nanoid@3.3.11: {} + nanoid@3.3.19: {} - nanostores@1.3.0: {} + nanostores@1.5.4: {} napi-postinstall@0.3.4: {} @@ -7266,44 +7900,39 @@ snapshots: neverthrow@7.2.0: {} - next-auth@5.0.0-beta.30(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): + next-themes@0.4.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - '@auth/core': 0.41.0 - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - next-themes@0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@16.3.8(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@25.9.9)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - '@next/env': 16.1.6 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001777 - postcss: 8.4.31 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) + '@next/env': 16.3.8 + '@swc/helpers': 0.5.23 + baseline-browser-mapping: 2.11.27 + caniuse-lite: 1.0.30001814 + postcss: 8.5.23 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.3.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.1.6 - '@next/swc-darwin-x64': 16.1.6 - '@next/swc-linux-arm64-gnu': 16.1.6 - '@next/swc-linux-arm64-musl': 16.1.6 - '@next/swc-linux-x64-gnu': 16.1.6 - '@next/swc-linux-x64-musl': 16.1.6 - '@next/swc-win32-arm64-msvc': 16.1.6 - '@next/swc-win32-x64-msvc': 16.1.6 + '@next/swc-darwin-arm64': 16.3.8 + '@next/swc-darwin-x64': 16.3.8 + '@next/swc-linux-arm64-gnu': 16.3.8 + '@next/swc-linux-arm64-musl': 16.3.8 + '@next/swc-linux-x64-gnu': 16.3.8 + '@next/swc-linux-x64-musl': 16.3.8 + '@next/swc-win32-arm64-msvc': 16.3.8 + '@next/swc-win32-x64-msvc': 16.3.8 '@opentelemetry/api': 1.9.1 babel-plugin-react-compiler: 1.0.0 - sharp: 0.34.5 + sharp: 0.35.5(@types/node@25.9.9) transitivePeerDependencies: - '@babel/core' + - '@types/node' - babel-plugin-macros - node-exports-info@1.6.0: + node-exports-info@1.6.2: dependencies: array.prototype.flatmap: 1.3.3 es-errors: 1.3.0 @@ -7314,9 +7943,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.36: {} - - oauth4webapi@3.8.5: {} + node-releases@2.0.57: {} object-assign@4.1.1: {} @@ -7326,41 +7953,41 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 object.groupby@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 - ohash@2.0.11: {} + ohash@2.0.12: {} optionator@0.9.4: dependencies: @@ -7371,35 +7998,36 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - own-keys@1.0.1: + own-keys@1.0.2: dependencies: + call-bound: 1.0.4 get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxc-parser@0.143.0: + oxc-parser@0.152.0: dependencies: - '@oxc-project/types': 0.143.0 + '@oxc-project/types': 0.152.0 optionalDependencies: - '@oxc-parser/binding-android-arm-eabi': 0.143.0 - '@oxc-parser/binding-android-arm64': 0.143.0 - '@oxc-parser/binding-darwin-arm64': 0.143.0 - '@oxc-parser/binding-darwin-x64': 0.143.0 - '@oxc-parser/binding-freebsd-x64': 0.143.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.143.0 - '@oxc-parser/binding-linux-arm-musleabihf': 0.143.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.143.0 - '@oxc-parser/binding-linux-arm64-musl': 0.143.0 - '@oxc-parser/binding-linux-ppc64-gnu': 0.143.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.143.0 - '@oxc-parser/binding-linux-riscv64-musl': 0.143.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.143.0 - '@oxc-parser/binding-linux-x64-gnu': 0.143.0 - '@oxc-parser/binding-linux-x64-musl': 0.143.0 - '@oxc-parser/binding-openharmony-arm64': 0.143.0 - '@oxc-parser/binding-win32-arm64-msvc': 0.143.0 - '@oxc-parser/binding-win32-ia32-msvc': 0.143.0 - '@oxc-parser/binding-win32-x64-msvc': 0.143.0 + '@oxc-parser/binding-android-arm-eabi': 0.152.0 + '@oxc-parser/binding-android-arm64': 0.152.0 + '@oxc-parser/binding-darwin-arm64': 0.152.0 + '@oxc-parser/binding-darwin-x64': 0.152.0 + '@oxc-parser/binding-freebsd-x64': 0.152.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.152.0 + '@oxc-parser/binding-linux-arm64-musl': 0.152.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.152.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-musl': 0.152.0 + '@oxc-parser/binding-openharmony-arm64': 0.152.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.152.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.152.0 + '@oxc-parser/binding-win32-x64-msvc': 0.152.0 p-limit@2.3.0: dependencies: @@ -7430,8 +8058,6 @@ snapshots: path-exists@4.0.0: {} - path-expression-matcher@1.5.0: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -7443,22 +8069,24 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + peberminta@0.9.0: {} perfect-debounce@2.1.0: {} - pg-cloudflare@1.4.0: + pg-cloudflare@1.4.1: optional: true - pg-connection-string@2.13.0: {} + pg-connection-string@2.14.1: {} pg-int8@1.0.1: {} - pg-pool@3.14.0(pg@8.21.0): + pg-pool@3.14.0(pg@8.23.1): dependencies: - pg: 8.21.0 + pg: 8.23.1 - pg-protocol@1.14.0: {} + pg-protocol@1.16.1: {} pg-types@2.2.0: dependencies: @@ -7468,15 +8096,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.21.0: + pg@8.23.1: dependencies: - pg-connection-string: 2.13.0 - pg-pool: 3.14.0(pg@8.21.0) - pg-protocol: 1.14.0 + pg-connection-string: 2.14.1 + pg-pool: 3.14.0(pg@8.23.1) + pg-protocol: 1.16.1 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.4.0 + pg-cloudflare: 1.4.1 pgpass@1.0.5: dependencies: @@ -7484,47 +8112,39 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.7: {} - pingram@1.0.1: + pingram@1.0.36: dependencies: node-fetch: 2.7.0 transitivePeerDependencies: - encoding - pkg-types@2.3.1: + pkg-types@2.3.3: dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 + confbox: 0.3.1 + exsolve: 1.1.3 pathe: 2.0.3 pngjs@5.0.0: {} possible-typed-array-names@1.1.0: {} - postal-mime@2.7.3: {} - - postcss-value-parser@4.2.0: {} - - postcss@8.4.31: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 + postal-mime@2.7.6: {} - postcss@8.4.49: + postcss@8.5.23: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.19 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 - postcss@8.5.8: + postcss@8.5.28: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.19 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 postgres-array@2.0.0: {} @@ -7540,44 +8160,36 @@ snapshots: postgres@3.4.7: {} - posthog-js@1.434.15(@types/react@19.2.14)(preact-render-to-string@6.5.11(preact@10.24.3))(react@19.2.4): + posthog-js@1.435.8(@types/react@19.3.0)(react@19.3.0): dependencies: - '@posthog/browser-common': 0.9.0 - '@posthog/core': 1.55.2 - '@posthog/types': 1.412.4 + '@posthog/browser-common': 0.9.1 + '@posthog/core': 1.55.3 + '@posthog/types': 1.413.0 core-js: 3.50.0 dompurify: 3.4.16 fflate: 0.4.9 - preact: 10.29.8(preact-render-to-string@6.5.11(preact@10.24.3)) + preact: 10.29.8 query-selector-shadow-dom: 1.0.1 web-vitals: 6.2.2 web-vitals-soft-navs: web-vitals@6.2.1 optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.4 + '@types/react': 19.3.0 + react: 19.3.0 transitivePeerDependencies: - preact-render-to-string - preact-render-to-string@6.5.11(preact@10.24.3): - dependencies: - preact: 10.24.3 - - preact@10.24.3: {} - - preact@10.29.8(preact-render-to-string@6.5.11(preact@10.24.3)): - optionalDependencies: - preact-render-to-string: 6.5.11(preact@10.24.3) + preact@10.29.8: {} prelude-ls@1.2.1: {} - prettier@3.8.1: {} + prettier@3.9.9: {} - prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@5.9.3): dependencies: - '@prisma/config': 7.8.0 - '@prisma/dev': 0.24.3(typescript@5.9.3) - '@prisma/engines': 7.8.0 - '@prisma/studio-core': 0.27.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@prisma/config': 7.10.0 + '@prisma/dev': 0.24.17(typescript@5.9.3) + '@prisma/engines': 7.10.0 + '@prisma/studio-core': 0.33.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) mysql2: 3.15.3 postgres: 3.4.7 optionalDependencies: @@ -7603,7 +8215,7 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 - proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} punycode@2.3.1: {} @@ -7619,42 +8231,42 @@ snapshots: queue-microtask@1.2.3: {} - rc9@3.0.1: + rc9@3.1.0: dependencies: defu: 6.1.7 destr: 2.0.5 - react-apexcharts@2.1.0(apexcharts@5.10.3)(react@19.2.4): + react-apexcharts@2.1.1(apexcharts@7.8.0)(react@19.3.0): dependencies: - apexcharts: 5.10.3 + apexcharts: 7.8.0 prop-types: 15.8.1 - react: 19.2.4 + react: 19.3.0 - react-dom@19.2.4(react@19.2.4): + react-dom@19.3.0(react@19.3.0): dependencies: - react: 19.2.4 - scheduler: 0.27.0 + react: 19.3.0 + scheduler: 0.28.0 react-is@16.13.1: {} - react@19.2.4: {} + react@19.3.0: {} - readdirp@5.0.0: {} + readdirp@5.1.1: {} reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -7669,66 +8281,64 @@ snapshots: require-main-filename@2.0.0: {} - resend@6.9.3(@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + resend@6.32.0(@react-email/render@2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)): dependencies: - postal-mime: 2.7.3 - svix: 1.84.1 + postal-mime: 2.7.6 + standardwebhooks: 1.1.1 optionalDependencies: - '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-email/render': 2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - resolve@2.0.0-next.6: + resolve@2.0.0-next.7: dependencies: es-errors: 1.3.0 - is-core-module: 2.16.1 - node-exports-info: 1.6.0 + is-core-module: 2.17.0 + node-exports-info: 1.6.2 object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + ret@0.5.0: {} + retry@0.12.0: {} reusify@1.1.0: {} - rollup@4.63.5: + robust-predicates@3.0.3: {} + + rollup@4.64.0: dependencies: '@types/estree': 1.0.9 optionalDependencies: '@napi-rs/lzma-linux-x64-gnu': 1.5.1 - '@rollup/rollup-android-arm-eabi': 4.63.5 - '@rollup/rollup-android-arm64': 4.63.5 - '@rollup/rollup-darwin-arm64': 4.63.5 - '@rollup/rollup-darwin-x64': 4.63.5 - '@rollup/rollup-freebsd-arm64': 4.63.5 - '@rollup/rollup-freebsd-x64': 4.63.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.63.5 - '@rollup/rollup-linux-arm-musleabihf': 4.63.5 - '@rollup/rollup-linux-arm64-gnu': 4.63.5 - '@rollup/rollup-linux-arm64-musl': 4.63.5 - '@rollup/rollup-linux-loong64-gnu': 4.63.5 - '@rollup/rollup-linux-loong64-musl': 4.63.5 - '@rollup/rollup-linux-ppc64-gnu': 4.63.5 - '@rollup/rollup-linux-ppc64-musl': 4.63.5 - '@rollup/rollup-linux-riscv64-gnu': 4.63.5 - '@rollup/rollup-linux-riscv64-musl': 4.63.5 - '@rollup/rollup-linux-s390x-gnu': 4.63.5 - '@rollup/rollup-linux-x64-gnu': 4.63.5 - '@rollup/rollup-linux-x64-musl': 4.63.5 - '@rollup/rollup-openbsd-x64': 4.63.5 - '@rollup/rollup-openharmony-arm64': 4.63.5 - '@rollup/rollup-win32-arm64-msvc': 4.63.5 - '@rollup/rollup-win32-ia32-msvc': 4.63.5 - '@rollup/rollup-win32-x64-gnu': 4.63.5 - '@rollup/rollup-win32-x64-msvc': 4.63.5 + '@rollup/rollup-android-arm-eabi': 4.64.0 + '@rollup/rollup-android-arm64': 4.64.0 + '@rollup/rollup-darwin-arm64': 4.64.0 + '@rollup/rollup-darwin-x64': 4.64.0 + '@rollup/rollup-freebsd-arm64': 4.64.0 + '@rollup/rollup-freebsd-x64': 4.64.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.64.0 + '@rollup/rollup-linux-arm-musleabihf': 4.64.0 + '@rollup/rollup-linux-arm64-gnu': 4.64.0 + '@rollup/rollup-linux-arm64-musl': 4.64.0 + '@rollup/rollup-linux-loong64-gnu': 4.64.0 + '@rollup/rollup-linux-loong64-musl': 4.64.0 + '@rollup/rollup-linux-ppc64-gnu': 4.64.0 + '@rollup/rollup-linux-ppc64-musl': 4.64.0 + '@rollup/rollup-linux-riscv64-gnu': 4.64.0 + '@rollup/rollup-linux-riscv64-musl': 4.64.0 + '@rollup/rollup-linux-s390x-gnu': 4.64.0 + '@rollup/rollup-linux-x64-gnu': 4.64.0 + '@rollup/rollup-linux-x64-musl': 4.64.0 + '@rollup/rollup-openbsd-x64': 4.64.0 + '@rollup/rollup-openharmony-arm64': 4.64.0 + '@rollup/rollup-win32-arm64-msvc': 4.64.0 + '@rollup/rollup-win32-ia32-msvc': 4.64.0 + '@rollup/rollup-win32-x64-gnu': 4.64.0 + '@rollup/rollup-win32-x64-msvc': 4.64.0 fsevents: 2.3.3 rou3@0.7.12: {} @@ -7737,9 +8347,9 @@ snapshots: dependencies: queue-microtask: 1.2.3 - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -7756,9 +8366,13 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + safer-buffer@2.1.2: {} - scheduler@0.27.0: {} + scheduler@0.28.0: {} selderee@0.11.0: dependencies: @@ -7766,15 +8380,15 @@ snapshots: semver@6.3.1: {} - semver@7.7.4: {} + semver@7.8.5: {} - sentry@0.44.1: {} + sentry@0.45.0: {} seq-queue@0.0.5: {} set-blocking@2.0.0: {} - set-cookie-parser@3.1.0: {} + set-cookie-parser@3.1.2: {} set-function-length@1.2.2: dependencies: @@ -7796,40 +8410,40 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 - - shallowequal@1.1.0: {} + es-object-atoms: 1.1.2 - sharp@0.34.5: + sharp@0.35.5(@types/node@25.9.9): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.4 + semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.35.5 + '@img/sharp-darwin-x64': 0.35.5 + '@img/sharp-freebsd-wasm32': 0.35.5 + '@img/sharp-libvips-darwin-arm64': 1.3.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 + '@img/sharp-libvips-linux-arm': 1.3.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 + '@img/sharp-libvips-linux-x64': 1.3.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + '@img/sharp-linux-arm': 0.35.5 + '@img/sharp-linux-arm64': 0.35.5 + '@img/sharp-linux-ppc64': 0.35.5 + '@img/sharp-linux-riscv64': 0.35.5 + '@img/sharp-linux-s390x': 0.35.5 + '@img/sharp-linux-x64': 0.35.5 + '@img/sharp-linuxmusl-arm64': 0.35.5 + '@img/sharp-linuxmusl-x64': 0.35.5 + '@img/sharp-webcontainers-wasm32': 0.35.5 + '@img/sharp-win32-arm64': 0.35.5 + '@img/sharp-win32-ia32': 0.35.5 + '@img/sharp-win32-x64': 0.35.5 + '@types/node': 25.9.9 optional: true shebang-command@2.0.0: @@ -7838,7 +8452,7 @@ snapshots: shebang-regex@3.0.0: {} - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -7858,19 +8472,21 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.1.0: + side-channel@1.1.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} - source-map-js@1.2.1: {} + source-map-js@1.2.2: {} split2@4.2.0: {} @@ -7878,11 +8494,13 @@ snapshots: stable-hash@0.0.5: {} + stackback@0.0.2: {} + stacktrace-parser@0.1.11: dependencies: type-fest: 0.7.1 - standardwebhooks@1.0.0: + standardwebhooks@1.1.1: dependencies: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 @@ -7902,53 +8520,54 @@ snapshots: string.prototype.includes@2.0.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 - string.prototype.matchall@4.0.12: + string.prototype.matchall@4.1.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 internal-slot: 1.1.0 regexp.prototype.flags: 1.5.4 set-function-name: 2.0.2 - side-channel: 1.1.0 + side-channel: 1.1.1 string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 - string.prototype.trim@1.2.10: + string.prototype.trim@1.2.11: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 - string.prototype.trimend@1.0.9: + string.prototype.trimend@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 strip-ansi@6.0.1: dependencies: @@ -7958,31 +8577,25 @@ snapshots: strip-json-comments@3.1.1: {} - strnum@2.4.0: + strip-literal@3.1.0: dependencies: - anynum: 1.0.0 + js-tokens: 9.0.1 - styled-components@6.3.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + styled-components@6.5.3(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@emotion/is-prop-valid': 1.4.0 - '@emotion/unitless': 0.10.0 - '@types/stylis': 4.2.7 - css-to-react-native: 3.2.0 csstype: 3.2.3 - postcss: 8.4.49 - react: 19.2.4 - shallowequal: 1.1.0 + react: 19.3.0 stylis: 4.3.6 - tslib: 2.8.1 optionalDependencies: - react-dom: 19.2.4(react@19.2.4) + react-dom: 19.3.0(react@19.3.0) - styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4): + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.3.0): dependencies: client-only: 0.0.1 - react: 19.2.4 + react: 19.3.0 optionalDependencies: - '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/core': 7.29.7 stylis@4.3.6: {} @@ -7996,19 +8609,24 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svix@1.84.1: - dependencies: - standardwebhooks: 1.0.0 - uuid: 10.0.0 + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} - tailwindcss@4.2.1: {} + tinybench@2.9.0: {} - tapable@2.3.0: {} + tinyexec@0.3.2: {} - tinyglobby@0.2.15: + tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.6: {} to-regex-range@5.0.1: dependencies: @@ -8016,7 +8634,7 @@ snapshots: tr46@0.0.3: {} - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -8029,9 +8647,9 @@ snapshots: tslib@2.8.1: {} - tsx@4.22.4: + tsx@4.23.15: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 @@ -8049,38 +8667,37 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 - typed-array-byte-offset@1.0.4: + typed-array-byte-offset@1.0.5: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 - has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.7: + typed-array-length@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8096,35 +8713,38 @@ snapshots: uncrypto@0.1.3: {} - undici-types@7.18.2: {} + undici-types@7.24.6: {} - unrs-resolver@1.11.1: + unrs-resolver@1.12.2: dependencies: napi-postinstall: 0.3.4 optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 - '@unrs/resolver-binding-android-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-x64': 1.11.1 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + + update-browserslist-db@1.3.3(browserslist@4.29.3): + dependencies: + browserslist: 4.29.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -8132,12 +8752,87 @@ snapshots: dependencies: punycode: 2.3.1 - uuid@10.0.0: {} - - valibot@1.2.0(typescript@5.9.3): + valibot@1.4.2(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 + vite-node@3.2.4(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.6(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.6(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + postcss: 8.5.28 + rollup: 4.64.0 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.9.9 + fsevents: 2.3.3 + jiti: 2.7.0 + lightningcss: 1.32.0 + tsx: 4.23.15 + + vitest@3.2.7(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.7 + '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.7 + '@vitest/snapshot': 3.2.7 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.6(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15) + vite-node: 3.2.4(@types/node@25.9.9)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.9 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + web-vitals@6.2.1: {} web-vitals@6.2.2: {} @@ -8160,7 +8855,7 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 - function.prototype.name: 1.1.8 + function.prototype.name: 1.2.0 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 @@ -8171,7 +8866,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.20 + which-typed-array: 1.1.24 which-collection@1.0.2: dependencies: @@ -8182,10 +8877,10 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.20: + which-typed-array@1.1.24: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -8196,6 +8891,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -8204,10 +8904,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - ws@8.19.0: {} - - xml-naming@0.1.0: {} - xtend@4.0.2: {} y18n@4.0.3: {} @@ -8237,11 +8933,11 @@ snapshots: zeptomatch@2.1.0: dependencies: - grammex: 3.1.12 + grammex: 3.1.13 graphmatch: 1.1.1 - zod-validation-error@4.0.2(zod@4.4.3): + zod-validation-error@4.0.2(zod@4.6.5): dependencies: - zod: 4.4.3 + zod: 4.6.5 - zod@4.4.3: {} + zod@4.6.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..71d9d8b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,10 @@ +packages: + - stackable-app + - packages/* + - services/* + +# pnpm 10 keeps build-script permissions here; Prisma needs them to fetch its engine. +onlyBuiltDependencies: + - prisma + - "@prisma/client" + - "@prisma/engines" diff --git a/services/academics/package.json b/services/academics/package.json new file mode 100644 index 0000000..6520dc7 --- /dev/null +++ b/services/academics/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stackable/academics", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/main.ts", + "start": "tsx src/main.ts", + "typecheck": "tsc --noEmit", + "test": "vitest run --root ../.." + }, + "dependencies": { + "@hono/node-server": "^1.19.0", + "@stackable/contracts": "workspace:*", + "@stackable/service-kit": "workspace:*", + "pg": "^8.16.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/pg": "^8.15.0", + "tsx": "^4.20.0", + "typescript": "^5.9.0" + } +} diff --git a/services/academics/src/lib/auth.ts b/services/academics/src/lib/auth.ts new file mode 100644 index 0000000..06e3a6f --- /dev/null +++ b/services/academics/src/lib/auth.ts @@ -0,0 +1,48 @@ +import { AppError, errors } from "@stackable/service-kit"; +import type { ServiceTokenClaims } from "@stackable/contracts"; + +export const TEACHER_ROLES = ["teacher", "admin"] as const; +export const STUDENT_ROLES = ["student", "pupil"] as const; +export const PARENT_ROLES = ["parent"] as const; +export const ADMIN_ROLES = ["admin"] as const; +export const SERVICE_ROLES = ["ai", "service"] as const; // the ai service sets the answer gate + +export interface Actor { + id: string; + schoolId: string; + role: string; + /** True only when the gateway verified the school security code for this request. */ + canRelease: boolean; +} + +/** Builds an application error with a DOMAIN_CONDITION code. */ +export function domainError(status: 400 | 403 | 404 | 409, code: string, message: string, details: Record = {}) { + return new AppError(status, code, message, details); +} + +// The contracts schema strips unknown claims, so `rel` is read from the payload the middleware already verified. +function releaseClaim(authorization: string | undefined): boolean { + const token = authorization?.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : ""; + try { + const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8")); + return payload.rel === true; + } catch { + return false; + } +} + +/** Reads the verified actor from a request that already passed requireServiceToken. */ +export function actorOf(c: { + var: { claims: ServiceTokenClaims }; + req: { header(name: string): string | undefined }; +}): Actor { + const { sub, schoolId, role } = c.var.claims; + if (!role) throw errors.forbidden("Token has no role"); + return { id: sub, schoolId, role, canRelease: releaseClaim(c.req.header("authorization")) }; +} + +export function requireRole(actor: Actor, roles: readonly string[]): void { + if (!roles.includes(actor.role)) throw errors.forbidden("Your role cannot do this"); +} + +export const isAdmin = (actor: Actor): boolean => actor.role === "admin"; diff --git a/services/academics/src/lib/validate.ts b/services/academics/src/lib/validate.ts new file mode 100644 index 0000000..71885f9 --- /dev/null +++ b/services/academics/src/lib/validate.ts @@ -0,0 +1,110 @@ +import { errors } from "@stackable/service-kit"; + +// academics cannot import zod (not in its dependencies), so this is a small schema kit with the same envelope. +interface Issue { + path: string; + message: string; +} + +export type Schema = (value: unknown, path: string, issues: Issue[]) => T; +export type Infer = S extends Schema ? T : never; + +const MAX_TEXT_LENGTH = 10_000; // bounds every free-text field so one request cannot store megabytes +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +const EXTERNAL_ID_PATTERN = /^[\w.:-]{1,128}$/; // ids owned by other services may not be uuids +const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T/; + +function bad(issues: Issue[], path: string, message: string): T { + issues.push({ path, message }); + return undefined as T; +} + +export const string = + ({ min = 1, max = MAX_TEXT_LENGTH }: { min?: number; max?: number } = {}): Schema => + (v, p, i) => { + if (typeof v !== "string") return bad(i, p, "must be a string"); + const s = v.trim(); + if (s.length < min || s.length > max) return bad(i, p, `length must be ${min} to ${max}`); + return s; + }; + +export const uuid = (): Schema => (v, p, i) => + typeof v === "string" && UUID_PATTERN.test(v) ? v : bad(i, p, "must be a uuid"); + +export const externalId = (): Schema => (v, p, i) => + typeof v === "string" && EXTERNAL_ID_PATTERN.test(v) ? v : bad(i, p, "must be a valid id"); + +export const num = + ({ min = -1e9, max = 1e9 }: { min?: number; max?: number } = {}): Schema => + (v, p, i) => { + if (typeof v !== "number" || !Number.isFinite(v)) return bad(i, p, "must be a number"); + return v < min || v > max ? bad(i, p, `must be between ${min} and ${max}`) : v; + }; + +/** coerce accepts numeric strings so the same schema serves query parameters. */ +export const int = + ({ min = 0, max = 1_000_000, coerce = false }: { min?: number; max?: number; coerce?: boolean } = {}): Schema => + (v, p, i) => { + const n = coerce && typeof v === "string" && v.trim() !== "" ? Number(v) : v; + if (typeof n !== "number" || !Number.isInteger(n)) return bad(i, p, "must be an integer"); + return n < min || n > max ? bad(i, p, `must be between ${min} and ${max}`) : n; + }; + +export const bool = (): Schema => (v, p, i) => + typeof v === "boolean" ? v : bad(i, p, "must be true or false"); + +/** Accepts an ISO 8601 timestamp and returns it normalised to UTC. */ +export const isoDate = (): Schema => (v, p, i) => { + if (typeof v !== "string" || !ISO_PATTERN.test(v) || Number.isNaN(Date.parse(v))) { + return bad(i, p, "must be an ISO 8601 timestamp"); + } + return new Date(v).toISOString(); +}; + +export const oneOf = + (values: V): Schema => + (v, p, i) => + typeof v === "string" && values.includes(v) ? (v as V[number]) : bad(i, p, `must be one of ${values.join(", ")}`); + +export const optional = + (schema: Schema): Schema => + (v, p, i) => + v === undefined || v === null ? undefined : schema(v, p, i); + +export const array = + (item: Schema, { min = 0, max = 500 }: { min?: number; max?: number } = {}): Schema => + (v, p, i) => { + if (!Array.isArray(v)) return bad(i, p, "must be an array"); + if (v.length < min || v.length > max) return bad(i, p, `must have ${min} to ${max} items`); + return v.map((x, idx) => item(x, `${p}[${idx}]`, i)); + }; + +export const any = (): Schema => (v) => v; + +export function object>>(shape: S): Schema<{ [K in keyof S]: Infer }> { + return (v, p, i) => { + if (typeof v !== "object" || v === null || Array.isArray(v)) return bad(i, p, "must be an object"); + const out: Record = {}; + for (const key of Object.keys(shape)) { + out[key] = shape[key]((v as Record)[key], p ? `${p}.${key}` : key, i); + } + return out as { [K in keyof S]: Infer }; + }; +} + +/** Runs a schema over untrusted input and throws the standard 400 envelope listing every failing field. */ +export function parse(schema: Schema, input: unknown): T { + const issues: Issue[] = []; + const value = schema(input, "", issues); + if (issues.length > 0) throw errors.validation("Request validation failed", { fields: issues }); + return value; +} + +/** Reads a JSON body; a malformed body is a 400, never a 500. */ +export async function readJson(c: { req: { json(): Promise } }): Promise { + try { + return await c.req.json(); + } catch { + throw errors.validation("Malformed JSON body"); + } +} diff --git a/services/academics/src/placeholder.ts b/services/academics/src/placeholder.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/services/academics/src/placeholder.ts @@ -0,0 +1 @@ +export {}; diff --git a/services/academics/tsconfig.json b/services/academics/tsconfig.json new file mode 100644 index 0000000..cc2c43a --- /dev/null +++ b/services/academics/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src"] +} diff --git a/services/ai/package.json b/services/ai/package.json new file mode 100644 index 0000000..90c73b3 --- /dev/null +++ b/services/ai/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stackable/ai", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/main.ts", + "start": "tsx src/main.ts", + "typecheck": "tsc --noEmit", + "test": "vitest run --root ../.." + }, + "dependencies": { + "@hono/node-server": "^1.19.0", + "@stackable/contracts": "workspace:*", + "@stackable/service-kit": "workspace:*", + "pg": "^8.16.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/pg": "^8.15.0", + "tsx": "^4.20.0", + "typescript": "^5.9.0" + } +} diff --git a/services/ai/src/placeholder.ts b/services/ai/src/placeholder.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/services/ai/src/placeholder.ts @@ -0,0 +1 @@ +export {}; diff --git a/services/ai/tsconfig.json b/services/ai/tsconfig.json new file mode 100644 index 0000000..cc2c43a --- /dev/null +++ b/services/ai/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src"] +} diff --git a/services/communication/go.mod b/services/communication/go.mod new file mode 100644 index 0000000..e87b05b --- /dev/null +++ b/services/communication/go.mod @@ -0,0 +1,11 @@ +module github.com/stackable/communication + +go 1.25.0 + +require ( + github.com/go-chi/chi/v5 v5.3.2 + github.com/jackc/pgx/v5 v5.11.0 + github.com/stackable/go-kit v0.0.0 +) + +replace github.com/stackable/go-kit => ../../packages/go-kit diff --git a/services/communication/go.sum b/services/communication/go.sum new file mode 100644 index 0000000..a785dba --- /dev/null +++ b/services/communication/go.sum @@ -0,0 +1,30 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY= +github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/services/communication/internal/notifications/callbacks.go b/services/communication/internal/notifications/callbacks.go new file mode 100644 index 0000000..a2ef52f --- /dev/null +++ b/services/communication/internal/notifications/callbacks.go @@ -0,0 +1,112 @@ +package notifications + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + kit "github.com/stackable/go-kit" +) + +// SignatureVerifier checks that a provider callback really came from the provider. +type SignatureVerifier interface { + Verify(provider string, headers map[string]string, body []byte) error +} + +// FakeVerifier accepts every callback unless Reject is set; it is for dev and tests only. +type FakeVerifier struct{ Reject bool } + +// Verify implements SignatureVerifier. +func (f FakeVerifier) Verify(string, map[string]string, []byte) error { + if f.Reject { + return errors.New("signature rejected") + } + return nil +} + +// parsers turn each provider's callback body into a CallbackEvent; an empty status means "not a final report". +// Headers are lower-cased by the handler. +var parsers = map[string]func(headers map[string]string, body []byte) (CallbackEvent, error){ + "fake": parseFake, + "resend": parseResend, + "africastalking": parseAfricasTalking, +} + +func parseFake(_ map[string]string, body []byte) (CallbackEvent, error) { + var p struct { + ID string `json:"id"` + ProviderRef string `json:"providerRef"` + Status string `json:"status"` + } + if err := json.Unmarshal(body, &p); err != nil { + return CallbackEvent{}, err + } + return CallbackEvent{EventID: p.ID, ProviderRef: p.ProviderRef, Status: p.Status}, nil +} + +// parseResend maps Resend webhook types; the event id is the svix-id header Resend sends. +func parseResend(headers map[string]string, body []byte) (CallbackEvent, error) { + var p struct { + Type string `json:"type"` + Data struct { + EmailID string `json:"email_id"` + } `json:"data"` + } + if err := json.Unmarshal(body, &p); err != nil { + return CallbackEvent{}, err + } + status := "" + switch p.Type { + case "email.delivered": + status = StatusDelivered + case "email.bounced", "email.failed", "email.complained": + status = StatusFailed + } + return CallbackEvent{EventID: headers["svix-id"], ProviderRef: p.Data.EmailID, Status: status}, nil +} + +// parseAfricasTalking maps the delivery report fields of Africa's Talking. +func parseAfricasTalking(_ map[string]string, body []byte) (CallbackEvent, error) { + var p struct { + ID string `json:"id"` + Status string `json:"status"` + } + if err := json.Unmarshal(body, &p); err != nil { + return CallbackEvent{}, err + } + status := "" + switch strings.ToLower(p.Status) { + case "success": + status = StatusDelivered + case "failed", "rejected", "absentsubscriber": + status = StatusFailed + } + return CallbackEvent{EventID: p.ID, ProviderRef: p.ID, Status: status}, nil +} + +// RecordCallback stores one provider delivery report. +// It exists so providers can retry the same callback safely; repeats change nothing. +func (s *Service) RecordCallback(ctx context.Context, provider string, headers map[string]string, body []byte) (CallbackOutcome, error) { + parse, ok := parsers[provider] + if !ok { + return CallbackOutcome{}, kit.NotFound("Unknown provider.") + } + ev, err := parse(headers, body) + if err != nil { + return CallbackOutcome{}, kit.Validation("Callback body is not valid for this provider.", nil) + } + ev.Provider, ev.Payload = provider, body + if ev.EventID == "" || ev.ProviderRef == "" { + return CallbackOutcome{}, kit.Validation("Callback has no event id or provider reference.", nil) + } + if ev.Status != StatusDelivered && ev.Status != StatusFailed { + return CallbackOutcome{}, nil // progress reports such as "sent" carry nothing to store + } + out, err := s.store.RecordCallback(ctx, ev) + if err != nil { + return CallbackOutcome{}, fmt.Errorf("record callback: %w", err) + } + return out, nil +} diff --git a/services/communication/internal/notifications/handler.go b/services/communication/internal/notifications/handler.go new file mode 100644 index 0000000..b6b9e53 --- /dev/null +++ b/services/communication/internal/notifications/handler.go @@ -0,0 +1,166 @@ +package notifications + +import ( + "io" + "net/http" + "strconv" + "strings" + + "github.com/go-chi/chi/v5" + kit "github.com/stackable/go-kit" +) + +const maxCallbackBytes = 1 << 20 // 1 MiB, same cap as every other body + +// Handler exposes the notification routes. +type Handler struct { + svc *Service + verifier SignatureVerifier +} + +// NewHandler builds a Handler; the verifier guards the public callback route. +func NewHandler(svc *Service, verifier SignatureVerifier) *Handler { + return &Handler{svc: svc, verifier: verifier} +} + +// Routes mounts the /v1 routes; everything except callbacks needs a service token. +func (h *Handler) Routes(r chi.Router, tokens *kit.ServiceTokenVerifier) { + r.Post("/v1/callbacks/{provider}", kit.Handle(h.callback)) + r.Group(func(g chi.Router) { + g.Use(kit.RequireServiceToken(tokens)) + g.Post("/v1/notifications", kit.Handle(h.create)) + g.Get("/v1/notifications/{id}/deliveries", kit.Handle(h.deliveries)) + g.Get("/v1/inbox", kit.Handle(h.inbox)) + g.Post("/v1/inbox/{id}/read", kit.Handle(h.markRead)) + g.Get("/v1/preferences", kit.Handle(h.getPreferences)) + g.Put("/v1/preferences", kit.Handle(h.putPreferences)) + }) +} + +// actor returns the school and user the service token was issued for. +func actor(r *http.Request) (schoolID, userID string, err error) { + claims, ok := kit.ClaimsFrom(r.Context()) + if !ok { + return "", "", kit.Unauthorized("Missing service token.") + } + return claims.SchoolID, claims.Subject, nil +} + +func (h *Handler) create(w http.ResponseWriter, r *http.Request) error { + schoolID, _, err := actor(r) + if err != nil { + return err + } + var in CreateInput + if err := kit.DecodeJSON(w, r, &in); err != nil { + return err + } + n, created, err := h.svc.Create(r.Context(), schoolID, in) + if err != nil { + return err + } + status := http.StatusOK + if created { + status = http.StatusCreated + } + kit.WriteJSON(w, status, n) + return nil +} + +func (h *Handler) inbox(w http.ResponseWriter, r *http.Request) error { + schoolID, userID, err := actor(r) + if err != nil { + return err + } + limit := 0 + if raw := r.URL.Query().Get("limit"); raw != "" { + if limit, err = strconv.Atoi(raw); err != nil || limit < 1 { + return kit.Validation("limit must be between 1 and 100.", map[string]string{"limit": "1-100"}) + } + } + page, err := h.svc.Inbox(r.Context(), schoolID, userID, limit, r.URL.Query().Get("cursor")) + if err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, page) + return nil +} + +func (h *Handler) markRead(w http.ResponseWriter, r *http.Request) error { + schoolID, userID, err := actor(r) + if err != nil { + return err + } + if err := h.svc.MarkRead(r.Context(), schoolID, userID, chi.URLParam(r, "id")); err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, map[string]bool{"read": true}) + return nil +} + +func (h *Handler) getPreferences(w http.ResponseWriter, r *http.Request) error { + schoolID, userID, err := actor(r) + if err != nil { + return err + } + prefs, err := h.svc.Preferences(r.Context(), schoolID, userID) + if err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, map[string]any{"preferences": prefs}) + return nil +} + +func (h *Handler) putPreferences(w http.ResponseWriter, r *http.Request) error { + schoolID, userID, err := actor(r) + if err != nil { + return err + } + var in struct { + Preferences []Preference `json:"preferences"` + } + if err := kit.DecodeJSON(w, r, &in); err != nil { + return err + } + prefs, err := h.svc.SavePreferences(r.Context(), schoolID, userID, in.Preferences) + if err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, map[string]any{"preferences": prefs}) + return nil +} + +func (h *Handler) deliveries(w http.ResponseWriter, r *http.Request) error { + schoolID, _, err := actor(r) + if err != nil { + return err + } + id := chi.URLParam(r, "id") + counts, err := h.svc.DeliveryCounts(r.Context(), schoolID, id) + if err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, map[string]any{"notificationId": id, "counts": counts}) + return nil +} + +func (h *Handler) callback(w http.ResponseWriter, r *http.Request) error { + body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxCallbackBytes)) + if err != nil { + return kit.Validation("Request body could not be read.", nil) + } + provider := chi.URLParam(r, "provider") + headers := map[string]string{} + for name := range r.Header { + headers[strings.ToLower(name)] = r.Header.Get(name) + } + if err := h.verifier.Verify(provider, headers, body); err != nil { + return kit.Unauthorized("Invalid callback signature.") + } + out, err := h.svc.RecordCallback(r.Context(), provider, headers, body) + if err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, map[string]any{"received": true, "matched": out.Matched, "duplicate": out.Duplicate}) + return nil +} diff --git a/services/communication/internal/notifications/memory_store.go b/services/communication/internal/notifications/memory_store.go new file mode 100644 index 0000000..36ddde8 --- /dev/null +++ b/services/communication/internal/notifications/memory_store.go @@ -0,0 +1,322 @@ +package notifications + +import ( + "context" + "encoding/json" + "sort" + "strings" + "sync" + "time" +) + +type recipientRow struct { + Recipient + schoolID string + readAt *time.Time +} + +// DeliveryRow is one delivery as the memory store holds it. +type DeliveryRow struct { + ID string + SchoolID string + NotificationID string + UserID string + Channel string + Status string + Attempt int + ProviderRef string + Error string + claimedAt time.Time +} + +// OutboxRow is one event waiting to be published. +type OutboxRow struct { + SchoolID string + Topic string + Payload []byte +} + +type notificationRow struct { + Notification + schoolID string +} + +// MemoryStore is an in-process Store for tests and local runs without a database. +type MemoryStore struct { + mu sync.Mutex + now func() time.Time + notifs map[string]*notificationRow + byKey map[string]string + recipients map[string][]*recipientRow + deliveries []*DeliveryRow + prefs map[string]Preference + callbacks map[string]bool + outbox []OutboxRow +} + +// NewMemoryStore returns an empty MemoryStore using the real clock. +func NewMemoryStore() *MemoryStore { return NewMemoryStoreWithClock(time.Now) } + +// NewMemoryStoreWithClock lets tests control created_at and claim leases. +func NewMemoryStoreWithClock(now func() time.Time) *MemoryStore { + return &MemoryStore{ + now: now, + notifs: map[string]*notificationRow{}, + byKey: map[string]string{}, + recipients: map[string][]*recipientRow{}, + prefs: map[string]Preference{}, + callbacks: map[string]bool{}, + } +} + +// key joins parts with a NUL so ("a","bc") and ("ab","c") never collide. +func key(parts ...string) string { + return strings.Join(parts, "\x00") +} + +// CreateNotification stores the notification once per (school, idempotency key). +func (m *MemoryStore) CreateNotification(_ context.Context, n NewNotification) (Notification, bool, error) { + m.mu.Lock() + defer m.mu.Unlock() + if id, ok := m.byKey[key(n.SchoolID, n.IdempotencyKey)]; ok { + return m.notifs[id].Notification, false, nil + } + n.CreatedAt = m.now() + m.notifs[n.ID] = ¬ificationRow{Notification: n.Notification, schoolID: n.SchoolID} + m.byKey[key(n.SchoolID, n.IdempotencyKey)] = n.ID + for _, r := range n.Recipients { + m.recipients[n.ID] = append(m.recipients[n.ID], &recipientRow{Recipient: r, schoolID: n.SchoolID}) + m.deliveries = append(m.deliveries, &DeliveryRow{ + ID: NewID(), SchoolID: n.SchoolID, NotificationID: n.ID, UserID: r.UserID, + Channel: ChannelPush, Status: StatusQueued, + }) + } + return n.Notification, true, nil +} + +// ListInbox returns the user's notifications newest first, keyset-paginated. +func (m *MemoryStore) ListInbox(_ context.Context, schoolID, userID string, limit int, after *Cursor) ([]InboxItem, error) { + m.mu.Lock() + defer m.mu.Unlock() + var items []InboxItem + for id, rows := range m.recipients { + n := m.notifs[id] + if n.schoolID != schoolID { + continue + } + for _, r := range rows { + if r.schoolID != schoolID || r.UserID != userID { + continue + } + items = append(items, InboxItem{ID: id, Type: n.Type, Title: n.Title, Body: n.Body, Urgent: n.Urgent, + ChildIDs: append([]string{}, r.ChildIDs...), CreatedAt: n.CreatedAt, ReadAt: r.readAt}) + } + } + sort.Slice(items, func(i, j int) bool { + if !items[i].CreatedAt.Equal(items[j].CreatedAt) { + return items[i].CreatedAt.After(items[j].CreatedAt) + } + return items[i].ID > items[j].ID + }) + out := []InboxItem{} + for _, it := range items { + if after != nil && !isBefore(it, *after) { + continue + } + if len(out) == limit { + break + } + out = append(out, it) + } + return out, nil +} + +func isBefore(it InboxItem, c Cursor) bool { + return it.CreatedAt.Before(c.CreatedAt) || (it.CreatedAt.Equal(c.CreatedAt) && it.ID < c.ID) +} + +// MarkRead sets read_at once and keeps the first read time on repeats. +func (m *MemoryStore) MarkRead(_ context.Context, schoolID, userID, notificationID string) error { + m.mu.Lock() + defer m.mu.Unlock() + for _, r := range m.recipients[notificationID] { + if r.schoolID == schoolID && r.UserID == userID { + if r.readAt == nil { + t := m.now() + r.readAt = &t + } + return nil + } + } + return ErrNotFound +} + +// ListPreferences returns the user's preference rows sorted by type. +func (m *MemoryStore) ListPreferences(_ context.Context, schoolID, userID string) ([]Preference, error) { + m.mu.Lock() + defer m.mu.Unlock() + prefix := key(schoolID, userID) + "\x00" + out := []Preference{} + for k, p := range m.prefs { + if strings.HasPrefix(k, prefix) { + out = append(out, p) + } + } + sort.Slice(out, func(i, j int) bool { return out[i].Type < out[j].Type }) + return out, nil +} + +// FindPreference prefers the exact type and falls back to the AnyType row. +func (m *MemoryStore) FindPreference(_ context.Context, schoolID, userID, notifType string) (*Preference, error) { + m.mu.Lock() + defer m.mu.Unlock() + for _, t := range []string{notifType, AnyType} { + if p, ok := m.prefs[key(schoolID, userID, t)]; ok { + return &p, nil + } + } + return nil, nil +} + +// SavePreferences upserts each given type. +func (m *MemoryStore) SavePreferences(_ context.Context, schoolID, userID string, prefs []Preference) error { + m.mu.Lock() + defer m.mu.Unlock() + for _, p := range prefs { + m.prefs[key(schoolID, userID, p.Type)] = p + } + return nil +} + +// DeliveryCounts counts deliveries per status for one notification. +func (m *MemoryStore) DeliveryCounts(_ context.Context, schoolID, notificationID string) (map[string]int, error) { + m.mu.Lock() + defer m.mu.Unlock() + if n, ok := m.notifs[notificationID]; !ok || n.schoolID != schoolID { + return nil, ErrNotFound + } + counts := map[string]int{} + for _, s := range AllStatuses { + counts[s] = 0 + } + for _, d := range m.deliveries { + if d.NotificationID == notificationID && d.SchoolID == schoolID { + counts[d.Status]++ + } + } + return counts, nil +} + +// ClaimQueued leases QUEUED deliveries whose claim is missing or older than lease. +func (m *MemoryStore) ClaimQueued(_ context.Context, limit int, lease time.Duration) ([]Job, error) { + m.mu.Lock() + defer m.mu.Unlock() + now := m.now() + var jobs []Job + for _, d := range m.deliveries { + if len(jobs) == limit { + break + } + if d.Status != StatusQueued || (!d.claimedAt.IsZero() && now.Sub(d.claimedAt) < lease) { + continue + } + d.claimedAt = now + n := m.notifs[d.NotificationID] + job := Job{DeliveryID: d.ID, SchoolID: d.SchoolID, NotificationID: d.NotificationID, UserID: d.UserID, + Attempt: d.Attempt, Type: n.Type, Title: n.Title, Body: n.Body, Urgent: n.Urgent} + for _, r := range m.recipients[d.NotificationID] { + if r.UserID == d.UserID { + job.ChildIDs, job.ChannelsAllowed, job.Email, job.Phone = r.ChildIDs, r.ChannelsAllowed, r.Email, r.Phone + } + } + jobs = append(jobs, job) + } + return jobs, nil +} + +func (m *MemoryStore) findDelivery(schoolID, id string) *DeliveryRow { + for _, d := range m.deliveries { + if d.ID == id && d.SchoolID == schoolID { + return d + } + } + return nil +} + +// ReleaseDelivery clears the claim so the next tick can pick the delivery up again. +func (m *MemoryStore) ReleaseDelivery(_ context.Context, schoolID, deliveryID string) error { + m.mu.Lock() + defer m.mu.Unlock() + d := m.findDelivery(schoolID, deliveryID) + if d == nil { + return ErrNotFound + } + d.claimedAt = time.Time{} + return nil +} + +// FinishDelivery records the result and the outbox row for UNDELIVERED. +func (m *MemoryStore) FinishDelivery(_ context.Context, schoolID, deliveryID string, res Result) error { + m.mu.Lock() + defer m.mu.Unlock() + d := m.findDelivery(schoolID, deliveryID) + if d == nil { + return ErrNotFound + } + d.Status, d.Attempt, d.ProviderRef, d.Error, d.claimedAt = res.Status, res.Attempt, res.ProviderRef, res.Error, time.Time{} + if res.Channel != "" { + d.Channel = res.Channel + } + if res.Status == StatusUndelivered { + payload, err := json.Marshal(map[string]string{"notificationId": d.NotificationID, "userId": d.UserID, "deliveryId": d.ID}) + if err != nil { + return err + } + m.outbox = append(m.outbox, OutboxRow{SchoolID: schoolID, Topic: OutboxTopicUndelivered, Payload: payload}) + } + return nil +} + +// RecordCallback is idempotent per (provider, event id) and ignores reports for unknown provider refs. +func (m *MemoryStore) RecordCallback(_ context.Context, ev CallbackEvent) (CallbackOutcome, error) { + m.mu.Lock() + defer m.mu.Unlock() + var target *DeliveryRow + for _, d := range m.deliveries { + if d.ProviderRef != "" && d.ProviderRef == ev.ProviderRef { + target = d + } + } + if target == nil { + return CallbackOutcome{}, nil + } + k := key(ev.Provider, ev.EventID) + if m.callbacks[k] { + return CallbackOutcome{Matched: true, Duplicate: true}, nil + } + m.callbacks[k] = true + if canApplyCallback(target.Status) { + target.Status = ev.Status + } + return CallbackOutcome{Matched: true}, nil +} + +// Deliveries returns copies of a notification's deliveries for tests. +func (m *MemoryStore) Deliveries(schoolID, notificationID string) []DeliveryRow { + m.mu.Lock() + defer m.mu.Unlock() + var out []DeliveryRow + for _, d := range m.deliveries { + if d.SchoolID == schoolID && d.NotificationID == notificationID { + out = append(out, *d) + } + } + return out +} + +// Outbox returns copies of the outbox rows for tests. +func (m *MemoryStore) Outbox() []OutboxRow { + m.mu.Lock() + defer m.mu.Unlock() + return append([]OutboxRow{}, m.outbox...) +} diff --git a/services/communication/internal/notifications/pg_store.go b/services/communication/internal/notifications/pg_store.go new file mode 100644 index 0000000..d41c14e --- /dev/null +++ b/services/communication/internal/notifications/pg_store.go @@ -0,0 +1,372 @@ +package notifications + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + kit "github.com/stackable/go-kit" +) + +// Not run against a real database yet. Ids are cast to and from text so pgx never guesses a uuid codec. +const ( + insertNotificationSQL = ` +insert into communication.notifications (id, school_id, type, title, body, urgent, idempotency_key) +values ($1::uuid, $2::uuid, $3, $4, $5, $6, $7) +on conflict (school_id, idempotency_key) do nothing +returning created_at` + + selectByKeySQL = ` +select id::text, type, title, body, urgent, sender_identity_id::text, idempotency_key, created_at +from communication.notifications where school_id = $1::uuid and idempotency_key = $2` + + insertRecipientSQL = ` +insert into communication.notification_recipients (school_id, notification_id, user_id, child_ids, channels_allowed, email, phone) +values ($1::uuid, $2::uuid, $3::uuid, $4::text[]::uuid[], $5::text[], nullif($6, ''), nullif($7, ''))` + + insertDeliverySQL = ` +insert into communication.notification_deliveries (school_id, notification_id, user_id, channel, status) +values ($1::uuid, $2::uuid, $3::uuid, 'push', 'QUEUED')` + + listInboxSQL = ` +select n.id::text, n.type, n.title, n.body, n.urgent, n.created_at, r.child_ids::text[], r.read_at +from communication.notification_recipients r +join communication.notifications n on n.id = r.notification_id +where r.school_id = $1::uuid and r.user_id = $2::uuid + and ($3::timestamptz is null or (n.created_at, n.id::text) < ($3::timestamptz, $4::text)) +order by n.created_at desc, n.id desc +limit $5` + + markReadSQL = ` +update communication.notification_recipients +set read_at = coalesce(read_at, now()), updated_at = now() +where school_id = $1::uuid and notification_id = $2::uuid and user_id = $3::uuid` + + listPrefsSQL = ` +select type, channels, quiet_start::text, quiet_end::text +from communication.notification_preferences +where school_id = $1::uuid and user_id = $2::uuid order by type limit 200` + + findPrefSQL = ` +select type, channels, quiet_start::text, quiet_end::text +from communication.notification_preferences +where school_id = $1::uuid and user_id = $2::uuid and type in ($3, '*') +order by (type = '*') limit 1` + + upsertPrefSQL = ` +insert into communication.notification_preferences (school_id, user_id, type, channels, quiet_start, quiet_end) +values ($1::uuid, $2::uuid, $3, $4::text[], $5::time, $6::time) +on conflict (school_id, user_id, type) +do update set channels = excluded.channels, quiet_start = excluded.quiet_start, quiet_end = excluded.quiet_end, updated_at = now()` + + notificationExistsSQL = `select 1 from communication.notifications where id = $1::uuid and school_id = $2::uuid` + + countsSQL = ` +select status, count(*) from communication.notification_deliveries +where notification_id = $1::uuid and school_id = $2::uuid group by status` + + claimSQL = ` +with claimed as ( + update communication.notification_deliveries d + set claimed_at = now(), updated_at = now() + where d.id in ( + select id from communication.notification_deliveries + where status = 'QUEUED' and (claimed_at is null or claimed_at < now() - make_interval(secs => $2)) + order by created_at limit $1 for update skip locked) + returning d.id, d.school_id, d.notification_id, d.user_id, d.attempt) +select c.id::text, c.school_id::text, c.notification_id::text, c.user_id::text, c.attempt, + n.type, n.title, n.body, n.urgent, r.child_ids::text[], r.channels_allowed, coalesce(r.email, ''), coalesce(r.phone, '') +from claimed c +join communication.notifications n on n.id = c.notification_id +join communication.notification_recipients r on r.notification_id = c.notification_id and r.user_id = c.user_id` + + releaseSQL = ` +update communication.notification_deliveries set claimed_at = null, updated_at = now() +where id = $1::uuid and school_id = $2::uuid` + + finishSQL = ` +update communication.notification_deliveries +set status = $3, channel = coalesce(nullif($4, ''), channel), attempt = $5, + provider_ref = nullif($6, ''), error = nullif($7, ''), claimed_at = null, updated_at = now() +where id = $1::uuid and school_id = $2::uuid +returning notification_id::text, user_id::text` + + outboxSQL = ` +insert into communication.outbox (school_id, topic, payload) values ($1::uuid, $2, $3::jsonb)` + + findByRefSQL = ` +select id::text, school_id::text from communication.notification_deliveries where provider_ref = $1 limit 1` + + insertCallbackSQL = ` +insert into communication.delivery_callbacks (school_id, delivery_id, provider, provider_event_id, status, payload) +values ($1::uuid, $2::uuid, $3, $4, $5, $6::jsonb) +on conflict (provider, provider_event_id) do nothing` + + applyCallbackSQL = ` +update communication.notification_deliveries set status = $2, updated_at = now() +where id = $1::uuid and status in ('SENT', 'FAILED')` +) + +// PgStore is the Postgres-backed Store. +type PgStore struct { + pool *pgxpool.Pool +} + +// NewPgStore wraps a pool. +func NewPgStore(pool *pgxpool.Pool) *PgStore { return &PgStore{pool: pool} } + +// CreateNotification inserts the notification, recipients and deliveries in one school-scoped transaction. +func (s *PgStore) CreateNotification(ctx context.Context, n NewNotification) (Notification, bool, error) { + out := n.Notification + created := false + err := kit.WithSchool(ctx, s.pool, n.SchoolID, func(ctx context.Context, tx pgx.Tx) error { + err := tx.QueryRow(ctx, insertNotificationSQL, n.ID, n.SchoolID, n.Type, n.Title, n.Body, n.Urgent, n.IdempotencyKey).Scan(&out.CreatedAt) + if errors.Is(err, pgx.ErrNoRows) { + return tx.QueryRow(ctx, selectByKeySQL, n.SchoolID, n.IdempotencyKey).Scan( + &out.ID, &out.Type, &out.Title, &out.Body, &out.Urgent, &out.SenderIdentityID, &out.IdempotencyKey, &out.CreatedAt) + } + if err != nil { + return fmt.Errorf("insert notification: %w", err) + } + created = true + batch := &pgx.Batch{} + for _, r := range n.Recipients { + batch.Queue(insertRecipientSQL, n.SchoolID, n.ID, r.UserID, r.ChildIDs, r.ChannelsAllowed, r.Email, r.Phone) + batch.Queue(insertDeliverySQL, n.SchoolID, n.ID, r.UserID) + } + br := tx.SendBatch(ctx, batch) + for range n.Recipients { + for range 2 { + if _, err := br.Exec(); err != nil { + _ = br.Close() + return fmt.Errorf("insert recipient: %w", err) + } + } + } + return br.Close() + }) + return out, created, err +} + +// ListInbox pages with a (created_at, id) keyset so deep pages stay fast. +func (s *PgStore) ListInbox(ctx context.Context, schoolID, userID string, limit int, after *Cursor) ([]InboxItem, error) { + var afterTime *time.Time + afterID := "" + if after != nil { + afterTime, afterID = &after.CreatedAt, after.ID + } + items := []InboxItem{} + err := kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + rows, err := tx.Query(ctx, listInboxSQL, schoolID, userID, afterTime, afterID, limit) + if err != nil { + return fmt.Errorf("query inbox: %w", err) + } + defer rows.Close() + for rows.Next() { + var it InboxItem + if err := rows.Scan(&it.ID, &it.Type, &it.Title, &it.Body, &it.Urgent, &it.CreatedAt, &it.ChildIDs, &it.ReadAt); err != nil { + return fmt.Errorf("scan inbox: %w", err) + } + items = append(items, it) + } + return rows.Err() + }) + return items, err +} + +// MarkRead keeps the first read time on repeats. +func (s *PgStore) MarkRead(ctx context.Context, schoolID, userID, notificationID string) error { + return kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + tag, err := tx.Exec(ctx, markReadSQL, schoolID, notificationID, userID) + if err != nil { + return fmt.Errorf("mark read: %w", err) + } + if tag.RowsAffected() == 0 { + return ErrNotFound + } + return nil + }) +} + +func scanPreference(row pgx.Row) (*Preference, error) { + var p Preference + if err := row.Scan(&p.Type, &p.Channels, &p.QuietStart, &p.QuietEnd); err != nil { + return nil, err + } + trimClock(p.QuietStart) + trimClock(p.QuietEnd) + return &p, nil +} + +// trimClock turns Postgres "22:00:00" into "22:00". +func trimClock(t *string) { + if t != nil && len(*t) > 5 { + *t = (*t)[:5] + } +} + +// ListPreferences returns every preference row of the user. +func (s *PgStore) ListPreferences(ctx context.Context, schoolID, userID string) ([]Preference, error) { + out := []Preference{} + err := kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + rows, err := tx.Query(ctx, listPrefsSQL, schoolID, userID) + if err != nil { + return fmt.Errorf("query preferences: %w", err) + } + defer rows.Close() + for rows.Next() { + p, err := scanPreference(rows) + if err != nil { + return fmt.Errorf("scan preference: %w", err) + } + out = append(out, *p) + } + return rows.Err() + }) + return out, err +} + +// FindPreference returns the exact-type row, else the AnyType row, else nil. +func (s *PgStore) FindPreference(ctx context.Context, schoolID, userID, notifType string) (*Preference, error) { + var found *Preference + err := kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + p, err := scanPreference(tx.QueryRow(ctx, findPrefSQL, schoolID, userID, notifType)) + if errors.Is(err, pgx.ErrNoRows) { + return nil + } + found = p + return err + }) + return found, err +} + +// SavePreferences upserts each type in one transaction. +func (s *PgStore) SavePreferences(ctx context.Context, schoolID, userID string, prefs []Preference) error { + return kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + for _, p := range prefs { + if _, err := tx.Exec(ctx, upsertPrefSQL, schoolID, userID, p.Type, p.Channels, p.QuietStart, p.QuietEnd); err != nil { + return fmt.Errorf("upsert preference: %w", err) + } + } + return nil + }) +} + +// DeliveryCounts returns a count for every status, zero when absent. +func (s *PgStore) DeliveryCounts(ctx context.Context, schoolID, notificationID string) (map[string]int, error) { + counts := map[string]int{} + for _, st := range AllStatuses { + counts[st] = 0 + } + err := kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + var one int + if err := tx.QueryRow(ctx, notificationExistsSQL, notificationID, schoolID).Scan(&one); errors.Is(err, pgx.ErrNoRows) { + return ErrNotFound + } else if err != nil { + return fmt.Errorf("check notification: %w", err) + } + rows, err := tx.Query(ctx, countsSQL, notificationID, schoolID) + if err != nil { + return fmt.Errorf("count deliveries: %w", err) + } + defer rows.Close() + for rows.Next() { + var status string + var n int64 + if err := rows.Scan(&status, &n); err != nil { + return fmt.Errorf("scan counts: %w", err) + } + counts[status] = int(n) + } + return rows.Err() + }) + return counts, err +} + +// ClaimQueued runs across all schools, so the service role must own the tables or bypass RLS. +func (s *PgStore) ClaimQueued(ctx context.Context, limit int, lease time.Duration) ([]Job, error) { + rows, err := s.pool.Query(ctx, claimSQL, limit, lease.Seconds()) + if err != nil { + return nil, fmt.Errorf("claim deliveries: %w", err) + } + defer rows.Close() + var jobs []Job + for rows.Next() { + var j Job + if err := rows.Scan(&j.DeliveryID, &j.SchoolID, &j.NotificationID, &j.UserID, &j.Attempt, + &j.Type, &j.Title, &j.Body, &j.Urgent, &j.ChildIDs, &j.ChannelsAllowed, &j.Email, &j.Phone); err != nil { + return nil, fmt.Errorf("scan claim: %w", err) + } + jobs = append(jobs, j) + } + return jobs, rows.Err() +} + +// ReleaseDelivery clears the claim. +func (s *PgStore) ReleaseDelivery(ctx context.Context, schoolID, deliveryID string) error { + return kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + if _, err := tx.Exec(ctx, releaseSQL, deliveryID, schoolID); err != nil { + return fmt.Errorf("release delivery: %w", err) + } + return nil + }) +} + +// FinishDelivery updates the delivery and writes the outbox row in the same transaction. +func (s *PgStore) FinishDelivery(ctx context.Context, schoolID, deliveryID string, res Result) error { + return kit.WithSchool(ctx, s.pool, schoolID, func(ctx context.Context, tx pgx.Tx) error { + var notificationID, userID string + err := tx.QueryRow(ctx, finishSQL, deliveryID, schoolID, res.Status, res.Channel, res.Attempt, res.ProviderRef, res.Error).Scan(¬ificationID, &userID) + if errors.Is(err, pgx.ErrNoRows) { + return ErrNotFound + } + if err != nil { + return fmt.Errorf("finish delivery: %w", err) + } + if res.Status != StatusUndelivered { + return nil + } + payload, err := json.Marshal(map[string]string{"notificationId": notificationID, "userId": userID, "deliveryId": deliveryID}) + if err != nil { + return fmt.Errorf("marshal outbox payload: %w", err) + } + if _, err := tx.Exec(ctx, outboxSQL, schoolID, OutboxTopicUndelivered, string(payload)); err != nil { + return fmt.Errorf("insert outbox: %w", err) + } + return nil + }) +} + +// RecordCallback relies on the unique (provider, provider_event_id) key, so concurrent repeats store one row. +func (s *PgStore) RecordCallback(ctx context.Context, ev CallbackEvent) (CallbackOutcome, error) { + var out CallbackOutcome + tx, err := s.pool.Begin(ctx) + if err != nil { + return out, fmt.Errorf("begin tx: %w", err) + } + defer func() { _ = tx.Rollback(ctx) }() // no-op after commit + + var deliveryID, schoolID string + if err := tx.QueryRow(ctx, findByRefSQL, ev.ProviderRef).Scan(&deliveryID, &schoolID); errors.Is(err, pgx.ErrNoRows) { + return out, nil + } else if err != nil { + return out, fmt.Errorf("find delivery: %w", err) + } + out.Matched = true + tag, err := tx.Exec(ctx, insertCallbackSQL, schoolID, deliveryID, ev.Provider, ev.EventID, ev.Status, string(ev.Payload)) + if err != nil { + return out, fmt.Errorf("insert callback: %w", err) + } + if tag.RowsAffected() == 0 { + out.Duplicate = true + return out, nil + } + if _, err := tx.Exec(ctx, applyCallbackSQL, deliveryID, ev.Status); err != nil { + return out, fmt.Errorf("apply callback: %w", err) + } + return out, tx.Commit(ctx) +} diff --git a/services/communication/internal/notifications/service.go b/services/communication/internal/notifications/service.go new file mode 100644 index 0000000..7f3de9f --- /dev/null +++ b/services/communication/internal/notifications/service.go @@ -0,0 +1,347 @@ +package notifications + +import ( + "context" + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "regexp" + "strings" + "time" + + kit "github.com/stackable/go-kit" +) + +const ( + maxRecipients = 1000 // one request cannot fan out to more than a large school + maxTitleLen = 200 + maxBodyLen = 5000 + maxTypeLen = 64 + maxKeyLen = 128 + defaultLimit = 20 + maxInboxLimit = 100 + maxChildIDs = 20 + maxPrefsPerPut = 50 +) + +var ( + uuidPattern = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) + clockPattern = regexp.MustCompile(`^([01]\d|2[0-3]):[0-5]\d$`) +) + +// NewID returns a random UUID v4 string. +func NewID() string { + b := make([]byte, 16) + _, _ = rand.Read(b) // crypto/rand does not fail on supported platforms + b[6] = b[6]&0x0f | 0x40 + b[8] = b[8]&0x3f | 0x80 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) +} + +// RecipientInput is one already-resolved recipient from the caller. +type RecipientInput struct { + UserID string `json:"userId"` + ChildIDs []string `json:"childIds"` + ChannelsAllowed []string `json:"channelsAllowed"` + Email string `json:"email"` + Phone string `json:"phone"` +} + +// CreateInput is the body of POST /v1/notifications. +type CreateInput struct { + Type string `json:"type"` + Title string `json:"title"` + Body string `json:"body"` + Recipients []RecipientInput `json:"recipients"` + Urgent bool `json:"urgent"` + IdempotencyKey string `json:"idempotencyKey"` +} + +// InboxPage is one page of a user's inbox. +type InboxPage struct { + Items []InboxItem `json:"items"` + NextCursor *string `json:"nextCursor"` +} + +// Service holds the notification rules. It knows nothing about HTTP. +type Service struct { + store Store +} + +// NewService builds a Service over a Store. +func NewService(store Store) *Service { return &Service{store: store} } + +func isValidChannel(c string) bool { + for _, known := range ChannelOrder { + if c == known { + return true + } + } + return false +} + +// orderedChannels returns the set in fallback order; an empty set means every channel. +func orderedChannels(set map[string]bool) []string { + out := []string{} + for _, c := range ChannelOrder { + if len(set) == 0 || set[c] { + out = append(out, c) + } + } + return out +} + +func addUnique(list []string, items ...string) []string { + seen := map[string]bool{} + for _, v := range list { + seen[v] = true + } + for _, v := range items { + if !seen[v] { + seen[v] = true + list = append(list, v) + } + } + return list +} + +// validateCreate checks the whole body and reports every failed field at once. +func validateCreate(in *CreateInput) error { + bad := map[string]string{} + in.Type, in.Title, in.IdempotencyKey = strings.TrimSpace(in.Type), strings.TrimSpace(in.Title), strings.TrimSpace(in.IdempotencyKey) + if in.Type == "" || len(in.Type) > maxTypeLen { + bad["type"] = "required, at most 64 characters" + } + if in.Title == "" || len(in.Title) > maxTitleLen { + bad["title"] = "required, at most 200 characters" + } + if in.Body == "" || len(in.Body) > maxBodyLen { + bad["body"] = "required, at most 5000 characters" + } + if in.IdempotencyKey == "" || len(in.IdempotencyKey) > maxKeyLen { + bad["idempotencyKey"] = "required, at most 128 characters" + } + if len(in.Recipients) == 0 || len(in.Recipients) > maxRecipients { + bad["recipients"] = "between 1 and 1000 recipients required" + } + for i := range in.Recipients { + r := &in.Recipients[i] + r.UserID = strings.ToLower(strings.TrimSpace(r.UserID)) + if !uuidPattern.MatchString(r.UserID) { + bad[fmt.Sprintf("recipients[%d].userId", i)] = "must be a uuid" + } + if len(r.ChildIDs) > maxChildIDs { + bad[fmt.Sprintf("recipients[%d].childIds", i)] = "at most 20 children" + } + for j := range r.ChildIDs { + r.ChildIDs[j] = strings.ToLower(strings.TrimSpace(r.ChildIDs[j])) + if !uuidPattern.MatchString(r.ChildIDs[j]) { + bad[fmt.Sprintf("recipients[%d].childIds[%d]", i, j)] = "must be a uuid" + } + } + for _, c := range r.ChannelsAllowed { + if !isValidChannel(c) { + bad[fmt.Sprintf("recipients[%d].channelsAllowed", i)] = "allowed values are push, sms, email" + } + } + } + if len(bad) > 0 { + return kit.Validation("Notification is invalid.", bad) + } + return nil +} + +// dedupeRecipients merges repeated parents into one entry that lists all their children. +// Channel sets are unioned so a parent allowed on push for one child and sms for another gets both. +func dedupeRecipients(in []RecipientInput) []Recipient { + var order []string + byUser := map[string]*Recipient{} + channels := map[string]map[string]bool{} + for _, r := range in { + cur, seen := byUser[r.UserID] + if !seen { + cur = &Recipient{UserID: r.UserID, ChildIDs: []string{}} + byUser[r.UserID] = cur + channels[r.UserID] = map[string]bool{} + order = append(order, r.UserID) + } + cur.ChildIDs = addUnique(cur.ChildIDs, r.ChildIDs...) + if cur.Email == "" { + cur.Email = strings.TrimSpace(r.Email) + } + if cur.Phone == "" { + cur.Phone = strings.TrimSpace(r.Phone) + } + // an entry with no channel list allows everything, so the union becomes "all" + if len(r.ChannelsAllowed) == 0 { + for _, c := range ChannelOrder { + channels[r.UserID][c] = true + } + } + for _, c := range r.ChannelsAllowed { + channels[r.UserID][c] = true + } + } + out := make([]Recipient, 0, len(order)) + for _, id := range order { + byUser[id].ChannelsAllowed = orderedChannels(channels[id]) + out = append(out, *byUser[id]) + } + return out +} + +// Create stores a notification and queues one delivery per distinct parent. +// A repeated idempotency key returns the original notification and created=false. +func (s *Service) Create(ctx context.Context, schoolID string, in CreateInput) (Notification, bool, error) { + if err := validateCreate(&in); err != nil { + return Notification{}, false, err + } + n, created, err := s.store.CreateNotification(ctx, NewNotification{ + Notification: Notification{ID: NewID(), Type: in.Type, Title: in.Title, Body: in.Body, Urgent: in.Urgent, IdempotencyKey: in.IdempotencyKey}, + SchoolID: schoolID, + Recipients: dedupeRecipients(in.Recipients), + }) + if err != nil { + return Notification{}, false, fmt.Errorf("create notification: %w", err) + } + return n, created, nil +} + +func encodeCursor(c Cursor) string { + return base64.RawURLEncoding.EncodeToString([]byte(c.CreatedAt.UTC().Format(time.RFC3339Nano) + "|" + c.ID)) +} + +func decodeCursor(raw string) (*Cursor, error) { + if raw == "" { + return nil, nil + } + b, err := base64.RawURLEncoding.DecodeString(raw) + if err != nil { + return nil, kit.Validation("Cursor is invalid.", nil) + } + ts, id, found := strings.Cut(string(b), "|") + t, perr := time.Parse(time.RFC3339Nano, ts) + if !found || perr != nil || !uuidPattern.MatchString(id) { + return nil, kit.Validation("Cursor is invalid.", nil) + } + return &Cursor{CreatedAt: t, ID: id}, nil +} + +// Inbox returns one page of the user's notifications; limit 0 means the default. +func (s *Service) Inbox(ctx context.Context, schoolID, userID string, limit int, rawCursor string) (InboxPage, error) { + if limit < 0 || limit > maxInboxLimit { + return InboxPage{}, kit.Validation("limit must be between 1 and 100.", map[string]string{"limit": "1-100"}) + } + if limit == 0 { + limit = defaultLimit + } + after, err := decodeCursor(rawCursor) + if err != nil { + return InboxPage{}, err + } + // one extra row tells us whether another page exists + items, err := s.store.ListInbox(ctx, schoolID, userID, limit+1, after) + if err != nil { + return InboxPage{}, fmt.Errorf("list inbox: %w", err) + } + page := InboxPage{Items: items} + if len(items) > limit { + page.Items = items[:limit] + last := page.Items[limit-1] + next := encodeCursor(Cursor{CreatedAt: last.CreatedAt, ID: last.ID}) + page.NextCursor = &next + } + return page, nil +} + +// MarkRead marks one notification read for the caller. +func (s *Service) MarkRead(ctx context.Context, schoolID, userID, notificationID string) error { + if !uuidPattern.MatchString(notificationID) { + return kit.Validation("Notification id must be a uuid.", nil) + } + if err := s.store.MarkRead(ctx, schoolID, userID, notificationID); err != nil { + return mapNotFound(err, "Notification not found.") + } + return nil +} + +// Preferences lists the caller's preference rows. +func (s *Service) Preferences(ctx context.Context, schoolID, userID string) ([]Preference, error) { + prefs, err := s.store.ListPreferences(ctx, schoolID, userID) + if err != nil { + return nil, fmt.Errorf("list preferences: %w", err) + } + return prefs, nil +} + +func validatePreferences(prefs []Preference) error { + bad := map[string]string{} + if len(prefs) == 0 || len(prefs) > maxPrefsPerPut { + bad["preferences"] = "between 1 and 50 rows required" + } + for i, p := range prefs { + field := fmt.Sprintf("preferences[%d]", i) + if p.Type == "" || len(p.Type) > maxTypeLen { + bad[field+".type"] = "required, at most 64 characters" + } + if len(p.Channels) == 0 { + bad[field+".channels"] = "at least one channel required" + } + for _, c := range p.Channels { + if !isValidChannel(c) { + bad[field+".channels"] = "allowed values are push, sms, email" + } + } + if (p.QuietStart == nil) != (p.QuietEnd == nil) { + bad[field+".quietStart"] = "quietStart and quietEnd must be set together" + } else if p.QuietStart != nil && (!clockPattern.MatchString(*p.QuietStart) || !clockPattern.MatchString(*p.QuietEnd)) { + bad[field+".quietStart"] = "times must be HH:MM" + } + } + if len(bad) > 0 { + return kit.Validation("Preferences are invalid.", bad) + } + return nil +} + +// SavePreferences upserts the given types and returns all of the caller's rows. +func (s *Service) SavePreferences(ctx context.Context, schoolID, userID string, prefs []Preference) ([]Preference, error) { + if err := validatePreferences(prefs); err != nil { + return nil, err + } + for i := range prefs { + prefs[i].Channels = orderedChannelsFrom(prefs[i].Channels) + } + if err := s.store.SavePreferences(ctx, schoolID, userID, prefs); err != nil { + return nil, fmt.Errorf("save preferences: %w", err) + } + return s.Preferences(ctx, schoolID, userID) +} + +func orderedChannelsFrom(list []string) []string { + set := map[string]bool{} + for _, c := range list { + set[c] = true + } + return orderedChannels(set) +} + +// DeliveryCounts returns how many deliveries of a notification sit in each status. +func (s *Service) DeliveryCounts(ctx context.Context, schoolID, notificationID string) (map[string]int, error) { + if !uuidPattern.MatchString(notificationID) { + return nil, kit.Validation("Notification id must be a uuid.", nil) + } + counts, err := s.store.DeliveryCounts(ctx, schoolID, notificationID) + if err != nil { + return nil, mapNotFound(err, "Notification not found.") + } + return counts, nil +} + +func mapNotFound(err error, message string) error { + if errors.Is(err, ErrNotFound) { + return kit.NotFound(message) + } + return err +} diff --git a/services/communication/internal/notifications/store.go b/services/communication/internal/notifications/store.go new file mode 100644 index 0000000..53bd4f8 --- /dev/null +++ b/services/communication/internal/notifications/store.go @@ -0,0 +1,32 @@ +package notifications + +import ( + "context" + "time" +) + +// Store persists notifications. Every method is scoped by school id except ClaimQueued and RecordCallback. +// Those two run for the whole service because the worker and provider callbacks carry no school token. +type Store interface { + // CreateNotification returns the stored row and false when the idempotency key already existed. + CreateNotification(ctx context.Context, n NewNotification) (Notification, bool, error) + // ListInbox returns up to limit items newest first, after the cursor when given. + ListInbox(ctx context.Context, schoolID, userID string, limit int, after *Cursor) ([]InboxItem, error) + // MarkRead is idempotent and returns ErrNotFound when the user is not a recipient. + MarkRead(ctx context.Context, schoolID, userID, notificationID string) error + ListPreferences(ctx context.Context, schoolID, userID string) ([]Preference, error) + // FindPreference returns the row for the type, else the AnyType row, else nil. + FindPreference(ctx context.Context, schoolID, userID, notifType string) (*Preference, error) + // SavePreferences upserts the given types and leaves other types untouched. + SavePreferences(ctx context.Context, schoolID, userID string, prefs []Preference) error + // DeliveryCounts returns a count for every status, or ErrNotFound. + DeliveryCounts(ctx context.Context, schoolID, notificationID string) (map[string]int, error) + // ClaimQueued leases up to limit QUEUED deliveries so two workers never send the same one. + ClaimQueued(ctx context.Context, limit int, lease time.Duration) ([]Job, error) + // ReleaseDelivery gives a claimed delivery back to the queue untouched. + ReleaseDelivery(ctx context.Context, schoolID, deliveryID string) error + // FinishDelivery writes the result and, for UNDELIVERED, the outbox row in the same transaction. + FinishDelivery(ctx context.Context, schoolID, deliveryID string, res Result) error + // RecordCallback stores a provider report once per (provider, event id) and updates the delivery it names. + RecordCallback(ctx context.Context, ev CallbackEvent) (CallbackOutcome, error) +} diff --git a/services/communication/internal/notifications/types.go b/services/communication/internal/notifications/types.go new file mode 100644 index 0000000..32c8020 --- /dev/null +++ b/services/communication/internal/notifications/types.go @@ -0,0 +1,138 @@ +package notifications + +import ( + "errors" + "time" +) + +// Delivery channels in fallback order. +const ( + ChannelPush = "push" + ChannelSMS = "sms" + ChannelEmail = "email" +) + +// Delivery statuses from the delivery state machine. +const ( + StatusQueued = "QUEUED" + StatusSent = "SENT" + StatusDelivered = "DELIVERED" + StatusFailed = "FAILED" + StatusUndelivered = "UNDELIVERED" +) + +// TypeSafety notifications ignore quiet hours because a child's safety cannot wait for morning. +const TypeSafety = "safety" + +// AnyType is the preference row that applies when no row exists for the notification's own type. +const AnyType = "*" + +// ChannelOrder is the fallback order tried for every delivery. +var ChannelOrder = []string{ChannelPush, ChannelSMS, ChannelEmail} + +// AllStatuses lists every delivery status so counts always include zeros. +var AllStatuses = []string{StatusQueued, StatusSent, StatusDelivered, StatusFailed, StatusUndelivered} + +// ErrNotFound is returned by a Store when the row does not exist for that school. +var ErrNotFound = errors.New("not found") + +// Notification is one message sent to many recipients. +type Notification struct { + ID string `json:"id"` + Type string `json:"type"` + Title string `json:"title"` + Body string `json:"body"` + Urgent bool `json:"urgent"` + SenderIdentityID *string `json:"senderIdentityId"` + IdempotencyKey string `json:"idempotencyKey"` + CreatedAt time.Time `json:"createdAt"` +} + +// Recipient is one parent after deduplication, with the children the message is about. +type Recipient struct { + UserID string + ChildIDs []string + ChannelsAllowed []string + Email string + Phone string +} + +// NewNotification is what a Store persists in one transaction: the notification, its recipients and one QUEUED delivery each. +type NewNotification struct { + Notification + SchoolID string + Recipients []Recipient +} + +// InboxItem is a notification as one recipient sees it. +type InboxItem struct { + ID string `json:"id"` + Type string `json:"type"` + Title string `json:"title"` + Body string `json:"body"` + Urgent bool `json:"urgent"` + ChildIDs []string `json:"childIds"` + CreatedAt time.Time `json:"createdAt"` + ReadAt *time.Time `json:"readAt"` +} + +// Cursor is the keyset position after the last inbox item of a page. +type Cursor struct { + CreatedAt time.Time + ID string +} + +// Preference is a user's channel choice and quiet hours for one notification type. +type Preference struct { + Type string `json:"type"` + Channels []string `json:"channels"` + QuietStart *string `json:"quietStart"` + QuietEnd *string `json:"quietEnd"` +} + +// Job is one claimed QUEUED delivery with everything the worker needs to send it. +type Job struct { + DeliveryID string + SchoolID string + NotificationID string + UserID string + Attempt int + Type string + Title string + Body string + Urgent bool + ChildIDs []string + ChannelsAllowed []string + Email string + Phone string +} + +// Result is the final state the worker writes for a delivery. +type Result struct { + Status string + Channel string + Attempt int + ProviderRef string + Error string +} + +// CallbackEvent is a provider delivery report normalised by ParseCallback. +type CallbackEvent struct { + Provider string + EventID string + ProviderRef string + Status string + Payload []byte +} + +// CallbackOutcome tells the caller what RecordCallback did. +type CallbackOutcome struct { + Matched bool + Duplicate bool +} + +// OutboxTopicUndelivered is published when every channel failed for a recipient. +const OutboxTopicUndelivered = "delivery.undelivered" + +// canApplyCallback reports whether a provider report may move a delivery from this status. +func canApplyCallback(from string) bool { return from == StatusSent || from == StatusFailed } diff --git a/services/communication/migrations/00001_create_communication.sql b/services/communication/migrations/00001_create_communication.sql new file mode 100644 index 0000000..aa1dd49 --- /dev/null +++ b/services/communication/migrations/00001_create_communication.sql @@ -0,0 +1,141 @@ +-- +goose Up +create schema if not exists communication; + +create table communication.sender_identities ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + email_from text, + sms_sender_id text, + verified boolean not null default false, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); +create index sender_identities_school_idx on communication.sender_identities (school_id); + +create table communication.notifications ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + type text not null, + title text not null, + body text not null, + urgent boolean not null default false, + sender_identity_id uuid references communication.sender_identities (id), + idempotency_key text not null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + unique (school_id, idempotency_key) +); +create index notifications_sender_identity_idx on communication.notifications (sender_identity_id); + +create table communication.notification_recipients ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + notification_id uuid not null references communication.notifications (id), + user_id uuid not null, + child_ids uuid[] not null default '{}', + channels_allowed text[] not null, + email text, + phone text, + read_at timestamptz, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + unique (notification_id, user_id) +); +create index notification_recipients_user_idx on communication.notification_recipients (school_id, user_id); + +create table communication.notification_deliveries ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + notification_id uuid not null references communication.notifications (id), + user_id uuid not null, + channel text not null check (channel in ('push', 'sms', 'email')), + status text not null check (status in ('QUEUED', 'SENT', 'DELIVERED', 'FAILED', 'UNDELIVERED')), + attempt integer not null default 0, + provider_ref text, + error text, + claimed_at timestamptz, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); +create index notification_deliveries_notification_idx on communication.notification_deliveries (notification_id); +create index notification_deliveries_queue_idx on communication.notification_deliveries (created_at) where status = 'QUEUED'; +create index notification_deliveries_provider_ref_idx on communication.notification_deliveries (provider_ref) where provider_ref is not null; + +create table communication.delivery_callbacks ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + delivery_id uuid not null references communication.notification_deliveries (id), + provider text not null, + provider_event_id text not null, + status text not null, + payload jsonb not null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + unique (provider, provider_event_id) +); +create index delivery_callbacks_delivery_idx on communication.delivery_callbacks (delivery_id); + +create table communication.notification_preferences ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + user_id uuid not null, + type text not null, + channels text[] not null, + quiet_start time, + quiet_end time, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + unique (school_id, user_id, type) +); + +create table communication.outbox ( + id uuid primary key default gen_random_uuid(), + school_id uuid not null, + topic text not null, + payload jsonb not null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + published_at timestamptz +); +create index outbox_unpublished_idx on communication.outbox (created_at) where published_at is null; + +-- FORCE is off on purpose: the owner role (worker, callbacks) bypasses RLS, every request path goes through WithSchool. +alter table communication.sender_identities enable row level security; +alter table communication.notifications enable row level security; +alter table communication.notification_recipients enable row level security; +alter table communication.notification_deliveries enable row level security; +alter table communication.delivery_callbacks enable row level security; +alter table communication.notification_preferences enable row level security; +alter table communication.outbox enable row level security; + +create policy tenant_isolation on communication.sender_identities + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); +create policy tenant_isolation on communication.notifications + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); +create policy tenant_isolation on communication.notification_recipients + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); +create policy tenant_isolation on communication.notification_deliveries + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); +create policy tenant_isolation on communication.delivery_callbacks + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); +create policy tenant_isolation on communication.notification_preferences + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); +create policy tenant_isolation on communication.outbox + using (school_id = current_setting('app.school_id', true)::uuid) + with check (school_id = current_setting('app.school_id', true)::uuid); + +-- +goose Down +drop table communication.outbox; +drop table communication.notification_preferences; +drop table communication.delivery_callbacks; +drop table communication.notification_deliveries; +drop table communication.notification_recipients; +drop table communication.notifications; +drop table communication.sender_identities; +drop schema communication; diff --git a/services/documents/go.mod b/services/documents/go.mod new file mode 100644 index 0000000..6bd264d --- /dev/null +++ b/services/documents/go.mod @@ -0,0 +1,11 @@ +module github.com/stackable/documents + +go 1.25.0 + +require ( + github.com/go-chi/chi/v5 v5.3.2 + github.com/jackc/pgx/v5 v5.11.0 + github.com/stackable/go-kit v0.0.0 +) + +replace github.com/stackable/go-kit => ../../packages/go-kit diff --git a/services/documents/internal/documents/cachekey.go b/services/documents/internal/documents/cachekey.go new file mode 100644 index 0000000..0ae7508 --- /dev/null +++ b/services/documents/internal/documents/cachekey.go @@ -0,0 +1,41 @@ +package documents + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" +) + +// canonicalJSON re-encodes raw so equal objects give equal bytes (encoding/json sorts map keys). +func canonicalJSON(raw json.RawMessage) (string, error) { + if len(bytes.TrimSpace(raw)) == 0 { + return "{}", nil + } + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.UseNumber() // keeps big integers exact instead of rounding through float64 + var v any + if err := dec.Decode(&v); err != nil { + return "", err + } + out, err := json.Marshal(v) + if err != nil { + return "", err + } + return string(out), nil +} + +// CacheKey is sha256 over type, canonical params, dataVersion, format and schoolId. +// dataVersion is in the key so changed data can never be answered with an older file. +func CacheKey(schoolID string, t DocType, params json.RawMessage, dataVersion string, f Format) (string, error) { + canon, err := canonicalJSON(params) + if err != nil { + return "", fmt.Errorf("canonical params: %w", err) + } + h := sha256.New() + for _, part := range []string{string(t), canon, dataVersion, string(f), schoolID} { + fmt.Fprintf(h, "%d:%s|", len(part), part) // length prefix stops "ab"+"c" colliding with "a"+"bc" + } + return hex.EncodeToString(h.Sum(nil)), nil +} diff --git a/services/documents/internal/documents/content.go b/services/documents/internal/documents/content.go new file mode 100644 index 0000000..347e8a8 --- /dev/null +++ b/services/documents/internal/documents/content.go @@ -0,0 +1,181 @@ +package documents + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" +) + +// Column is one table column; Money columns hold int64 minor units. +type Column struct { + Key string `json:"key"` + Label string `json:"label"` + Money bool `json:"money"` +} + +// TableData is the layout used by every type except receipt. +type TableData struct { + SchoolName string `json:"schoolName"` + Title string `json:"title"` + Columns []Column `json:"columns"` + Rows []map[string]any `json:"rows"` +} + +// ReceiptLine is one charged item on a receipt. +type ReceiptLine struct { + Description string `json:"description"` + AmountMinor int64 `json:"amountMinor"` +} + +// ReceiptData is the receipt layout; the total is always summed from the lines. +type ReceiptData struct { + SchoolName string `json:"schoolName"` + ReceiptNo string `json:"receiptNo"` + PayerName string `json:"payerName"` + IssuedAt string `json:"issuedAt"` + Lines []ReceiptLine `json:"lines"` +} + +// Total sums the line amounts. +func (r ReceiptData) Total() int64 { + var sum int64 + for _, l := range r.Lines { + sum += l.AmountMinor + } + return sum +} + +// Content is validated render input: exactly one of Receipt or Table is set. +type Content struct { + Receipt *ReceiptData + Table *TableData +} + +// SchoolName returns the header text. +func (c Content) SchoolName() string { + if c.Receipt != nil { + return c.Receipt.SchoolName + } + return c.Table.SchoolName +} + +// RowCount estimates the render size and decides sync versus queued. +func (c Content) RowCount() int { + if c.Receipt != nil { + return len(c.Receipt.Lines) + } + return len(c.Table.Rows) +} + +// AsTable converts a receipt into a two-column table so xlsx has one code path. +func (c Content) AsTable() TableData { + if c.Table != nil { + return *c.Table + } + r := c.Receipt + t := TableData{ + SchoolName: r.SchoolName, + Title: "Receipt " + r.ReceiptNo, + Columns: []Column{{Key: "description", Label: "Description"}, {Key: "amount", Label: "Amount", Money: true}}, + } + for _, l := range r.Lines { + t.Rows = append(t.Rows, map[string]any{"description": l.Description, "amount": json.Number(strconv.FormatInt(l.AmountMinor, 10))}) + } + t.Rows = append(t.Rows, map[string]any{"description": "Total", "amount": json.Number(strconv.FormatInt(r.Total(), 10))}) + return t +} + +// ParseContent decodes and validates the caller's data for a document type. +func ParseContent(t DocType, raw json.RawMessage) (Content, error) { + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.UseNumber() // table cells keep exact integers, so money never passes through float64 + if t == TypeReceipt { + var r ReceiptData + if err := dec.Decode(&r); err != nil { + return Content{}, invalid("Receipt data is malformed.", fieldError{"data", "must match the receipt shape: " + err.Error()}) + } + if err := validateReceipt(r); err != nil { + return Content{}, err + } + return Content{Receipt: &r}, nil + } + var tb TableData + if err := dec.Decode(&tb); err != nil { + return Content{}, invalid("Table data is malformed.", fieldError{"data", "must match the table shape: " + err.Error()}) + } + if err := validateTable(tb); err != nil { + return Content{}, err + } + return Content{Table: &tb}, nil +} + +func validateReceipt(r ReceiptData) error { + var f []fieldError + if strings.TrimSpace(r.SchoolName) == "" { + f = append(f, fieldError{"data.schoolName", "is required"}) + } + if strings.TrimSpace(r.ReceiptNo) == "" { + f = append(f, fieldError{"data.receiptNo", "is required"}) + } + if len(r.Lines) == 0 { + f = append(f, fieldError{"data.lines", "needs at least one line"}) + } + if len(f) > 0 { + return invalid("Receipt data is invalid.", f...) + } + return nil +} + +func validateTable(t TableData) error { + var f []fieldError + if strings.TrimSpace(t.SchoolName) == "" { + f = append(f, fieldError{"data.schoolName", "is required"}) + } + if len(t.Columns) == 0 { + f = append(f, fieldError{"data.columns", "needs at least one column"}) + } + for i, c := range t.Columns { + if c.Key == "" || c.Label == "" { + f = append(f, fieldError{fmt.Sprintf("data.columns[%d]", i), "key and label are required"}) + } + } + if len(f) > 0 { + return invalid("Table data is invalid.", f...) + } + for i, row := range t.Rows { + for _, c := range t.Columns { + if !c.Money { + continue + } + if _, ok := moneyCell(row[c.Key]); !ok { + return invalid("Table data is invalid.", fieldError{fmt.Sprintf("data.rows[%d].%s", i, c.Key), "must be an integer amount in minor units"}) + } + } + } + return nil +} + +// moneyCell reads a minor-unit integer; anything else (float, string, missing) is rejected. +func moneyCell(v any) (int64, bool) { + n, ok := v.(json.Number) + if !ok { + return 0, false + } + i, err := n.Int64() + return i, err == nil +} + +func cellText(v any) string { + switch x := v.(type) { + case nil: + return "" + case string: + return x + case json.Number: + return x.String() + default: + return fmt.Sprint(x) + } +} diff --git a/services/documents/internal/documents/errors.go b/services/documents/internal/documents/errors.go new file mode 100644 index 0000000..b3b6a18 --- /dev/null +++ b/services/documents/internal/documents/errors.go @@ -0,0 +1,34 @@ +package documents + +import ( + "net/http" + + kit "github.com/stackable/go-kit" +) + +// docError builds an AppError with a DOMAIN_CONDITION code that go-kit has no constructor for. +func docError(status int, code, message string) *kit.AppError { + return &kit.AppError{Status: status, Code: code, Message: message} +} + +func notFoundErr() *kit.AppError { + return docError(http.StatusNotFound, "DOCUMENT_NOT_FOUND", "Document not found.") +} + +func linkExpiredErr() *kit.AppError { + return docError(http.StatusForbidden, "DOCUMENT_LINK_EXPIRED", "This download link has expired. Request a new one.") +} + +func linkInvalidErr() *kit.AppError { + return docError(http.StatusForbidden, "DOCUMENT_LINK_INVALID", "This download link is not valid.") +} + +// fieldError is one failed field in a validation response. +type fieldError struct { + Field string `json:"field"` + Message string `json:"message"` +} + +func invalid(message string, fields ...fieldError) *kit.AppError { + return kit.Validation(message, map[string]any{"fields": fields}) +} diff --git a/services/documents/internal/documents/id.go b/services/documents/internal/documents/id.go new file mode 100644 index 0000000..554072c --- /dev/null +++ b/services/documents/internal/documents/id.go @@ -0,0 +1,20 @@ +package documents + +import ( + "crypto/rand" + "fmt" + "regexp" +) + +var uuidPattern = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) + +func isUUID(s string) bool { return uuidPattern.MatchString(s) } + +// newUUID returns a random v4 uuid; the database columns are uuid, so ids must be valid ones. +func newUUID() string { + b := make([]byte, 16) + _, _ = rand.Read(b) // crypto/rand does not fail on supported platforms + b[6] = b[6]&0x0f | 0x40 + b[8] = b[8]&0x3f | 0x80 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) +} diff --git a/services/documents/internal/documents/money.go b/services/documents/internal/documents/money.go new file mode 100644 index 0000000..7203256 --- /dev/null +++ b/services/documents/internal/documents/money.go @@ -0,0 +1,33 @@ +package documents + +import ( + "strconv" + "strings" +) + +// FormatKES renders int64 minor units (cents) as "KES 1,234.56"; negatives become "-KES 5.00". +// Money stays an integer everywhere else so rounding can never change a receipt. +func FormatKES(minor int64) string { + neg := minor < 0 + abs := uint64(minor) + if neg { + abs = -abs // two's complement negate also handles MinInt64 + } + whole := strconv.FormatUint(abs/100, 10) + cents := strconv.FormatUint(abs%100, 10) + if len(cents) == 1 { + cents = "0" + cents + } + var b strings.Builder + for i, ch := range whole { + if i > 0 && (len(whole)-i)%3 == 0 { + b.WriteByte(',') + } + b.WriteRune(ch) + } + out := "KES " + b.String() + "." + cents + if neg { + return "-" + out + } + return out +} diff --git a/services/documents/internal/documents/pdf.go b/services/documents/internal/documents/pdf.go new file mode 100644 index 0000000..2285766 --- /dev/null +++ b/services/documents/internal/documents/pdf.go @@ -0,0 +1,133 @@ +package documents + +import ( + "bytes" + "context" + "fmt" + + "github.com/go-pdf/fpdf" +) + +const ( + pageMargin = 15.0 // mm + pageBottom = 18.0 // mm kept free for the footer + contentWidth = 180.0 // A4 width minus both margins + tableRowHeight = 7.0 +) + +// PDFRenderer draws A4 documents with a school-name header on every page. +type PDFRenderer struct { + Uncompressed bool // tests turn compression off to look for text in the bytes +} + +// Render draws a receipt layout for receipts and a table layout for everything else. +func (r PDFRenderer) Render(_ context.Context, c Content) ([]byte, error) { + p := fpdf.New("P", "mm", "A4", "") + p.SetCompression(!r.Uncompressed) + p.SetMargins(pageMargin, pageMargin, pageMargin) + p.SetAutoPageBreak(true, pageBottom) + tr := p.UnicodeTranslatorFromDescriptor("") // core fonts are cp1252, so convert from UTF-8 + + school := c.SchoolName() + p.SetHeaderFunc(func() { + p.SetFont("Helvetica", "B", 14) + p.CellFormat(0, 8, tr(school), "", 1, "L", false, 0, "") + p.Line(pageMargin, p.GetY(), pageMargin+contentWidth, p.GetY()) + p.Ln(4) + }) + p.SetFooterFunc(func() { + p.SetY(-12) + p.SetFont("Helvetica", "", 8) + p.CellFormat(0, 6, fmt.Sprintf("Page %d of {nb}", p.PageNo()), "", 0, "C", false, 0, "") + }) + p.AliasNbPages("") + p.AddPage() + + if c.Receipt != nil { + drawReceipt(p, tr, *c.Receipt) + } else { + drawTable(p, tr, *c.Table) + } + + var buf bytes.Buffer + if err := p.Output(&buf); err != nil { + return nil, fmt.Errorf("pdf output: %w", err) + } + return buf.Bytes(), nil +} + +func drawReceipt(p *fpdf.Fpdf, tr func(string) string, r ReceiptData) { + p.SetFont("Helvetica", "B", 18) + p.CellFormat(0, 10, "RECEIPT", "", 1, "L", false, 0, "") + p.SetFont("Helvetica", "", 10) + for _, kv := range [][2]string{{"Receipt no", r.ReceiptNo}, {"Received from", r.PayerName}, {"Date", r.IssuedAt}} { + if kv[1] == "" { + continue + } + p.CellFormat(35, 6, kv[0], "", 0, "L", false, 0, "") + p.CellFormat(0, 6, tr(kv[1]), "", 1, "L", false, 0, "") + } + p.Ln(4) + + const descW, amountW = 120.0, 60.0 + p.SetFont("Helvetica", "B", 10) + p.SetFillColor(230, 230, 230) + p.CellFormat(descW, tableRowHeight, "Description", "1", 0, "L", true, 0, "") + p.CellFormat(amountW, tableRowHeight, "Amount", "1", 1, "R", true, 0, "") + p.SetFont("Helvetica", "", 10) + for _, l := range r.Lines { + p.CellFormat(descW, tableRowHeight, tr(fit(p, l.Description, descW)), "1", 0, "L", false, 0, "") + p.CellFormat(amountW, tableRowHeight, FormatKES(l.AmountMinor), "1", 1, "R", false, 0, "") + } + p.SetFont("Helvetica", "B", 11) + p.CellFormat(descW, tableRowHeight+1, "Total", "1", 0, "L", false, 0, "") + p.CellFormat(amountW, tableRowHeight+1, FormatKES(r.Total()), "1", 1, "R", false, 0, "") +} + +func drawTable(p *fpdf.Fpdf, tr func(string) string, t TableData) { + if t.Title != "" { + p.SetFont("Helvetica", "B", 13) + p.CellFormat(0, 9, tr(t.Title), "", 1, "L", false, 0, "") + } + colW := contentWidth / float64(len(t.Columns)) + _, pageH := p.GetPageSize() + + header := func() { + p.SetFont("Helvetica", "B", 9) + p.SetFillColor(230, 230, 230) + for _, c := range t.Columns { + p.CellFormat(colW, tableRowHeight, tr(fit(p, c.Label, colW)), "1", 0, "L", true, 0, "") + } + p.Ln(-1) + p.SetFont("Helvetica", "", 9) + } + header() + for _, row := range t.Rows { + if p.GetY()+tableRowHeight > pageH-pageBottom { + p.AddPage() + header() + } + for _, c := range t.Columns { + if c.Money { + amount, _ := moneyCell(row[c.Key]) // validated when parsed + p.CellFormat(colW, tableRowHeight, FormatKES(amount), "1", 0, "R", false, 0, "") + continue + } + p.CellFormat(colW, tableRowHeight, tr(fit(p, cellText(row[c.Key]), colW)), "1", 0, "L", false, 0, "") + } + p.Ln(-1) + } +} + +// fit trims text so it stays inside its cell instead of overlapping the next column. +func fit(p *fpdf.Fpdf, s string, w float64) string { + limit := w - 2 // cell padding + if p.GetStringWidth(s) <= limit { + return s + } + runes := []rune(s) + for len(runes) > 0 && p.GetStringWidth(string(runes)+"...") > limit { + runes = runes[:len(runes)-1] + } + return string(runes) + "..." +} diff --git a/services/documents/internal/documents/renderer.go b/services/documents/internal/documents/renderer.go new file mode 100644 index 0000000..a776bfd --- /dev/null +++ b/services/documents/internal/documents/renderer.go @@ -0,0 +1,16 @@ +package documents + +import "context" + +// Renderer turns validated content into file bytes for one format. +type Renderer interface { + Render(ctx context.Context, c Content) ([]byte, error) +} + +// contentType maps a format to its MIME type for storage and downloads. +func contentType(f Format) string { + if f == FormatXLSX { + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + } + return "application/pdf" +} diff --git a/services/documents/internal/documents/types.go b/services/documents/internal/documents/types.go new file mode 100644 index 0000000..e34a921 --- /dev/null +++ b/services/documents/internal/documents/types.go @@ -0,0 +1,93 @@ +package documents + +import ( + "encoding/json" + "time" +) + +// DocType is the business kind of a document. +type DocType string + +// Format is the output file format. +type Format string + +// Status is the lifecycle state of a document row or the outcome of a request. +type Status string + +const ( + TypeReceipt DocType = "receipt" + TypeReportCard DocType = "report_card" + TypeFeeStatement DocType = "fee_statement" + TypeClassSheet DocType = "class_sheet" + TypeGeneric DocType = "generic" + + FormatPDF Format = "pdf" + FormatXLSX Format = "xlsx" + + StatusServed Status = "SERVED" // response only: cache hit + StatusStoredAndCached Status = "STORED_AND_CACHED" // file stored and cache row written + StatusQueuedJob Status = "QUEUED_JOB" // waiting for the worker + StatusRendering Status = "RENDERING" // claimed by the worker + StatusFailed Status = "FAILED" // render failed, needs a new request +) + +// Document is one stored (or pending) rendered file. +type Document struct { + ID string + SchoolID string + Type DocType + Format Format + Params json.RawMessage + DataVersion string + ObjectKey string + Status Status + RenderMs int64 + JobPayload json.RawMessage // caller data, kept only while the job is queued + CreatedAt time.Time + UpdatedAt time.Time +} + +// CacheEntry maps a cache key to a stored document until ExpiresAt. +type CacheEntry struct { + SchoolID string + CacheKey string + DocumentID string + ExpiresAt time.Time +} + +// OutboxEvent is a fact to be relayed to other services. +type OutboxEvent struct { + ID string + SchoolID string + Topic string + Payload json.RawMessage + CreatedAt time.Time +} + +// CreateRequest is the POST /v1/documents body; the school comes from the token, never the body. +type CreateRequest struct { + Type DocType `json:"type"` + Format Format `json:"format"` + Params json.RawMessage `json:"params"` + DataVersion string `json:"dataVersion"` + Data json.RawMessage `json:"data"` +} + +// Result is the POST /v1/documents response. +type Result struct { + ID string `json:"id"` + Status Status `json:"status"` + URL string `json:"url,omitempty"` + RenderMs *int64 `json:"renderMs,omitempty"` +} + +// View is the GET /v1/documents/{id} response. +type View struct { + ID string `json:"id"` + Status Status `json:"status"` + Type DocType `json:"type"` + Format Format `json:"format"` + URL string `json:"url,omitempty"` + RenderMs *int64 `json:"renderMs,omitempty"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/services/documents/internal/documents/xlsx.go b/services/documents/internal/documents/xlsx.go new file mode 100644 index 0000000..59e5412 --- /dev/null +++ b/services/documents/internal/documents/xlsx.go @@ -0,0 +1,67 @@ +package documents + +import ( + "context" + "fmt" + + "github.com/xuri/excelize/v2" +) + +const ( + sheetName = "Report" + moneyFormat = 4 // built-in "#,##0.00" +) + +// XLSXRenderer writes one sheet: a bold header row, then one row per record. +type XLSXRenderer struct{} + +// Render writes money columns as numbers in major units so spreadsheets can sum them. +func (XLSXRenderer) Render(_ context.Context, c Content) ([]byte, error) { + t := c.AsTable() + f := excelize.NewFile() + defer func() { _ = f.Close() }() // in-memory file, nothing to flush + if err := f.SetSheetName("Sheet1", sheetName); err != nil { + return nil, fmt.Errorf("name sheet: %w", err) + } + bold, err := f.NewStyle(&excelize.Style{Font: &excelize.Font{Bold: true}}) + if err != nil { + return nil, fmt.Errorf("header style: %w", err) + } + money, err := f.NewStyle(&excelize.Style{NumFmt: moneyFormat}) + if err != nil { + return nil, fmt.Errorf("money style: %w", err) + } + + for i, col := range t.Columns { + cell, _ := excelize.CoordinatesToCellName(i+1, 1) + if err := f.SetCellValue(sheetName, cell, col.Label); err != nil { + return nil, fmt.Errorf("write header: %w", err) + } + if err := f.SetCellStyle(sheetName, cell, cell, bold); err != nil { + return nil, fmt.Errorf("style header: %w", err) + } + } + for r, row := range t.Rows { + for i, col := range t.Columns { + cell, _ := excelize.CoordinatesToCellName(i+1, r+2) + if col.Money { + minor, _ := moneyCell(row[col.Key]) // validated when parsed + if err := f.SetCellValue(sheetName, cell, float64(minor)/100); err != nil { + return nil, fmt.Errorf("write money: %w", err) + } + if err := f.SetCellStyle(sheetName, cell, cell, money); err != nil { + return nil, fmt.Errorf("style money: %w", err) + } + continue + } + if err := f.SetCellValue(sheetName, cell, cellText(row[col.Key])); err != nil { + return nil, fmt.Errorf("write cell: %w", err) + } + } + } + buf, err := f.WriteToBuffer() + if err != nil { + return nil, fmt.Errorf("xlsx output: %w", err) + } + return buf.Bytes(), nil +} diff --git a/services/identity/package.json b/services/identity/package.json new file mode 100644 index 0000000..4c8a7a0 --- /dev/null +++ b/services/identity/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stackable/identity", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/main.ts", + "start": "tsx src/main.ts", + "typecheck": "tsc --noEmit", + "test": "vitest run --root ../.." + }, + "dependencies": { + "@hono/node-server": "^1.19.0", + "@stackable/contracts": "workspace:*", + "@stackable/service-kit": "workspace:*", + "pg": "^8.16.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/pg": "^8.15.0", + "tsx": "^4.20.0", + "typescript": "^5.9.0" + } +} diff --git a/services/identity/src/app.test.ts b/services/identity/src/app.test.ts new file mode 100644 index 0000000..d4a722a --- /dev/null +++ b/services/identity/src/app.test.ts @@ -0,0 +1,53 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { Pool } from "pg"; +import { signServiceToken } from "@stackable/service-kit"; +import { buildApp } from "./app"; + +beforeEach(() => { + process.env.SERVICE_TOKEN_SECRET = "test-secret-test-secret-test-secret-1234"; + vi.spyOn(console, "log").mockImplementation(() => undefined); +}); +afterEach(() => { + delete process.env.SERVICE_TOKEN_SECRET; + vi.restoreAllMocks(); +}); + +const okPool = { query: async () => ({ rows: [] }) } as unknown as Pool; +const badPool = { query: async () => Promise.reject(new Error("down")) } as unknown as Pool; + +describe("GET /v1/me", () => { + it("returns the actor from a valid token", async () => { + const token = await signServiceToken({ audience: "identity", sub: "u1", schoolId: "s1", role: "teacher" }); + const res = await buildApp(okPool, "1").request("/v1/me", { headers: { authorization: `Bearer ${token}` } }); + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ userId: "u1", schoolId: "s1", role: "teacher" }); + }); + + it("rejects a missing token with the envelope", async () => { + const res = await buildApp(okPool, "1").request("/v1/me"); + expect(res.status).toBe(401); + expect(await res.json()).toMatchObject({ code: "AUTH_UNAUTHORIZED" }); + }); + + it("rejects a token issued for another service", async () => { + const token = await signServiceToken({ audience: "payments", sub: "u1", schoolId: "s1" }); + const res = await buildApp(okPool, "1").request("/v1/me", { headers: { authorization: `Bearer ${token}` } }); + expect(res.status).toBe(401); + }); +}); + +describe("probes", () => { + it("/ready is 200 with a healthy database", async () => { + expect((await buildApp(okPool, "1").request("/ready")).status).toBe(200); + }); + + it("/ready is 503 when the database is down", async () => { + expect((await buildApp(badPool, "1").request("/ready")).status).toBe(503); + }); + + it("/ready is degraded, not 503, when no database is configured in dev", async () => { + const res = await buildApp(undefined, "1").request("/ready"); + expect(res.status).toBe(200); + expect(await res.json()).toMatchObject({ status: "degraded" }); + }); +}); diff --git a/services/identity/src/app.ts b/services/identity/src/app.ts new file mode 100644 index 0000000..ee2cf15 --- /dev/null +++ b/services/identity/src/app.ts @@ -0,0 +1,22 @@ +import type { Pool } from "pg"; +import type { MeResponse } from "@stackable/contracts"; +import { createService, requireServiceToken, type ReadyCheck } from "@stackable/service-kit"; + +export const SERVICE_NAME = "identity"; + +/** Builds the identity app; pool is optional so dev can run without a database. */ +export function buildApp(pool: Pool | undefined, version: string) { + const dbCheck: ReadyCheck = pool + ? { name: "database", run: () => pool.query("select 1") } + : { name: "database", required: false, run: () => Promise.reject(new Error("DATABASE_URL not set")) }; + + const app = createService({ name: SERVICE_NAME, version, checks: [dbCheck] }); + + app.get("/v1/me", requireServiceToken(SERVICE_NAME), (c) => { + const claims = c.var.claims; + const body: MeResponse = { userId: claims.sub, schoolId: claims.schoolId, role: claims.role ?? null }; + return c.json(body); + }); + + return app; +} diff --git a/services/identity/src/main.ts b/services/identity/src/main.ts new file mode 100644 index 0000000..a5a1163 --- /dev/null +++ b/services/identity/src/main.ts @@ -0,0 +1,19 @@ +import { serve } from "@hono/node-server"; +import pg from "pg"; +import { buildApp } from "./app"; + +const DEFAULT_PORT = 4001; +const databaseUrl = process.env.DATABASE_URL; + +// Only dev may run without a database; production must fail at boot, not at first request. +if (!databaseUrl && process.env.NODE_ENV === "production") { + throw new Error("DATABASE_URL is required in production"); +} + +const pool = databaseUrl ? new pg.Pool({ connectionString: databaseUrl }) : undefined; +const app = buildApp(pool, process.env.npm_package_version ?? "0.0.0"); +const server = serve({ fetch: app.fetch, port: Number(process.env.PORT ?? DEFAULT_PORT) }); + +process.on("SIGTERM", () => { + server.close(() => void pool?.end()); +}); diff --git a/services/identity/tsconfig.json b/services/identity/tsconfig.json new file mode 100644 index 0000000..cc2c43a --- /dev/null +++ b/services/identity/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src"] +} diff --git a/services/payments/README.md b/services/payments/README.md new file mode 100644 index 0000000..25b11c5 --- /dev/null +++ b/services/payments/README.md @@ -0,0 +1,36 @@ +# payments + +Go service for fees, payments and the ledger. Today it has `/health`, `/ready` and one endpoint: +`POST /v1/webhooks/{provider}` (providers: `fake`, `paystack`, `mpesa`). A webhook is stored once per +`(provider, external_event_id)`; a repeat returns 200 with `"duplicate": true` and changes nothing. + +## Run + +``` +go run ./cmd/payments +``` + +Without `DATABASE_URL` it runs in dev with an in-memory store and `/ready` reports `degraded`. + +## Environment + +| Variable | Default | Meaning | +|---|---|---| +| `PORT` | `4005` | HTTP port | +| `DATABASE_URL` | unset | Postgres URL; required when `APP_ENV=production` | +| `APP_ENV` | unset | Set to `production` to refuse the in-memory store | +| `SERVICE_TOKEN_SECRET` | unset | Used by go-kit's service-token middleware once internal routes exist | + +## Migrations and queries + +- Apply `migrations/*.sql` with goose: `goose -dir migrations postgres "$DATABASE_URL" up`. +- `sqlc.yaml` and `queries/` are ready, but sqlc is not installed here, so + `internal/webhooks/pg_store.go` holds the same query by hand. Run `sqlc generate` later and swap it in. + +## Test + +``` +go vet ./... && go test ./... +``` + +Money in this service is `int64` minor units (note only; no money logic yet). diff --git a/services/payments/cmd/payments/main.go b/services/payments/cmd/payments/main.go new file mode 100644 index 0000000..7a03aa4 --- /dev/null +++ b/services/payments/cmd/payments/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "errors" + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/jackc/pgx/v5/pgxpool" + kit "github.com/stackable/go-kit" + "github.com/stackable/payments/internal/webhooks" +) + +const ( + serviceName = "payments" + defaultPort = "4005" +) + +// version is set at build time with -ldflags "-X main.version=...". +var version = "dev" + +func main() { + logger := kit.NewLogger(serviceName) + if err := run(logger); err != nil { + logger.Error("fatal", "err", err) + os.Exit(1) + } +} + +func run(logger *slog.Logger) error { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + store, checks, closeDB, err := buildStore(ctx) + if err != nil { + return err + } + defer closeDB() + + router := kit.NewRouter(kit.Config{Name: serviceName, Version: version, Logger: logger, Checks: checks}) + webhooks.NewHandler(webhooks.NewService(store)).Routes(router) + + port := os.Getenv("PORT") + if port == "" { + port = defaultPort + } + return kit.Serve(ctx, ":"+port, router, logger) +} + +// buildStore uses Postgres when DATABASE_URL is set and an in-memory store only outside production. +func buildStore(ctx context.Context) (webhooks.Store, []kit.Check, func(), error) { + url := os.Getenv("DATABASE_URL") + if url == "" { + if os.Getenv("APP_ENV") == "production" { + return nil, nil, nil, errors.New("DATABASE_URL is required in production") + } + unset := func(context.Context) error { return errors.New("DATABASE_URL not set") } + return webhooks.NewMemoryStore(), []kit.Check{{Name: "database", Required: false, Fn: unset}}, func() {}, nil + } + pool, err := pgxpool.New(ctx, url) + if err != nil { + return nil, nil, nil, err + } + checks := []kit.Check{{Name: "database", Required: true, Fn: pool.Ping}} + return webhooks.NewPgStore(pool), checks, pool.Close, nil +} diff --git a/services/payments/go.mod b/services/payments/go.mod new file mode 100644 index 0000000..14b7f07 --- /dev/null +++ b/services/payments/go.mod @@ -0,0 +1,20 @@ +module github.com/stackable/payments + +go 1.25.0 + +require ( + github.com/go-chi/chi/v5 v5.3.2 + github.com/jackc/pgx/v5 v5.11.0 + github.com/stackable/go-kit v0.0.0 +) + +require ( + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/text v0.29.0 // indirect +) + +replace github.com/stackable/go-kit => ../../packages/go-kit diff --git a/services/payments/go.sum b/services/payments/go.sum new file mode 100644 index 0000000..a785dba --- /dev/null +++ b/services/payments/go.sum @@ -0,0 +1,30 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY= +github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/services/payments/internal/fees/store.go b/services/payments/internal/fees/store.go new file mode 100644 index 0000000..5fee054 --- /dev/null +++ b/services/payments/internal/fees/store.go @@ -0,0 +1,58 @@ +package fees + +import ( + "context" + "time" +) + +// Repo is the data access inside one transaction; every call is scoped to one school. +type Repo interface { + InsertFeeStructure(ctx context.Context, fs FeeStructure) error + GetFeeStructure(ctx context.Context, id string) (*FeeStructure, error) + ListFeeStructures(ctx context.Context, term, level, after string, limit int) ([]FeeStructure, error) + + InsertInvoice(ctx context.Context, inv Invoice) error + // LinesForUpdate locks and returns the student's lines among ids; foreign or unknown ids are skipped. + LinesForUpdate(ctx context.Context, studentID string, ids []string) ([]LineBalance, error) + RefreshInvoiceStatuses(ctx context.Context, invoiceIDs []string) error + StatementLines(ctx context.Context, studentID, term string, limit int) ([]LineBalance, error) + + // InsertPaymentIfNew returns false when the idempotency key already exists. + InsertPaymentIfNew(ctx context.Context, p Payment) (bool, error) + GetPayment(ctx context.Context, id string) (*Payment, error) + GetPaymentByIdempotencyKey(ctx context.Context, key string) (*Payment, error) + GetPaymentByProviderTxn(ctx context.Context, rail, txnID string) (*Payment, error) + UpdatePayment(ctx context.Context, p Payment) error + ListPaymentsByStudent(ctx context.Context, studentID string, limit int) ([]PaymentSummary, error) + + InsertAllocations(ctx context.Context, allocs []Allocation) error + CreditAllocatedMinor(ctx context.Context, studentID string) (int64, error) + + InsertLedgerEntry(ctx context.Context, e LedgerEntry) error + // LedgerBalance is the signed sum for an account; an empty studentID means school-level entries. + LedgerBalance(ctx context.Context, account, studentID string) (int64, error) + + NextReceiptNumber(ctx context.Context) (string, error) + InsertReceipt(ctx context.Context, r Receipt) error + + InsertUnmatched(ctx context.Context, u Unmatched) error + GetUnmatchedForUpdate(ctx context.Context, paymentID string) (*Unmatched, error) + MarkUnmatchedResolved(ctx context.Context, paymentID, resolvedBy string, at time.Time) error + ListUnmatched(ctx context.Context, includeResolved bool, after string, limit int) ([]UnmatchedView, error) + + InsertOutbox(ctx context.Context, e OutboxEvent) error + // InsertWebhookEvent returns false when the (provider, event id) pair was already stored. + InsertWebhookEvent(ctx context.Context, ev WebhookEvent) (bool, error) + + Reconciliation(ctx context.Context, from, to time.Time, after string, limit int) ([]ReconRow, error) +} + +// Store runs work in all-or-nothing transactions. +type Store interface { + // InTx runs fn atomically for one school; an error from fn rolls everything back. + InTx(ctx context.Context, schoolID string, fn func(Repo) error) error + // PaymentSchool finds the school that owns a provider transaction; webhooks arrive without a token. + PaymentSchool(ctx context.Context, rail, txnID string) (schoolID string, found bool, err error) + // RecordOrphanEvent stores a webhook whose school cannot be determined. + RecordOrphanEvent(ctx context.Context, ev WebhookEvent) (bool, error) +} diff --git a/services/payments/internal/fees/types.go b/services/payments/internal/fees/types.go new file mode 100644 index 0000000..0af32ec --- /dev/null +++ b/services/payments/internal/fees/types.go @@ -0,0 +1,241 @@ +// Package fees holds the Fees MVP: structures, invoices, payments, the ledger, receipts and reconciliation. +package fees + +import ( + "encoding/json" + "errors" + "time" +) + +// Payment states (SDD P2-14). +const ( + StatusPending = "PENDING" + StatusConfirmed = "CONFIRMED" + StatusReversed = "REVERSED" + StatusFailed = "FAILED" + StatusUnmatched = "UNMATCHED" +) + +// Payment kinds; only INVOICE_LINE is implemented, the rest keep room in the schema. +const ( + KindInvoiceLine = "INVOICE_LINE" + KindEvent = "EVENT" + KindWallet = "WALLET" + KindFundraiser = "FUNDRAISER" +) + +// Invoice states. +const ( + InvoiceIssued = "ISSUED" + InvoicePartial = "PARTIAL" + InvoicePaid = "PAID" +) + +// Ledger entry kinds. A balance is the signed sum: debits are positive, credits negative. +const ( + LedgerDebitInvoice = "DEBIT_INVOICE" + LedgerCreditPayment = "CREDIT_PAYMENT" + LedgerRefund = "REFUND" + LedgerAdjustment = "ADJUSTMENT" +) + +// Ledger accounts. +const ( + AccountReceivable = "STUDENT_RECEIVABLE" + AccountSuspense = "UNMATCHED_SUSPENSE" +) + +// Allocation targets. +const ( + TargetInvoiceLine = "INVOICE_LINE" + TargetStudentCredit = "STUDENT_CREDIT" +) + +// Reasons a payment lands in the unmatched queue. +const ( + ReasonNoMatchingPayment = "NO_MATCHING_PAYMENT" + ReasonAmountMismatch = "AMOUNT_MISMATCH" +) + +// Rails a payment can use. +const ( + RailMpesa = "mpesa" + RailCard = "card" + RailBank = "bank" + RailFake = "fake" +) + +// ErrDuplicate is returned by a Repo when a unique constraint rejects an insert. +var ErrDuplicate = errors.New("duplicate row") + +// FeeItem is one chargeable line of a fee structure. +type FeeItem struct { + ID string `json:"id"` + Name string `json:"name"` + AmountMinor int64 `json:"amountMinor"` + Mandatory bool `json:"mandatory"` +} + +// FeeStructure is the price list for one term and level. +type FeeStructure struct { + ID string `json:"id"` + SchoolID string `json:"-"` + Term string `json:"term"` + Level string `json:"level"` + Name string `json:"name"` + Items []FeeItem `json:"items"` + CreatedAt time.Time `json:"createdAt"` +} + +// InvoiceLine is one amount a student owes. +type InvoiceLine struct { + ID string `json:"id"` + InvoiceID string `json:"invoiceId"` + FeeItemID string `json:"feeItemId,omitempty"` + Description string `json:"description"` + AmountMinor int64 `json:"amountMinor"` +} + +// Invoice is the bill issued to one student for one term. +type Invoice struct { + ID string `json:"id"` + SchoolID string `json:"-"` + StudentID string `json:"studentId"` + Term string `json:"term"` + Status string `json:"status"` + FeeStructureID string `json:"feeStructureId,omitempty"` + IssuedAt time.Time `json:"issuedAt"` + TotalMinor int64 `json:"totalMinor"` + Lines []InvoiceLine `json:"lines"` +} + +// Payment is one attempt or receipt of money. +type Payment struct { + ID string `json:"id"` + SchoolID string `json:"-"` + StudentID string `json:"studentId,omitempty"` + PayerUserID string `json:"payerUserId,omitempty"` + Rail string `json:"rail"` + ProviderTransactionID string `json:"providerTransactionId,omitempty"` + AmountMinor int64 `json:"amountMinor"` + Status string `json:"status"` + FailureCode string `json:"failureCode,omitempty"` + IdempotencyKey string `json:"-"` + Kind string `json:"kind"` + InvoiceLineIDs []string `json:"invoiceLineIds"` + PayerPhone string `json:"-"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// Allocation says how much of a payment went to one target. +type Allocation struct { + ID string + SchoolID string + PaymentID string + TargetType string + TargetID string + AmountMinor int64 + CreatedAt time.Time +} + +// LedgerEntry is one immutable line of the ledger. +type LedgerEntry struct { + ID string + SchoolID string + StudentID string // empty for school-level accounts such as the suspense account + Account string + Kind string + AmountMinor int64 + RefType string + RefID string + CreatedAt time.Time +} + +// Receipt is the proof of one confirmed payment. +type Receipt struct { + ID string `json:"id"` + SchoolID string `json:"-"` + PaymentID string `json:"paymentId"` + Number string `json:"number"` + IssuedAt time.Time `json:"issuedAt"` + DocumentID string `json:"documentId,omitempty"` +} + +// Unmatched is a queue row for money that needs a human decision. +type Unmatched struct { + ID string + SchoolID string + PaymentID string + Reason string + ResolvedBy string + ResolvedAt *time.Time + CreatedAt time.Time +} + +// UnmatchedView is an Unmatched joined with its payment for the finance screen. +type UnmatchedView struct { + PaymentID string `json:"paymentId"` + Reason string `json:"reason"` + Rail string `json:"rail"` + AmountMinor int64 `json:"amountMinor"` + StudentID string `json:"studentId,omitempty"` + ProviderTransactionID string `json:"providerTransactionId,omitempty"` + CreatedAt time.Time `json:"createdAt"` + ResolvedBy string `json:"resolvedBy,omitempty"` + ResolvedAt *time.Time `json:"resolvedAt,omitempty"` +} + +// OutboxEvent is a fact to be relayed to other services. +type OutboxEvent struct { + ID string + SchoolID string + Type string + AggregateID string + Payload json.RawMessage + CreatedAt time.Time +} + +// WebhookEvent is a provider callback stored once per (provider, event id). +type WebhookEvent struct { + Provider string + ExternalEventID string + Payload json.RawMessage +} + +// LineBalance is an invoice line with what has been paid against it. +type LineBalance struct { + InvoiceLine + StudentID string + Term string + InvoiceStatus string + PaidMinor int64 +} + +// Outstanding is what is still owed on the line. +func (l LineBalance) Outstanding() int64 { return l.AmountMinor - l.PaidMinor } + +// PaymentSummary is a payment as shown on a statement. +type PaymentSummary struct { + ID string `json:"id"` + Rail string `json:"rail"` + AmountMinor int64 `json:"amountMinor"` + Status string `json:"status"` + FailureCode string `json:"failureCode,omitempty"` + ReceiptNumber string `json:"receiptNumber,omitempty"` + CreatedAt time.Time `json:"createdAt"` +} + +// ReconRow is one payment line for the finance Excel export. +type ReconRow struct { + PaymentID string `json:"paymentId"` + ReceiptNumber string `json:"receiptNumber,omitempty"` + StudentID string `json:"studentId,omitempty"` + Rail string `json:"rail"` + ProviderTransactionID string `json:"providerTransactionId,omitempty"` + Status string `json:"status"` + AmountMinor int64 `json:"amountMinor"` + AllocatedMinor int64 `json:"allocatedMinor"` + CreditMinor int64 `json:"creditMinor"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/services/payments/internal/provider/fake.go b/services/payments/internal/provider/fake.go new file mode 100644 index 0000000..7c074fb --- /dev/null +++ b/services/payments/internal/provider/fake.go @@ -0,0 +1,69 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +// Phone numbers that make the fake provider fail at initiation, so demos and tests can trigger each path. +const ( + FakePhoneTimeout = "254700000003" + FakePhoneRailDown = "254700000005" +) + +// Fake is a deterministic provider for tests and demos; it never calls a network. +type Fake struct{} + +// Name returns the webhook path segment and rail name. +func (Fake) Name() string { return "fake" } + +// Initiate derives the transaction id from the payment id so repeats are stable. +func (Fake) Initiate(_ context.Context, p Payment) (Ref, error) { + switch p.PayerPhone { + case FakePhoneTimeout: + return Ref{}, &Error{Code: Timeout, Message: "fake provider timed out"} + case FakePhoneRailDown: + return Ref{}, &Error{Code: RailDown, Message: "fake provider is down"} + } + return Ref{TransactionID: "fake_" + p.ID}, nil +} + +type fakeWebhook struct { + ID string `json:"id"` + TransactionID string `json:"transactionId"` + Status string `json:"status"` + FailureCode string `json:"failureCode"` + AmountMinor int64 `json:"amountMinor"` + SchoolID string `json:"schoolId"` + StudentID string `json:"studentId"` +} + +// VerifyWebhook parses the fake JSON shape; a missing status means success. +func (Fake) VerifyWebhook(_ http.Header, body []byte) (Event, error) { + var w fakeWebhook + if err := json.Unmarshal(body, &w); err != nil || w.ID == "" { + return Event{}, fmt.Errorf("%w: fake webhook needs a JSON object with an id", ErrInvalidWebhook) + } + if w.AmountMinor < 0 { + return Event{}, fmt.Errorf("%w: negative amount", ErrInvalidWebhook) + } + ev := Event{ + EventID: w.ID, TransactionID: w.TransactionID, Succeeded: w.Status == "" || w.Status == "success", + AmountMinor: w.AmountMinor, SchoolHint: w.SchoolID, StudentHint: w.StudentID, + } + if ev.TransactionID == "" { + ev.TransactionID = w.ID + } + if !ev.Succeeded { + ev.FailureCode = FailureCode(w.FailureCode) + if ev.FailureCode == "" { + ev.FailureCode = RailDown + } + } + return ev, nil +} + +// Refund always succeeds because no money moves. +func (Fake) Refund(context.Context, Payment) error { return nil } diff --git a/services/payments/internal/provider/mpesa.go b/services/payments/internal/provider/mpesa.go new file mode 100644 index 0000000..811f03c --- /dev/null +++ b/services/payments/internal/provider/mpesa.go @@ -0,0 +1,325 @@ +package provider + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net" + "net/http" + "os" + "strconv" + "strings" + "sync" + "time" +) + +const ( + sandboxBaseURL = "https://sandbox.safaricom.co.ke" + mpesaHTTPTimeout = 15 * time.Second + tokenSafetyMargin = 30 * time.Second // refresh the OAuth token before it really expires + minorPerShilling = 100 + maxDarajaReply = 1 << 20 // 1 MiB cap so a bad reply cannot exhaust memory +) + +// Daraja ResultCode values from the STK callback that map to our failure codes. +const ( + resultSuccess = 0 + resultInsufficientFunds = 1 + resultWrongPin = 2001 + resultUserCancelled = 1032 + resultTimeout = 1037 + resultPromptExpired = 1019 +) + +// MpesaConfig holds the Daraja credentials; BaseURL defaults to the sandbox. +type MpesaConfig struct { + ConsumerKey string + ConsumerSecret string + Shortcode string + Passkey string + CallbackURL string + BaseURL string +} + +// Mpesa is the Daraja STK-push provider (sandbox shape). +type Mpesa struct { + cfg MpesaConfig + client *http.Client + now func() time.Time + + mu sync.Mutex + token string + tokenExp time.Time +} + +// NewMpesaFromEnv returns false when any MPESA_* variable is unset, which disables the rail. +func NewMpesaFromEnv() (*Mpesa, bool) { + cfg := MpesaConfig{ + ConsumerKey: os.Getenv("MPESA_CONSUMER_KEY"), + ConsumerSecret: os.Getenv("MPESA_CONSUMER_SECRET"), + Shortcode: os.Getenv("MPESA_SHORTCODE"), + Passkey: os.Getenv("MPESA_PASSKEY"), + CallbackURL: os.Getenv("MPESA_CALLBACK_URL"), + BaseURL: os.Getenv("MPESA_BASE_URL"), + } + if cfg.ConsumerKey == "" || cfg.ConsumerSecret == "" || cfg.Shortcode == "" || cfg.Passkey == "" || cfg.CallbackURL == "" { + return nil, false + } + return NewMpesa(cfg, &http.Client{Timeout: mpesaHTTPTimeout}), true +} + +// NewMpesa builds the provider; tests pass a client that points at a local server. +func NewMpesa(cfg MpesaConfig, client *http.Client) *Mpesa { + if cfg.BaseURL == "" { + cfg.BaseURL = sandboxBaseURL + } + return &Mpesa{cfg: cfg, client: client, now: time.Now} +} + +// Name returns the webhook path segment and rail name. +func (*Mpesa) Name() string { return "mpesa" } + +// NormalizeKenyanPhone turns 07.., 01.., +254.. or 254.. into 254XXXXXXXXX. +func NormalizeKenyanPhone(raw string) (string, error) { + s := strings.NewReplacer(" ", "", "-", "", "+", "").Replace(raw) + if strings.HasPrefix(s, "0") { + s = "254" + s[1:] + } + if len(s) != 12 || !strings.HasPrefix(s, "254") { + return "", fmt.Errorf("%w: phone must be a Kenyan number", ErrInvalidRequest) + } + for _, c := range s { + if c < '0' || c > '9' { + return "", fmt.Errorf("%w: phone must be digits only", ErrInvalidRequest) + } + } + return s, nil +} + +type stkRequest struct { + BusinessShortCode string + Password string + Timestamp string + TransactionType string + Amount int64 + PartyA string + PartyB string + PhoneNumber string + CallBackURL string + AccountReference string + TransactionDesc string +} + +type stkResponse struct { + MerchantRequestID string `json:"MerchantRequestID"` + CheckoutRequestID string `json:"CheckoutRequestID"` + ResponseCode string `json:"ResponseCode"` + ResponseDescription string `json:"ResponseDescription"` + ErrorCode string `json:"errorCode"` + ErrorMessage string `json:"errorMessage"` +} + +// Initiate sends an STK push and returns the CheckoutRequestID as the transaction id. +func (m *Mpesa) Initiate(ctx context.Context, p Payment) (Ref, error) { + phone, err := NormalizeKenyanPhone(p.PayerPhone) + if err != nil { + return Ref{}, err + } + if p.AmountMinor <= 0 || p.AmountMinor%minorPerShilling != 0 { + return Ref{}, fmt.Errorf("%w: M-Pesa takes whole shillings", ErrInvalidRequest) + } + token, err := m.accessToken(ctx) + if err != nil { + return Ref{}, err + } + stamp := m.now().UTC().Format("20060102150405") + body, err := json.Marshal(stkRequest{ + BusinessShortCode: m.cfg.Shortcode, + Password: base64.StdEncoding.EncodeToString([]byte(m.cfg.Shortcode + m.cfg.Passkey + stamp)), + Timestamp: stamp, + TransactionType: "CustomerPayBillOnline", + Amount: p.AmountMinor / minorPerShilling, + PartyA: phone, + PartyB: m.cfg.Shortcode, + PhoneNumber: phone, + CallBackURL: m.cfg.CallbackURL, + AccountReference: p.Reference, + TransactionDesc: "School fees", + }) + if err != nil { + return Ref{}, fmt.Errorf("encode stk request: %w", err) + } + raw, status, err := m.do(ctx, http.MethodPost, "/mpesa/stkpush/v1/processrequest", bytes.NewReader(body), "Bearer "+token) + if err != nil { + return Ref{}, err + } + return parseSTKResponse(raw, status) +} + +func parseSTKResponse(raw []byte, status int) (Ref, error) { + if status >= http.StatusInternalServerError { + return Ref{}, &Error{Code: RailDown, Message: "daraja is unavailable"} + } + var resp stkResponse + if err := json.Unmarshal(raw, &resp); err != nil { + return Ref{}, &Error{Code: RailDown, Message: fmt.Sprintf("unreadable daraja reply (http %d)", status)} + } + if status != http.StatusOK || resp.ResponseCode != "0" || resp.CheckoutRequestID == "" { + return Ref{}, &Error{Code: RailDown, Message: "daraja rejected the request: " + firstNonEmpty(resp.ErrorMessage, resp.ResponseDescription)} + } + return Ref{TransactionID: resp.CheckoutRequestID}, nil +} + +func firstNonEmpty(values ...string) string { + for _, v := range values { + if v != "" { + return v + } + } + return "no detail" +} + +func (m *Mpesa) accessToken(ctx context.Context) (string, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.token != "" && m.now().Before(m.tokenExp) { + return m.token, nil + } + basic := base64.StdEncoding.EncodeToString([]byte(m.cfg.ConsumerKey + ":" + m.cfg.ConsumerSecret)) + raw, status, err := m.do(ctx, http.MethodGet, "/oauth/v1/generate?grant_type=client_credentials", nil, "Basic "+basic) + if err != nil { + return "", err + } + token, ttl, err := parseOAuthResponse(raw, status) + if err != nil { + return "", err + } + m.token = token + m.tokenExp = m.now().Add(ttl - tokenSafetyMargin) + return m.token, nil +} + +func parseOAuthResponse(raw []byte, status int) (string, time.Duration, error) { + var resp struct { + AccessToken string `json:"access_token"` + ExpiresIn string `json:"expires_in"` + } + if status != http.StatusOK || json.Unmarshal(raw, &resp) != nil || resp.AccessToken == "" { + return "", 0, &Error{Code: RailDown, Message: fmt.Sprintf("daraja oauth failed (http %d)", status)} + } + secs, err := strconv.Atoi(resp.ExpiresIn) + if err != nil || secs <= 0 { + secs = 3599 // Daraja documents one hour; use it when the field is missing + } + return resp.AccessToken, time.Duration(secs) * time.Second, nil +} + +// do runs one request; timeouts and network failures become TIMEOUT and RAIL_DOWN. +func (m *Mpesa) do(ctx context.Context, method, path string, body io.Reader, auth string) ([]byte, int, error) { + req, err := http.NewRequestWithContext(ctx, method, strings.TrimRight(m.cfg.BaseURL, "/")+path, body) + if err != nil { + return nil, 0, fmt.Errorf("build daraja request: %w", err) + } + req.Header.Set("Authorization", auth) + req.Header.Set("Content-Type", "application/json") + resp, err := m.client.Do(req) + if err != nil { + var netErr net.Error + if errors.Is(err, context.DeadlineExceeded) || (errors.As(err, &netErr) && netErr.Timeout()) { + return nil, 0, &Error{Code: Timeout, Message: "daraja did not answer in time"} + } + return nil, 0, &Error{Code: RailDown, Message: "daraja is unreachable"} + } + defer resp.Body.Close() + raw, err := io.ReadAll(io.LimitReader(resp.Body, maxDarajaReply)) + if err != nil { + return nil, 0, &Error{Code: RailDown, Message: "daraja reply was cut off"} + } + return raw, resp.StatusCode, nil +} + +type stkCallback struct { + Body struct { + StkCallback struct { + MerchantRequestID string `json:"MerchantRequestID"` + CheckoutRequestID string `json:"CheckoutRequestID"` + ResultCode int `json:"ResultCode"` + ResultDesc string `json:"ResultDesc"` + CallbackMetadata struct { + Item []struct { + Name string `json:"Name"` + Value json.RawMessage `json:"Value"` + } `json:"Item"` + } `json:"CallbackMetadata"` + } `json:"stkCallback"` + } `json:"Body"` +} + +// VerifyWebhook parses an STK callback. Daraja does not sign callbacks, so the service only +// trusts it for a payment it started, and the proxy should allowlist Safaricom IPs. +func (*Mpesa) VerifyWebhook(_ http.Header, body []byte) (Event, error) { + var cb stkCallback + if err := json.Unmarshal(body, &cb); err != nil { + return Event{}, fmt.Errorf("%w: not an stk callback", ErrInvalidWebhook) + } + c := cb.Body.StkCallback + if c.CheckoutRequestID == "" { + return Event{}, fmt.Errorf("%w: missing CheckoutRequestID", ErrInvalidWebhook) + } + ev := Event{EventID: c.CheckoutRequestID, TransactionID: c.CheckoutRequestID, Succeeded: c.ResultCode == resultSuccess} + if !ev.Succeeded { + ev.FailureCode = failureForResultCode(c.ResultCode) + return ev, nil + } + for _, item := range c.CallbackMetadata.Item { + if item.Name != "Amount" { + continue + } + minor, err := shillingsToMinor(item.Value) + if err != nil { + return Event{}, fmt.Errorf("%w: bad Amount", ErrInvalidWebhook) + } + ev.AmountMinor = minor + } + return ev, nil +} + +func failureForResultCode(code int) FailureCode { + switch code { + case resultInsufficientFunds: + return InsufficientFunds + case resultWrongPin: + return WrongPin + case resultUserCancelled: + return UserCancelled + case resultTimeout, resultPromptExpired: + return Timeout + default: + return RailDown + } +} + +// shillingsToMinor reads a JSON number or string such as 1 or "1.50". +func shillingsToMinor(raw json.RawMessage) (int64, error) { + var n json.Number + if err := json.Unmarshal(raw, &n); err != nil { + var s string + if err2 := json.Unmarshal(raw, &s); err2 != nil { + return 0, err + } + n = json.Number(s) + } + f, err := strconv.ParseFloat(n.String(), 64) + if err != nil || f < 0 || math.IsNaN(f) || math.IsInf(f, 0) { + return 0, errors.New("bad amount") + } + return int64(math.Round(f * minorPerShilling)), nil +} + +// Refund is not offered: Daraja reversals need the Reversal API and initiator credentials. +func (*Mpesa) Refund(context.Context, Payment) error { return ErrNotSupported } diff --git a/services/payments/internal/provider/provider.go b/services/payments/internal/provider/provider.go new file mode 100644 index 0000000..8ea2aae --- /dev/null +++ b/services/payments/internal/provider/provider.go @@ -0,0 +1,70 @@ +// Package provider hides each payment rail (M-Pesa, fake, later card and bank) behind one interface. +package provider + +import ( + "context" + "errors" + "net/http" +) + +// FailureCode is the rail-neutral reason a payment attempt failed. +type FailureCode string + +const ( + InsufficientFunds FailureCode = "INSUFFICIENT_FUNDS" + WrongPin FailureCode = "WRONG_PIN" + Timeout FailureCode = "TIMEOUT" + UserCancelled FailureCode = "USER_CANCELLED" + RailDown FailureCode = "RAIL_DOWN" +) + +// Error is a provider failure with one of the stable codes. +type Error struct { + Code FailureCode + Message string +} + +func (e *Error) Error() string { return string(e.Code) + ": " + e.Message } + +var ( + // ErrInvalidWebhook means the body is not a valid callback for this provider. + ErrInvalidWebhook = errors.New("invalid webhook") + // ErrBadSignature means the callback failed authenticity checks. + ErrBadSignature = errors.New("bad webhook signature") + // ErrInvalidRequest means the payment cannot be sent to this rail as given. + ErrInvalidRequest = errors.New("invalid payment request") + // ErrNotSupported means the rail does not offer this operation yet. + ErrNotSupported = errors.New("operation not supported by provider") +) + +// Payment is the part of a payment a rail needs to start a charge. +type Payment struct { + ID string + AmountMinor int64 + PayerPhone string + Reference string +} + +// Ref identifies the charge on the provider side. +type Ref struct { + TransactionID string +} + +// Event is a verified provider callback in rail-neutral form. +type Event struct { + EventID string + TransactionID string + Succeeded bool + FailureCode FailureCode + AmountMinor int64 // zero when the callback does not state an amount + SchoolHint string + StudentHint string +} + +// PaymentProvider is the only way the service talks to a payment rail. +type PaymentProvider interface { + Name() string + Initiate(ctx context.Context, p Payment) (Ref, error) + VerifyWebhook(headers http.Header, body []byte) (Event, error) + Refund(ctx context.Context, p Payment) error +} diff --git a/services/payments/internal/provider/provider_test.go b/services/payments/internal/provider/provider_test.go new file mode 100644 index 0000000..6f13599 --- /dev/null +++ b/services/payments/internal/provider/provider_test.go @@ -0,0 +1,196 @@ +package provider + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +const successCallback = `{"Body":{"stkCallback":{"MerchantRequestID":"29115-34620561-1","CheckoutRequestID":"ws_CO_191220191020363925","ResultCode":0,"ResultDesc":"The service request is processed successfully.","CallbackMetadata":{"Item":[{"Name":"Amount","Value":1.00},{"Name":"MpesaReceiptNumber","Value":"NLJ7RT61SV"},{"Name":"TransactionDate","Value":20191219102115},{"Name":"PhoneNumber","Value":254708374149}]}}}}` + +func failureCallback(code int) string { + return `{"Body":{"stkCallback":{"MerchantRequestID":"m","CheckoutRequestID":"ws_CO_1","ResultCode":` + itoa(code) + `,"ResultDesc":"x"}}}` +} + +func itoa(n int) string { + b, _ := json.Marshal(n) + return string(b) +} + +func TestDarajaSuccessCallbackIsParsed(t *testing.T) { + ev, err := (&Mpesa{}).VerifyWebhook(nil, []byte(successCallback)) + if err != nil { + t.Fatal(err) + } + if !ev.Succeeded || ev.TransactionID != "ws_CO_191220191020363925" || ev.EventID != ev.TransactionID || ev.AmountMinor != 100 { + t.Fatalf("event %+v", ev) + } +} + +func TestDarajaFailureCodesMapToDistinctCodes(t *testing.T) { + cases := map[int]FailureCode{ + 1: InsufficientFunds, 2001: WrongPin, 1032: UserCancelled, 1037: Timeout, 1019: Timeout, 9999: RailDown, + } + for code, want := range cases { + ev, err := (&Mpesa{}).VerifyWebhook(nil, []byte(failureCallback(code))) + if err != nil || ev.Succeeded || ev.FailureCode != want { + t.Fatalf("code %d: got %+v err %v, want %s", code, ev, err, want) + } + } +} + +func TestDarajaRejectsMalformedCallbacks(t *testing.T) { + for name, body := range map[string]string{ + "not json": `nope`, + "no checkout id": `{"Body":{"stkCallback":{"ResultCode":0}}}`, + "empty object": `{}`, + "bad amount type": `{"Body":{"stkCallback":{"CheckoutRequestID":"c","ResultCode":0,"CallbackMetadata":{"Item":[{"Name":"Amount","Value":{"x":1}}]}}}}`, + } { + if _, err := (&Mpesa{}).VerifyWebhook(nil, []byte(body)); !errors.Is(err, ErrInvalidWebhook) { + t.Fatalf("%s: err %v", name, err) + } + } +} + +func TestAmountAsStringWithDecimals(t *testing.T) { + got, err := shillingsToMinor(json.RawMessage(`"1500.50"`)) + if err != nil || got != 150050 { + t.Fatalf("got %d %v", got, err) + } +} + +func TestNormalizeKenyanPhone(t *testing.T) { + for in, want := range map[string]string{ + "0712345678": "254712345678", "+254 712 345 678": "254712345678", "254112345678": "254112345678", + } { + if got, err := NormalizeKenyanPhone(in); err != nil || got != want { + t.Fatalf("%s: got %s %v", in, got, err) + } + } + for _, bad := range []string{"", "12345", "0712abc678", "255712345678"} { + if _, err := NormalizeKenyanPhone(bad); !errors.Is(err, ErrInvalidRequest) { + t.Fatalf("%q accepted", bad) + } + } +} + +func darajaServer(t *testing.T, stkStatus int, stkBody string, seen *stkRequest) *Mpesa { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/oauth/v1/generate": + if !strings.HasPrefix(r.Header.Get("Authorization"), "Basic ") { + w.WriteHeader(http.StatusUnauthorized) + return + } + _, _ = w.Write([]byte(`{"access_token":"tok","expires_in":"3599"}`)) + case "/mpesa/stkpush/v1/processrequest": + if r.Header.Get("Authorization") != "Bearer tok" { + w.WriteHeader(http.StatusUnauthorized) + return + } + _ = json.NewDecoder(r.Body).Decode(seen) + w.WriteHeader(stkStatus) + _, _ = w.Write([]byte(stkBody)) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + t.Cleanup(srv.Close) + return NewMpesa(MpesaConfig{ConsumerKey: "k", ConsumerSecret: "s", Shortcode: "174379", Passkey: "pk", CallbackURL: "https://x/cb", BaseURL: srv.URL}, srv.Client()) +} + +func TestMpesaInitiateSendsStkPush(t *testing.T) { + var seen stkRequest + m := darajaServer(t, 200, `{"MerchantRequestID":"m","CheckoutRequestID":"ws_CO_9","ResponseCode":"0","ResponseDescription":"ok"}`, &seen) + ref, err := m.Initiate(context.Background(), Payment{ID: "p1", AmountMinor: 250000, PayerPhone: "0712345678", Reference: "p1"}) + if err != nil || ref.TransactionID != "ws_CO_9" { + t.Fatalf("ref %+v err %v", ref, err) + } + if seen.Amount != 2500 || seen.PhoneNumber != "254712345678" || seen.BusinessShortCode != "174379" || seen.CallBackURL != "https://x/cb" || seen.Password == "" { + t.Fatalf("request %+v", seen) + } +} + +func TestMpesaInitiateMapsRailFailures(t *testing.T) { + var seen stkRequest + down := darajaServer(t, 503, `{}`, &seen) + _, err := down.Initiate(context.Background(), Payment{ID: "p", AmountMinor: 100, PayerPhone: "0712345678"}) + var pe *Error + if !errors.As(err, &pe) || pe.Code != RailDown { + t.Fatalf("err %v", err) + } + rejected := darajaServer(t, 400, `{"errorCode":"400.002.02","errorMessage":"Bad Request"}`, &seen) + _, err = rejected.Initiate(context.Background(), Payment{ID: "p", AmountMinor: 100, PayerPhone: "0712345678"}) + if !errors.As(err, &pe) || pe.Code != RailDown { + t.Fatalf("err %v", err) + } +} + +func TestMpesaInitiateTimeoutIsTimeout(t *testing.T) { + m := darajaServer(t, 200, `{}`, &stkRequest{}) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + _, err := m.Initiate(ctx, Payment{ID: "p", AmountMinor: 100, PayerPhone: "0712345678"}) + var pe *Error + if !errors.As(err, &pe) || (pe.Code != RailDown && pe.Code != Timeout) { + t.Fatalf("err %v", err) + } +} + +func TestMpesaRejectsFractionalShillings(t *testing.T) { + m := darajaServer(t, 200, `{}`, &stkRequest{}) + _, err := m.Initiate(context.Background(), Payment{ID: "p", AmountMinor: 150, PayerPhone: "0712345678"}) + if !errors.Is(err, ErrInvalidRequest) { + t.Fatalf("err %v", err) + } +} + +func TestMpesaDisabledWithoutEnv(t *testing.T) { + for _, k := range []string{"MPESA_CONSUMER_KEY", "MPESA_CONSUMER_SECRET", "MPESA_SHORTCODE", "MPESA_PASSKEY", "MPESA_CALLBACK_URL"} { + t.Setenv(k, "") + } + if _, ok := NewMpesaFromEnv(); ok { + t.Fatal("mpesa enabled with no config") + } + t.Setenv("MPESA_CONSUMER_KEY", "k") + t.Setenv("MPESA_CONSUMER_SECRET", "s") + t.Setenv("MPESA_SHORTCODE", "1") + t.Setenv("MPESA_PASSKEY", "p") + t.Setenv("MPESA_CALLBACK_URL", "https://x") + if _, ok := NewMpesaFromEnv(); !ok { + t.Fatal("mpesa disabled with full config") + } +} + +func TestMpesaRefundNotSupported(t *testing.T) { + if err := (&Mpesa{}).Refund(context.Background(), Payment{}); !errors.Is(err, ErrNotSupported) { + t.Fatalf("err %v", err) + } +} + +func TestFakeProvider(t *testing.T) { + f := Fake{} + ref, err := f.Initiate(context.Background(), Payment{ID: "p1"}) + if err != nil || ref.TransactionID != "fake_p1" { + t.Fatalf("%+v %v", ref, err) + } + for phone, want := range map[string]FailureCode{FakePhoneTimeout: Timeout, FakePhoneRailDown: RailDown} { + _, err := f.Initiate(context.Background(), Payment{ID: "p", PayerPhone: phone}) + var pe *Error + if !errors.As(err, &pe) || pe.Code != want { + t.Fatalf("%s: %v", phone, err) + } + } + ev, err := f.VerifyWebhook(nil, []byte(`{"id":"e1","transactionId":"fake_p1","status":"failed","failureCode":"WRONG_PIN"}`)) + if err != nil || ev.Succeeded || ev.FailureCode != WrongPin || ev.TransactionID != "fake_p1" { + t.Fatalf("%+v %v", ev, err) + } + if _, err := f.VerifyWebhook(nil, []byte(`{"amountMinor":5}`)); !errors.Is(err, ErrInvalidWebhook) { + t.Fatalf("err %v", err) + } +} diff --git a/services/payments/internal/webhooks/handler.go b/services/payments/internal/webhooks/handler.go new file mode 100644 index 0000000..a12343c --- /dev/null +++ b/services/payments/internal/webhooks/handler.go @@ -0,0 +1,37 @@ +package webhooks + +import ( + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" + kit "github.com/stackable/go-kit" +) + +// Handler exposes the webhook routes. +type Handler struct { + svc *Service +} + +// NewHandler builds a Handler over a Service. +func NewHandler(svc *Service) *Handler { + return &Handler{svc: svc} +} + +// Routes mounts POST /v1/webhooks/{provider}. +func (h *Handler) Routes(r chi.Router) { + r.Post("/v1/webhooks/{provider}", kit.Handle(h.receive)) +} + +func (h *Handler) receive(w http.ResponseWriter, r *http.Request) error { + var payload json.RawMessage + if err := kit.DecodeJSON(w, r, &payload); err != nil { + return err + } + duplicate, err := h.svc.Receive(r.Context(), chi.URLParam(r, "provider"), payload) + if err != nil { + return err + } + kit.WriteJSON(w, http.StatusOK, map[string]any{"received": true, "duplicate": duplicate}) + return nil +} diff --git a/services/payments/internal/webhooks/pg_store.go b/services/payments/internal/webhooks/pg_store.go new file mode 100644 index 0000000..5d2ec7b --- /dev/null +++ b/services/payments/internal/webhooks/pg_store.go @@ -0,0 +1,33 @@ +package webhooks + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" +) + +// Hand-written to match queries/webhook_events.sql until sqlc is installed. +const insertWebhookEventSQL = ` +insert into payments.webhook_events (provider, external_event_id, payload) +values ($1, $2, $3) +on conflict (provider, external_event_id) do nothing` + +// PgStore is the Postgres-backed Store. +type PgStore struct { + pool *pgxpool.Pool +} + +// NewPgStore wraps a pool. +func NewPgStore(pool *pgxpool.Pool) *PgStore { + return &PgStore{pool: pool} +} + +// Insert relies on the unique constraint, so two concurrent repeats still store one row. +func (s *PgStore) Insert(ctx context.Context, ev Event) (bool, error) { + tag, err := s.pool.Exec(ctx, insertWebhookEventSQL, ev.Provider, ev.ExternalEventID, []byte(ev.Payload)) + if err != nil { + return false, fmt.Errorf("insert webhook event: %w", err) + } + return tag.RowsAffected() == 1, nil +} diff --git a/services/payments/internal/webhooks/service.go b/services/payments/internal/webhooks/service.go new file mode 100644 index 0000000..31d33e8 --- /dev/null +++ b/services/payments/internal/webhooks/service.go @@ -0,0 +1,72 @@ +package webhooks + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + + kit "github.com/stackable/go-kit" +) + +// extractors read the provider's own event id from its payload. +var extractors = map[string]func(map[string]any) string{ + "fake": func(p map[string]any) string { return scalarString(p["id"]) }, + "paystack": func(p map[string]any) string { return scalarString(dig(p, "data", "id")) }, + "mpesa": func(p map[string]any) string { return scalarString(dig(p, "Body", "stkCallback", "CheckoutRequestID")) }, +} + +func dig(m map[string]any, path ...string) any { + var cur any = m + for _, key := range path { + next, ok := cur.(map[string]any) + if !ok { + return nil + } + cur = next[key] + } + return cur +} + +func scalarString(v any) string { + switch t := v.(type) { + case string: + return t + case float64: + return strconv.FormatFloat(t, 'f', -1, 64) + default: + return "" + } +} + +// Service records webhook events. It knows nothing about HTTP. +type Service struct { + store Store +} + +// NewService builds a Service over a Store. +func NewService(store Store) *Service { + return &Service{store: store} +} + +// Receive stores a webhook once and reports whether it was a repeat. +// A repeat changes nothing, so providers can retry safely. +func (s *Service) Receive(ctx context.Context, provider string, payload json.RawMessage) (duplicate bool, err error) { + extract, ok := extractors[provider] + if !ok { + return false, kit.NotFound("Unknown payment provider.") + } + var parsed map[string]any + if err := json.Unmarshal(payload, &parsed); err != nil { + return false, kit.Validation("Webhook body must be a JSON object.", nil) + } + id := extract(parsed) + if id == "" { + return false, kit.Validation("Webhook has no event id.", map[string]string{"external_event_id": "missing"}) + } + inserted, err := s.store.Insert(ctx, Event{Provider: provider, ExternalEventID: id, Payload: payload}) + if err != nil { + return false, fmt.Errorf("store webhook: %w", err) + } + return !inserted, nil +} diff --git a/services/payments/internal/webhooks/store.go b/services/payments/internal/webhooks/store.go new file mode 100644 index 0000000..d9cf70f --- /dev/null +++ b/services/payments/internal/webhooks/store.go @@ -0,0 +1,50 @@ +package webhooks + +import ( + "context" + "encoding/json" + "sync" +) + +// Event is one provider webhook as received. +type Event struct { + Provider string + ExternalEventID string + Payload json.RawMessage +} + +// Store persists webhook events once per (provider, external_event_id). +type Store interface { + // Insert returns true when the event is new and false when it was already stored. + Insert(ctx context.Context, ev Event) (bool, error) +} + +// MemoryStore is an in-process Store for tests and local runs without a database. +type MemoryStore struct { + mu sync.Mutex + events map[string]Event +} + +// NewMemoryStore returns an empty MemoryStore. +func NewMemoryStore() *MemoryStore { + return &MemoryStore{events: map[string]Event{}} +} + +// Insert stores ev unless the same (provider, id) pair exists. +func (m *MemoryStore) Insert(_ context.Context, ev Event) (bool, error) { + m.mu.Lock() + defer m.mu.Unlock() + key := ev.Provider + "\x00" + ev.ExternalEventID + if _, exists := m.events[key]; exists { + return false, nil + } + m.events[key] = ev + return true, nil +} + +// Count returns how many events are stored. +func (m *MemoryStore) Count() int { + m.mu.Lock() + defer m.mu.Unlock() + return len(m.events) +} diff --git a/services/payments/internal/webhooks/webhooks_test.go b/services/payments/internal/webhooks/webhooks_test.go new file mode 100644 index 0000000..765e076 --- /dev/null +++ b/services/payments/internal/webhooks/webhooks_test.go @@ -0,0 +1,91 @@ +package webhooks + +import ( + "encoding/json" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + + kit "github.com/stackable/go-kit" +) + +func newTestServer(store Store) http.Handler { + r := kit.NewRouter(kit.Config{Name: "payments", Logger: slog.New(slog.NewJSONHandler(io.Discard, nil))}) + NewHandler(NewService(store)).Routes(r) + return r +} + +func post(h http.Handler, path, body string) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + return rec +} + +func TestWebhookIsStoredOnce(t *testing.T) { + store := NewMemoryStore() + h := newTestServer(store) + body := `{"id":"evt_1","amount":5000}` + + first := post(h, "/v1/webhooks/fake", body) + second := post(h, "/v1/webhooks/fake", body) + + if first.Code != 200 || second.Code != 200 { + t.Fatalf("statuses %d %d", first.Code, second.Code) + } + var a, b map[string]any + _ = json.Unmarshal(first.Body.Bytes(), &a) + _ = json.Unmarshal(second.Body.Bytes(), &b) + if a["duplicate"] != false || b["duplicate"] != true { + t.Fatalf("duplicate flags: %v %v", a, b) + } + if store.Count() != 1 { + t.Fatalf("store holds %d rows, want 1", store.Count()) + } +} + +func TestSameEventIDFromDifferentProvidersAreSeparate(t *testing.T) { + store := NewMemoryStore() + h := newTestServer(store) + post(h, "/v1/webhooks/fake", `{"id":"1"}`) + post(h, "/v1/webhooks/paystack", `{"data":{"id":1}}`) + if store.Count() != 2 { + t.Fatalf("store holds %d rows, want 2", store.Count()) + } +} + +func TestMpesaEventIDIsCheckoutRequestID(t *testing.T) { + store := NewMemoryStore() + h := newTestServer(store) + body := `{"Body":{"stkCallback":{"CheckoutRequestID":"ws_CO_1","ResultCode":0}}}` + post(h, "/v1/webhooks/mpesa", body) + if rec := post(h, "/v1/webhooks/mpesa", body); rec.Code != 200 || store.Count() != 1 { + t.Fatalf("got %d, rows %d", rec.Code, store.Count()) + } +} + +func TestBadWebhooksUseErrorEnvelope(t *testing.T) { + cases := []struct { + name, path, body, code string + status int + }{ + {"unknown provider", "/v1/webhooks/nope", `{"id":"1"}`, "RESOURCE_NOT_FOUND", 404}, + {"not json", "/v1/webhooks/fake", `not json`, "VALIDATION_FAILED", 400}, + {"not an object", "/v1/webhooks/fake", `[1,2]`, "VALIDATION_FAILED", 400}, + {"missing event id", "/v1/webhooks/fake", `{"amount":1}`, "VALIDATION_FAILED", 400}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + store := NewMemoryStore() + rec := post(newTestServer(store), c.path, c.body) + var body map[string]any + _ = json.Unmarshal(rec.Body.Bytes(), &body) + if rec.Code != c.status || body["code"] != c.code || store.Count() != 0 { + t.Fatalf("got %d %v rows=%d", rec.Code, body, store.Count()) + } + }) + } +} diff --git a/services/payments/migrations/00001_create_webhook_events.sql b/services/payments/migrations/00001_create_webhook_events.sql new file mode 100644 index 0000000..a4363de --- /dev/null +++ b/services/payments/migrations/00001_create_webhook_events.sql @@ -0,0 +1,16 @@ +-- +goose Up +create schema if not exists payments; + +create table payments.webhook_events ( + id uuid primary key default gen_random_uuid(), + provider text not null, + external_event_id text not null, + payload jsonb not null, + received_at timestamptz not null default now(), + processed_at timestamptz, + unique (provider, external_event_id) +); + +-- +goose Down +drop table payments.webhook_events; +drop schema payments; diff --git a/services/payments/queries/webhook_events.sql b/services/payments/queries/webhook_events.sql new file mode 100644 index 0000000..a7df58e --- /dev/null +++ b/services/payments/queries/webhook_events.sql @@ -0,0 +1,4 @@ +-- name: InsertWebhookEvent :execrows +insert into payments.webhook_events (provider, external_event_id, payload) +values ($1, $2, $3) +on conflict (provider, external_event_id) do nothing; diff --git a/services/payments/sqlc.yaml b/services/payments/sqlc.yaml new file mode 100644 index 0000000..f93bc1e --- /dev/null +++ b/services/payments/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: postgresql + schema: migrations + queries: queries + gen: + go: + package: db + out: internal/db + sql_package: pgx/v5 diff --git a/stackable-app/AGENTS.md b/stackable-app/AGENTS.md new file mode 100644 index 0000000..643577d --- /dev/null +++ b/stackable-app/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/stackable-app/CLAUDE.md b/stackable-app/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/stackable-app/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/stackable-app/app/(developer)/error.tsx b/stackable-app/app/(developer)/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/(developer)/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/(developer)/loading.tsx b/stackable-app/app/(developer)/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/(developer)/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/(parent)/error.tsx b/stackable-app/app/(parent)/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/(parent)/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/(parent)/family/fees/page.tsx b/stackable-app/app/(parent)/family/fees/page.tsx index 416ef71..d67d955 100644 --- a/stackable-app/app/(parent)/family/fees/page.tsx +++ b/stackable-app/app/(parent)/family/fees/page.tsx @@ -1,17 +1,11 @@ -export default function FeesPage() { +import { ComingSoon } from "@/components/ui"; + +export default function SchoolFeesPage() { return ( -
-

School Fees

-
-
-
🏗️
-

Coming Soon

-

- This section is under construction. School fees information will be available - here soon. -

-
-
-
+ ); } diff --git a/stackable-app/app/(parent)/family/inbox/page.tsx b/stackable-app/app/(parent)/family/inbox/page.tsx index 435fa5c..85dd06c 100644 --- a/stackable-app/app/(parent)/family/inbox/page.tsx +++ b/stackable-app/app/(parent)/family/inbox/page.tsx @@ -1,17 +1,11 @@ +import { ComingSoon } from "@/components/ui"; + export default function InboxPage() { return ( -
-

Inbox

-
-
-
🏗️
-

Coming Soon

-

- This section is under construction. Your messages and inbox will be available - here soon. -

-
-
-
+ ); } diff --git a/stackable-app/app/(parent)/family/notices/page.tsx b/stackable-app/app/(parent)/family/notices/page.tsx index 422784e..82ec085 100644 --- a/stackable-app/app/(parent)/family/notices/page.tsx +++ b/stackable-app/app/(parent)/family/notices/page.tsx @@ -1,17 +1,11 @@ +import { ComingSoon } from "@/components/ui"; + export default function NoticesPage() { return ( -
-

Notices

-
-
-
🏗️
-

Coming Soon

-

- This section is under construction. School notices and announcements will be - available here soon. -

-
-
-
+ ); } diff --git a/stackable-app/app/(parent)/loading.tsx b/stackable-app/app/(parent)/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/(parent)/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/(principal)/error.tsx b/stackable-app/app/(principal)/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/(principal)/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/(principal)/loading.tsx b/stackable-app/app/(principal)/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/(principal)/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/(staff)/error.tsx b/stackable-app/app/(staff)/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/(staff)/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/(staff)/layout.tsx b/stackable-app/app/(staff)/layout.tsx new file mode 100644 index 0000000..fe845e6 --- /dev/null +++ b/stackable-app/app/(staff)/layout.tsx @@ -0,0 +1,8 @@ +import { requireRole } from "@/lib/api/guard"; +import { DashboardShell } from "@/components/dashboard/DashboardShell"; + +// Staff portal (/staff/*) for finance, secretary and driver. Menu: lib/nav.ts -> NAV.staff. +export default async function StaffLayout({ children }: { children: React.ReactNode }) { + await requireRole("staff"); + return {children}; +} diff --git a/stackable-app/app/(staff)/loading.tsx b/stackable-app/app/(staff)/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/(staff)/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/(staff)/staff/[...slug]/page.tsx b/stackable-app/app/(staff)/staff/[...slug]/page.tsx new file mode 100644 index 0000000..0df074a --- /dev/null +++ b/stackable-app/app/(staff)/staff/[...slug]/page.tsx @@ -0,0 +1,8 @@ +import { ComingSoon } from "@/components/ui"; +import { prettifySegment } from "@/lib/nav"; + +// Every unbuilt /staff page shows the same placeholder; remove a route's tag in lib/nav.ts when it is built. +export default async function StaffComingSoonPage({ params }: { params: Promise<{ slug: string[] }> }) { + const { slug } = await params; + return ; +} diff --git a/stackable-app/app/(staff)/staff/page.tsx b/stackable-app/app/(staff)/staff/page.tsx new file mode 100644 index 0000000..cd5b4a6 --- /dev/null +++ b/stackable-app/app/(staff)/staff/page.tsx @@ -0,0 +1,11 @@ +import { ComingSoon } from "@/components/ui"; + +export default function StaffHomePage() { + return ( + + ); +} diff --git a/stackable-app/app/(staff)/staff/settings/page.tsx b/stackable-app/app/(staff)/staff/settings/page.tsx new file mode 100644 index 0000000..9c0da07 --- /dev/null +++ b/stackable-app/app/(staff)/staff/settings/page.tsx @@ -0,0 +1,10 @@ +import TwoFactorSetup from "@/components/auth/TwoFactorSetup"; + +export default function StaffSettingsPage() { + return ( +
+

Settings

+ +
+ ); +} diff --git a/stackable-app/app/(student)/error.tsx b/stackable-app/app/(student)/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/(student)/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/(student)/learn/homework/page.tsx b/stackable-app/app/(student)/learn/homework/page.tsx index a0c0007..2af1abd 100644 --- a/stackable-app/app/(student)/learn/homework/page.tsx +++ b/stackable-app/app/(student)/learn/homework/page.tsx @@ -1,20 +1,11 @@ -import { NotebookPen } from "lucide-react"; +import { ComingSoon } from "@/components/ui"; export default function HomeworkPage() { return ( -
-

Homework

-
-
- -
-
-

Coming Soon

-

- Homework assignments and submissions will be available here shortly. -

-
-
-
+ ); } diff --git a/stackable-app/app/(student)/learn/subjects/page.tsx b/stackable-app/app/(student)/learn/subjects/page.tsx index ee39f6a..7fc2e18 100644 --- a/stackable-app/app/(student)/learn/subjects/page.tsx +++ b/stackable-app/app/(student)/learn/subjects/page.tsx @@ -1,20 +1,11 @@ -import { BookOpenText } from "lucide-react"; +import { ComingSoon } from "@/components/ui"; -export default function SubjectsPage() { +export default function MySubjectsPage() { return ( -
-

My Subjects

-
-
- -
-
-

Coming Soon

-

- Your subject details and resources will be available here shortly. -

-
-
-
+ ); } diff --git a/stackable-app/app/(student)/loading.tsx b/stackable-app/app/(student)/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/(student)/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/(teacher)/error.tsx b/stackable-app/app/(teacher)/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/(teacher)/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/(teacher)/loading.tsx b/stackable-app/app/(teacher)/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/(teacher)/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/(teacher)/teach/classes/page.tsx b/stackable-app/app/(teacher)/teach/classes/page.tsx index 1d06e9c..bfd0b01 100644 --- a/stackable-app/app/(teacher)/teach/classes/page.tsx +++ b/stackable-app/app/(teacher)/teach/classes/page.tsx @@ -1,13 +1,11 @@ +import { ComingSoon } from "@/components/ui"; + export default function MyClassesPage() { return ( -
-

My Classes

-
-

Coming Soon

-

- Detailed class management will be available here. -

-
-
+ ); } diff --git a/stackable-app/app/(teacher)/teach/subjects/page.tsx b/stackable-app/app/(teacher)/teach/subjects/page.tsx index 1bde246..6bc8720 100644 --- a/stackable-app/app/(teacher)/teach/subjects/page.tsx +++ b/stackable-app/app/(teacher)/teach/subjects/page.tsx @@ -1,13 +1,11 @@ +import { ComingSoon } from "@/components/ui"; + export default function MySubjectsPage() { return ( -
-

My Subjects

-
-

Coming Soon

-

- Subject management and resources will be available here. -

-
-
+ ); } diff --git a/stackable-app/app/api/health/route.ts b/stackable-app/app/api/health/route.ts new file mode 100644 index 0000000..dc73018 --- /dev/null +++ b/stackable-app/app/api/health/route.ts @@ -0,0 +1,18 @@ +// GET /api/health -> liveness: the process is up. Public, no dependencies checked. +import { NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; + +const startedAt = Date.now(); + +export function GET() { + return NextResponse.json( + { + status: "ok", + name: "gateway", + version: process.env.BUILD_VERSION ?? "dev", + uptimeSeconds: Math.round((Date.now() - startedAt) / 1000), + }, + { headers: { "Cache-Control": "no-store" } }, + ); +} diff --git a/stackable-app/app/api/ready/route.ts b/stackable-app/app/api/ready/route.ts new file mode 100644 index 0000000..614188f --- /dev/null +++ b/stackable-app/app/api/ready/route.ts @@ -0,0 +1,34 @@ +// GET /api/ready -> readiness: 503 when the database is down, "degraded" when only an optional dependency is. +import { NextResponse } from "next/server"; +import { getRedis } from "@/lib/api/redis"; +import { pingDatabase } from "@/lib/repositories/dev-console.repo"; + +export const dynamic = "force-dynamic"; +export const runtime = "nodejs"; + +type Check = { name: string; ok: boolean; required: boolean; latencyMs: number | null }; + +async function probe(name: string, required: boolean, run: () => Promise): Promise { + const startedAt = performance.now(); + try { + // 5 s: a sleeping serverless database can take seconds on the first query. + await Promise.race([run(), new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000))]); + return { name, ok: true, required, latencyMs: Math.round(performance.now() - startedAt) }; + } catch { + return { name, ok: false, required, latencyMs: null }; + } +} + +export async function GET() { + const redisConfigured = Boolean(process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN); + const checks = await Promise.all([ + probe("database", true, pingDatabase), + ...(redisConfigured ? [probe("redis", false, () => getRedis().ping())] : []), + ]); + const requiredDown = checks.some((c) => c.required && !c.ok); + const status = requiredDown ? "unavailable" : checks.some((c) => !c.ok) ? "degraded" : "ready"; + return NextResponse.json( + { status, checks: checks.map(({ name, ok, latencyMs }) => ({ name, ok, latencyMs })) }, + { status: requiredDown ? 503 : 200, headers: { "Cache-Control": "no-store" } }, + ); +} diff --git a/stackable-app/app/dashboard/ai/flashcard-maker/page.tsx b/stackable-app/app/dashboard/ai/flashcard-maker/page.tsx index 827b31a..262cf1d 100644 --- a/stackable-app/app/dashboard/ai/flashcard-maker/page.tsx +++ b/stackable-app/app/dashboard/ai/flashcard-maker/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function flashcardMaker() { - return ( -
-

Flashcard Maker

-
- ); +export default function AIFlashcardMakerPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/ai/homework-assistant/page.tsx b/stackable-app/app/dashboard/ai/homework-assistant/page.tsx index cddae34..d5aa98b 100644 --- a/stackable-app/app/dashboard/ai/homework-assistant/page.tsx +++ b/stackable-app/app/dashboard/ai/homework-assistant/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function homeworkAssistant() { - return ( -
-

Homework Assistant

-
- ); +export default function AIHomeworkAssistantPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/ai/quiz-generator/page.tsx b/stackable-app/app/dashboard/ai/quiz-generator/page.tsx index 8825ef7..b15129c 100644 --- a/stackable-app/app/dashboard/ai/quiz-generator/page.tsx +++ b/stackable-app/app/dashboard/ai/quiz-generator/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function quizGenerator() { - return ( -
-

Quiz Generator

-
- ); +export default function AIQuizGeneratorPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/ai/summarizer/page.tsx b/stackable-app/app/dashboard/ai/summarizer/page.tsx index cea97a4..8b21a8e 100644 --- a/stackable-app/app/dashboard/ai/summarizer/page.tsx +++ b/stackable-app/app/dashboard/ai/summarizer/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function Summarizer() { - return ( -
-

Summarizer

-
- ); +export default function AISummarizerPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/analytics/page.tsx b/stackable-app/app/dashboard/analytics/page.tsx index 0c7486b..ee1d1ce 100644 --- a/stackable-app/app/dashboard/analytics/page.tsx +++ b/stackable-app/app/dashboard/analytics/page.tsx @@ -1,9 +1,11 @@ -import React from 'react' +import { ComingSoon } from "@/components/ui"; -const analytics = () => { +export default function AnalyticsPage() { return ( -
analytics
- ) + + ); } - -export default analytics \ No newline at end of file diff --git a/stackable-app/app/dashboard/calender/page.tsx b/stackable-app/app/dashboard/calender/page.tsx index 4605eae..cec0ba6 100644 --- a/stackable-app/app/dashboard/calender/page.tsx +++ b/stackable-app/app/dashboard/calender/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
Calender
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function CalendarPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/classes/page.tsx b/stackable-app/app/dashboard/classes/page.tsx index a959bc8..f6c0d2f 100644 --- a/stackable-app/app/dashboard/classes/page.tsx +++ b/stackable-app/app/dashboard/classes/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function ClassesPage() { - return ( -
-

Classes

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/cognitive-abilities-test/page.tsx b/stackable-app/app/dashboard/cognitive-abilities-test/page.tsx index 3f517de..292f878 100644 --- a/stackable-app/app/dashboard/cognitive-abilities-test/page.tsx +++ b/stackable-app/app/dashboard/cognitive-abilities-test/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function catPage() { - return ( -
-

Cognitive Abilities Test

-
- ); +export default function CognitiveAbilitiesTestPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/error.tsx b/stackable-app/app/dashboard/error.tsx new file mode 100644 index 0000000..3c3260f --- /dev/null +++ b/stackable-app/app/dashboard/error.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/components/dashboard/PortalError"; diff --git a/stackable-app/app/dashboard/exams/page.tsx b/stackable-app/app/dashboard/exams/page.tsx index 8f1da3f..85f0a5d 100644 --- a/stackable-app/app/dashboard/exams/page.tsx +++ b/stackable-app/app/dashboard/exams/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function examsPage() { - return ( -
-

Exams

-
- ); +export default function ExamsPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/grades-reports/page.tsx b/stackable-app/app/dashboard/grades-reports/page.tsx index 03408f7..8e13239 100644 --- a/stackable-app/app/dashboard/grades-reports/page.tsx +++ b/stackable-app/app/dashboard/grades-reports/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function GradesReportsPage() { - return ( -
-

Grades & Reports

-
- ); +export default function GradesAndReportsPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/homework/page.tsx b/stackable-app/app/dashboard/homework/page.tsx index 56196a7..c56fb9a 100644 --- a/stackable-app/app/dashboard/homework/page.tsx +++ b/stackable-app/app/dashboard/homework/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function homeworkPage() { - return ( -
-

Homework

-
- ); +export default function HomeworkPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/inbox/page.tsx b/stackable-app/app/dashboard/inbox/page.tsx index c8154fa..2e6a520 100644 --- a/stackable-app/app/dashboard/inbox/page.tsx +++ b/stackable-app/app/dashboard/inbox/page.tsx @@ -1,5 +1,11 @@ -export default function inbox() { - return ( -
Inbox
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function InboxPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/library/page.tsx b/stackable-app/app/dashboard/library/page.tsx index 1cd7b85..a3b14d3 100644 --- a/stackable-app/app/dashboard/library/page.tsx +++ b/stackable-app/app/dashboard/library/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function LibraryPage() { - return ( -
-

Library

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/live-activity/page.tsx b/stackable-app/app/dashboard/live-activity/page.tsx index 983ebb8..e4be944 100644 --- a/stackable-app/app/dashboard/live-activity/page.tsx +++ b/stackable-app/app/dashboard/live-activity/page.tsx @@ -1,9 +1,11 @@ -import React from 'react' +import { ComingSoon } from "@/components/ui"; -const page = () => { +export default function LiveActivityPage() { return ( -
page
- ) + + ); } - -export default page \ No newline at end of file diff --git a/stackable-app/app/dashboard/live-activity/real-time/page.tsx b/stackable-app/app/dashboard/live-activity/real-time/page.tsx index 91e7f35..3cb55ad 100644 --- a/stackable-app/app/dashboard/live-activity/real-time/page.tsx +++ b/stackable-app/app/dashboard/live-activity/real-time/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function Page() { - return ( -
-

Page

-
- ); +export default function RealTimeEventsPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/live-activity/transport-status/page.tsx b/stackable-app/app/dashboard/live-activity/transport-status/page.tsx index 0480e66..da48b27 100644 --- a/stackable-app/app/dashboard/live-activity/transport-status/page.tsx +++ b/stackable-app/app/dashboard/live-activity/transport-status/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function TransportStatusPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/loading.tsx b/stackable-app/app/dashboard/loading.tsx new file mode 100644 index 0000000..00d9154 --- /dev/null +++ b/stackable-app/app/dashboard/loading.tsx @@ -0,0 +1 @@ +export { default } from "@/components/dashboard/PortalLoading"; diff --git a/stackable-app/app/dashboard/message-app/page.tsx b/stackable-app/app/dashboard/message-app/page.tsx index 22becdd..cafc75d 100644 --- a/stackable-app/app/dashboard/message-app/page.tsx +++ b/stackable-app/app/dashboard/message-app/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
new
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function MessagesPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/notifications/page.tsx b/stackable-app/app/dashboard/notifications/page.tsx index 18956db..39c77a1 100644 --- a/stackable-app/app/dashboard/notifications/page.tsx +++ b/stackable-app/app/dashboard/notifications/page.tsx @@ -1,9 +1,11 @@ -import React from 'react' +import { ComingSoon } from "@/components/ui"; -const notifications = () => { +export default function NotificationsPage() { return ( -
notifications
- ) + + ); } - -export default notifications \ No newline at end of file diff --git a/stackable-app/app/dashboard/parents/page.tsx b/stackable-app/app/dashboard/parents/page.tsx index 0b1b9d2..93396d4 100644 --- a/stackable-app/app/dashboard/parents/page.tsx +++ b/stackable-app/app/dashboard/parents/page.tsx @@ -1,7 +1,11 @@ -export default function Parents() { - return ( - <> - Parents - - ) - }; \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function ParentsAndGuardiansPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/parents/parents-resources/page.tsx b/stackable-app/app/dashboard/parents/parents-resources/page.tsx index b7146f8..404d30a 100644 --- a/stackable-app/app/dashboard/parents/parents-resources/page.tsx +++ b/stackable-app/app/dashboard/parents/parents-resources/page.tsx @@ -1,7 +1,11 @@ -export default function parentResources() { - return ( - <> - Resources - - ) - }; \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function ParentResourcesPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/payments/events/maintenance/page.tsx b/stackable-app/app/dashboard/payments/events/maintenance/page.tsx index 3de74e1..0933fb5 100644 --- a/stackable-app/app/dashboard/payments/events/maintenance/page.tsx +++ b/stackable-app/app/dashboard/payments/events/maintenance/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
Page
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function EventMaintenancePage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/payments/events/page.tsx b/stackable-app/app/dashboard/payments/events/page.tsx index 0480e66..1c3c593 100644 --- a/stackable-app/app/dashboard/payments/events/page.tsx +++ b/stackable-app/app/dashboard/payments/events/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function EventPaymentsPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/payments/page.tsx b/stackable-app/app/dashboard/payments/page.tsx index 983ebb8..9cf8a65 100644 --- a/stackable-app/app/dashboard/payments/page.tsx +++ b/stackable-app/app/dashboard/payments/page.tsx @@ -1,9 +1,11 @@ -import React from 'react' +import { ComingSoon } from "@/components/ui"; -const page = () => { +export default function PaymentsPage() { return ( -
page
- ) + + ); } - -export default page \ No newline at end of file diff --git a/stackable-app/app/dashboard/payments/salaries/page.tsx b/stackable-app/app/dashboard/payments/salaries/page.tsx index 589254d..b8fe654 100644 --- a/stackable-app/app/dashboard/payments/salaries/page.tsx +++ b/stackable-app/app/dashboard/payments/salaries/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
salaries
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function SalariesPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/payments/school-fees/page.tsx b/stackable-app/app/dashboard/payments/school-fees/page.tsx index b9d3567..f9ded15 100644 --- a/stackable-app/app/dashboard/payments/school-fees/page.tsx +++ b/stackable-app/app/dashboard/payments/school-fees/page.tsx @@ -1,5 +1,11 @@ -export default function Page() { - return ( -
fees
- ); -} \ No newline at end of file +import { ComingSoon } from "@/components/ui"; + +export default function SchoolFeesPage() { + return ( + + ); +} diff --git a/stackable-app/app/dashboard/quizes/page.tsx b/stackable-app/app/dashboard/quizes/page.tsx index 778c1ab..79e897b 100644 --- a/stackable-app/app/dashboard/quizes/page.tsx +++ b/stackable-app/app/dashboard/quizes/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function quizesPage() { - return ( -
-

Quizes

-
- ); +export default function QuizzesPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/staff/duty-allocation/page.tsx b/stackable-app/app/dashboard/staff/duty-allocation/page.tsx index 31270f4..3a17e05 100644 --- a/stackable-app/app/dashboard/staff/duty-allocation/page.tsx +++ b/stackable-app/app/dashboard/staff/duty-allocation/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function DutyAllocationPage() { - return ( -
-

Duty Allocation

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/staff/page.tsx b/stackable-app/app/dashboard/staff/page.tsx index fdc579a..13a9eba 100644 --- a/stackable-app/app/dashboard/staff/page.tsx +++ b/stackable-app/app/dashboard/staff/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function StaffPage() { - return ( -
-

Staff Dashboard

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/staff/staff-resources/page.tsx b/stackable-app/app/dashboard/staff/staff-resources/page.tsx index 0fc9209..772a081 100644 --- a/stackable-app/app/dashboard/staff/staff-resources/page.tsx +++ b/stackable-app/app/dashboard/staff/staff-resources/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function StaffResourcesPage() { - return ( -
-

Staff Resources

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/students/allocation/page.tsx b/stackable-app/app/dashboard/students/allocation/page.tsx index 0de65da..276c31e 100644 --- a/stackable-app/app/dashboard/students/allocation/page.tsx +++ b/stackable-app/app/dashboard/students/allocation/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; -export default function AllocationPage() { - return ( -
-

Allocation

-
- ); +export default function StudentAllocationPage() { + return ( + + ); } diff --git a/stackable-app/app/dashboard/students/students-resources/page.tsx b/stackable-app/app/dashboard/students/students-resources/page.tsx index 7352ca6..8e54953 100644 --- a/stackable-app/app/dashboard/students/students-resources/page.tsx +++ b/stackable-app/app/dashboard/students/students-resources/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function StudentResourcesPage() { - return ( -
-

Student Resources

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/teachers/load-allocation/page.tsx b/stackable-app/app/dashboard/teachers/load-allocation/page.tsx index d50f0e2..9153262 100644 --- a/stackable-app/app/dashboard/teachers/load-allocation/page.tsx +++ b/stackable-app/app/dashboard/teachers/load-allocation/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function LoadAllocationPage() { - return ( -
-

Load Allocation

-
- ); + return ( + + ); } diff --git a/stackable-app/app/dashboard/teachers/teachers-resources/page.tsx b/stackable-app/app/dashboard/teachers/teachers-resources/page.tsx index 0b45c1e..e018dd2 100644 --- a/stackable-app/app/dashboard/teachers/teachers-resources/page.tsx +++ b/stackable-app/app/dashboard/teachers/teachers-resources/page.tsx @@ -1,8 +1,11 @@ +import { ComingSoon } from "@/components/ui"; export default function TeacherResourcesPage() { - return ( -
-

Teacher Resources

-
- ); + return ( + + ); } diff --git a/stackable-app/app/demo/page.tsx b/stackable-app/app/demo/page.tsx new file mode 100644 index 0000000..ea1585d --- /dev/null +++ b/stackable-app/app/demo/page.tsx @@ -0,0 +1,52 @@ +"use client"; + +// Public demo entry: pick a role, explore the real screens with sample data. No login, nothing is saved. +import { Icon } from "@/components/ui"; +import { startDemo } from "@/components/demo/DemoProvider"; +import { ROLE_HOME, type Role } from "@/lib/validation/shared"; + +const ROLES_SHOWN: Array<{ role: Role; label: string; blurb: string; icon: string }> = [ + { role: "parent", label: "Parent", blurb: "Children, fees, notices and safety alerts.", icon: "user-hands" }, + { role: "student", label: "Student", blurb: "Homework, the AI tutor, tests and grades.", icon: "square-academic-cap" }, + { role: "teacher", label: "Teacher", blurb: "Classes, marking, attendance and timetable.", icon: "users-group-two-rounded" }, + { role: "admin", label: "Principal", blurb: "School overview, people, academics and approvals.", icon: "buildings-2" }, + { role: "finance", label: "Finance", blurb: "Fee structures, invoices and reconciliation.", icon: "wallet-money" }, + { role: "secretary", label: "Secretary", blurb: "Records, pickup checks and announcements.", icon: "folder-with-files" }, + { role: "driver", label: "Driver", blurb: "Route manifest and bus duty.", icon: "bus" }, + { role: "super-admin", label: "Platform admin", blurb: "Schools, users, audit trail and integrations.", icon: "code-square" }, +]; + +export default function DemoPage() { + return ( +
+

Try Stackable

+

+ Choose a role to explore the screens with sample data. No sign-in needed. Anything you change is kept only + for this browser session and disappears when it ends. +

+
    + {ROLES_SHOWN.map(({ role, label, blurb, icon }) => ( +
  • + +
  • + ))} +
+

+ Some pages show "Coming soon" while we build them. Have an account? Sign in. +

+
+ ); +} diff --git a/stackable-app/app/error.tsx b/stackable-app/app/error.tsx new file mode 100644 index 0000000..123073c --- /dev/null +++ b/stackable-app/app/error.tsx @@ -0,0 +1,28 @@ +"use client"; + +import * as Sentry from "@sentry/nextjs"; +import { useEffect } from "react"; +import { Button, EmptyState } from "@/components/ui"; + +export default function RootError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( +
+ Try again} + /> +
+ ); +} diff --git a/stackable-app/app/layout.tsx b/stackable-app/app/layout.tsx index 2b631c7..f8dbd4f 100644 --- a/stackable-app/app/layout.tsx +++ b/stackable-app/app/layout.tsx @@ -10,6 +10,7 @@ import Acumin from 'next/font/local'; import { ToastProvider } from "../components/toast/ToastProvider"; import { ConfirmationProvider } from "../components/confirmation/ConfirmationProvider"; import { QueryProvider } from "@/components/providers/QueryProvider"; +import { DemoProvider } from "@/components/demo/DemoProvider"; import { PostHogProvider } from "@/components/providers/PostHogProvider"; import { ThemeProvider as NextThemesProvider } from "next-themes"; import type { ThemeProviderProps } from "next-themes"; @@ -128,11 +129,13 @@ export default function RootLayout({ className={`${geistSans.variable} ${openSans.variable} ${geistMono.variable} ${Abeezee.variable} ${inter.variable} ${poppins.variable} ${acumin.variable} ${plusJakartaSans.variable} ${manrope.variable} ${spaceGrotesk.variable} antialiased `}> - - - {children} - - + + + + {children} + + + diff --git a/stackable-app/app/not-found.tsx b/stackable-app/app/not-found.tsx new file mode 100644 index 0000000..8441348 --- /dev/null +++ b/stackable-app/app/not-found.tsx @@ -0,0 +1,18 @@ +import { Button, EmptyState } from "@/components/ui"; + +export default function NotFound() { + return ( +
+ + Go home + + } + /> +
+ ); +} diff --git a/stackable-app/components/admin/users/Badges.tsx b/stackable-app/components/admin/users/Badges.tsx index d450b76..15d9140 100644 --- a/stackable-app/components/admin/users/Badges.tsx +++ b/stackable-app/components/admin/users/Badges.tsx @@ -15,6 +15,10 @@ const ROLE_TONE: Record = { pupil: "warning", parent: "neutral", staff: "neutral", + "dept-head": "success", + finance: "info", + secretary: "neutral", + driver: "neutral", }; export function RoleBadge({ role, size = "md" }: { role: Role; size?: "sm" | "md" }) { diff --git a/stackable-app/components/admin/users/userUtils.ts b/stackable-app/components/admin/users/userUtils.ts index d0ce45b..20224d2 100644 --- a/stackable-app/components/admin/users/userUtils.ts +++ b/stackable-app/components/admin/users/userUtils.ts @@ -17,6 +17,10 @@ export const ROLE_LABEL: Record = { student: "Student", pupil: "Pupil", staff: "Staff", + "dept-head": "Department head", + finance: "Finance", + secretary: "Secretary", + driver: "Driver", }; export const STATUS_LABEL: Record = { diff --git a/stackable-app/components/dashboard/MobileBottomNav.tsx b/stackable-app/components/dashboard/MobileBottomNav.tsx index 5203176..8285dbd 100644 --- a/stackable-app/components/dashboard/MobileBottomNav.tsx +++ b/stackable-app/components/dashboard/MobileBottomNav.tsx @@ -16,6 +16,7 @@ import { usePathname } from "next/navigation"; import type { Ref } from "react"; import { Icon } from "@/components/ui/Icon"; import { cn } from "@/lib/cn"; +import { useMe } from "@/hooks/useMe"; import { activeNavHref, primaryNav } from "@/lib/nav"; import type { Portal } from "@/lib/validation/shared"; import { NavIcon } from "@/components/sidebar/sidebar"; @@ -35,8 +36,9 @@ const ITEM = export function MobileBottomNav({ portal, menuOpen, onOpenMenu, moreRef, inert }: MobileBottomNavProps) { const pathname = usePathname(); - const activeHref = activeNavHref(portal, pathname); - const items = primaryNav(portal, 4); + const role = useMe().data?.role; + const activeHref = activeNavHref(portal, pathname, role); + const items = primaryNav(portal, 4, role); return (